From 530495029ab0759d7ac750b401e6cd1fb96bf94d Mon Sep 17 00:00:00 2001 From: Dmitry Shachnev Date: Fri, 16 Dec 2016 18:07:41 +0000 Subject: [PATCH 1/1] Import qtlocation-opensource-src_5.7.1.orig.tar.xz [dgit import orig qtlocation-opensource-src_5.7.1.orig.tar.xz] --- .qmake.conf | 3 + .tag | 1 + LGPL_EXCEPTION.txt | 22 + LICENSE.FDL | 450 ++ LICENSE.GPL2 | 339 ++ LICENSE.GPL3 | 674 +++ LICENSE.GPL3-EXCEPT | 704 +++ LICENSE.GPLv2 | 292 ++ LICENSE.GPLv3 | 686 +++ LICENSE.LGPL3 | 165 + LICENSE.LGPLv21 | 514 ++ LICENSE.LGPLv3 | 175 + config.tests/gypsy/gypsy.pro | 6 + config.tests/gypsy/main.cpp | 44 + dist/changes-5.2.1 | 53 + dist/changes-5.3.0 | 74 + dist/changes-5.3.1 | 57 + dist/changes-5.3.2 | 50 + dist/changes-5.4.0 | 54 + dist/changes-5.4.1 | 29 + dist/changes-5.4.2 | 28 + dist/changes-5.5.0 | 40 + dist/changes-5.5.1 | 54 + dist/changes-5.6.0 | 69 + dist/changes-5.6.1 | 38 + dist/changes-5.6.2 | 55 + dist/changes-5.7.0 | 53 + dist/changes-5.7.1 | 45 + examples/examples.pro | 7 + examples/location/location.pro | 10 + .../mapviewer/doc/images/mapviewer.png | Bin 0 -> 359712 bytes .../location/mapviewer/doc/src/mapviewer.qdoc | 171 + examples/location/mapviewer/forms/Geocode.qml | 81 + .../mapviewer/forms/GeocodeForm.ui.qml | 173 + examples/location/mapviewer/forms/Locale.qml | 82 + .../mapviewer/forms/LocaleForm.ui.qml | 153 + examples/location/mapviewer/forms/Message.qml | 58 + .../mapviewer/forms/MessageForm.ui.qml | 106 + .../mapviewer/forms/ReverseGeocode.qml | 75 + .../mapviewer/forms/ReverseGeocodeForm.ui.qml | 140 + .../location/mapviewer/forms/RouteAddress.qml | 142 + .../mapviewer/forms/RouteAddressForm.ui.qml | 197 + .../mapviewer/forms/RouteCoordinate.qml | 79 + .../forms/RouteCoordinateForm.ui.qml | 173 + .../location/mapviewer/forms/RouteList.qml | 87 + .../mapviewer/forms/RouteListDelegate.qml | 82 + .../mapviewer/forms/RouteListHeader.qml | 84 + examples/location/mapviewer/helper.js | 81 + examples/location/mapviewer/main.cpp | 109 + .../location/mapviewer/map/CircleItem.qml | 58 + examples/location/mapviewer/map/ImageItem.qml | 61 + .../location/mapviewer/map/MapComponent.qml | 641 +++ examples/location/mapviewer/map/Marker.qml | 116 + examples/location/mapviewer/map/MiniMap.qml | 81 + .../location/mapviewer/map/PolygonItem.qml | 63 + .../location/mapviewer/map/PolylineItem.qml | 57 + .../location/mapviewer/map/RectangleItem.qml | 59 + examples/location/mapviewer/mapviewer.pro | 41 + examples/location/mapviewer/mapviewer.qml | 487 ++ examples/location/mapviewer/mapviewer.qrc | 37 + .../mapviewer/menus/ItemPopupMenu.qml | 53 + .../location/mapviewer/menus/MainMenu.qml | 132 + .../location/mapviewer/menus/MapPopupMenu.qml | 64 + .../mapviewer/menus/MarkerPopupMenu.qml | 76 + .../location/mapviewer/resources/icon.png | Bin 0 -> 1859 bytes .../location/mapviewer/resources/marker.png | Bin 0 -> 752 bytes .../location/mapviewer/resources/scale.png | Bin 0 -> 98 bytes .../mapviewer/resources/scale_end.png | Bin 0 -> 93 bytes .../minimal_map/doc/images/minimal_map.png | Bin 0 -> 156053 bytes .../minimal_map/doc/src/minimal_map.qdoc | 89 + examples/location/minimal_map/main.cpp | 53 + examples/location/minimal_map/main.qml | 67 + examples/location/minimal_map/minimal_map.pro | 10 + examples/location/minimal_map/qml.qrc | 6 + .../location/places/doc/images/places.png | Bin 0 -> 151611 bytes examples/location/places/doc/src/places.qdoc | 166 + examples/location/places/forms/Message.qml | 58 + .../location/places/forms/MessageForm.ui.qml | 106 + .../location/places/forms/PlaceDetails.qml | 127 + .../places/forms/PlaceDetailsForm.ui.qml | 285 + .../places/forms/SearchBoundingBox.qml | 71 + .../places/forms/SearchBoundingBoxForm.ui.qml | 163 + .../places/forms/SearchBoundingCircle.qml | 69 + .../forms/SearchBoundingCircleForm.ui.qml | 151 + .../location/places/forms/SearchCenter.qml | 70 + .../places/forms/SearchCenterForm.ui.qml | 140 + .../location/places/forms/SearchOptions.qml | 78 + .../places/forms/SearchOptionsForm.ui.qml | 159 + examples/location/places/helper.js | 53 + examples/location/places/items/MainMenu.qml | 91 + .../location/places/items/MapComponent.qml | 214 + examples/location/places/items/SearchBar.qml | 122 + examples/location/places/main.cpp | 108 + examples/location/places/places.pro | 44 + examples/location/places/places.qml | 497 ++ examples/location/places/places.qrc | 42 + .../location/places/resources/categories.png | Bin 0 -> 130 bytes examples/location/places/resources/left.png | Bin 0 -> 141 bytes examples/location/places/resources/marker.png | Bin 0 -> 752 bytes examples/location/places/resources/right.png | Bin 0 -> 147 bytes examples/location/places/resources/scale.png | Bin 0 -> 98 bytes .../location/places/resources/scale_end.png | Bin 0 -> 93 bytes examples/location/places/resources/search.png | Bin 0 -> 259 bytes examples/location/places/resources/star.png | Bin 0 -> 1403 bytes .../places/views/CategoryDelegate.qml | 104 + .../location/places/views/CategoryView.qml | 67 + .../places/views/EditorialDelegate.qml | 86 + .../location/places/views/EditorialPage.qml | 114 + .../location/places/views/EditorialView.qml | 57 + examples/location/places/views/ImageView.qml | 151 + examples/location/places/views/RatingView.qml | 55 + .../location/places/views/ReviewDelegate.qml | 98 + examples/location/places/views/ReviewPage.qml | 123 + examples/location/places/views/ReviewView.qml | 57 + .../places/views/SearchResultDelegate.qml | 196 + .../places/views/SearchResultView.qml | 91 + .../location/places/views/SuggestionView.qml | 67 + examples/location/places_list/Marker.qml | 59 + .../places_list/doc/images/places_list.png | Bin 0 -> 18195 bytes .../places_list/doc/src/places_list.qdoc | 76 + examples/location/places_list/main.cpp | 51 + examples/location/places_list/marker.png | Bin 0 -> 752 bytes examples/location/places_list/places_list.pro | 12 + examples/location/places_list/places_list.qml | 101 + examples/location/places_list/places_list.qrc | 7 + .../places_map/doc/images/places_map.png | Bin 0 -> 167995 bytes .../places_map/doc/src/places_map.qdoc | 83 + examples/location/places_map/main.cpp | 51 + examples/location/places_map/marker.png | Bin 0 -> 752 bytes examples/location/places_map/places_map.pro | 12 + examples/location/places_map/places_map.qml | 129 + examples/location/places_map/places_map.qrc | 6 + examples/location/planespotter/Plane.qml | 115 + examples/location/planespotter/airplane.png | Bin 0 -> 831 bytes .../planespotter/doc/images/planespotter.png | Bin 0 -> 139390 bytes .../planespotter/doc/src/planespotter.qdoc | 143 + examples/location/planespotter/main.cpp | 201 + .../location/planespotter/planespotter.pro | 10 + .../location/planespotter/planespotter.qml | 218 + examples/location/planespotter/qml.qrc | 7 + .../geoflickr/doc/images/qml-flickr-1.jpg | Bin 0 -> 66794 bytes .../geoflickr/doc/src/geoflickr.qdoc | 87 + examples/positioning/geoflickr/flickr-90.qml | 51 + examples/positioning/geoflickr/flickr.qml | 137 + examples/positioning/geoflickr/flickr.qrc | 30 + .../geoflickr/flickrcommon/Progress.qml | 72 + .../geoflickr/flickrcommon/RestModel.qml | 65 + .../geoflickr/flickrcommon/ScrollBar.qml | 80 + .../geoflickr/flickrcommon/Slider.qml | 76 + .../geoflickr/flickrmobile/Button.qml | 78 + .../geoflickr/flickrmobile/GeoTab.qml | 171 + .../geoflickr/flickrmobile/GridDelegate.qml | 112 + .../geoflickr/flickrmobile/ImageDetails.qml | 156 + .../geoflickr/flickrmobile/ListDelegate.qml | 66 + .../geoflickr/flickrmobile/TitleBar.qml | 77 + .../geoflickr/flickrmobile/ToolBar.qml | 64 + .../geoflickr/flickrmobile/images/gloss.png | Bin 0 -> 889 bytes .../flickrmobile/images/lineedit.png | Bin 0 -> 1307 bytes .../flickrmobile/images/lineedit.sci | 5 + .../geoflickr/flickrmobile/images/moon.png | Bin 0 -> 2366 bytes .../geoflickr/flickrmobile/images/quit.png | Bin 0 -> 1785 bytes .../geoflickr/flickrmobile/images/star.png | Bin 0 -> 259 bytes .../geoflickr/flickrmobile/images/stripes.png | Bin 0 -> 159 bytes .../geoflickr/flickrmobile/images/sun.png | Bin 0 -> 8110 bytes .../flickrmobile/images/titlebar.png | Bin 0 -> 1327 bytes .../flickrmobile/images/titlebar.sci | 5 + .../flickrmobile/images/toolbutton.png | Bin 0 -> 2550 bytes .../flickrmobile/images/toolbutton.sci | 5 + .../geoflickr/flickrmobile/nmealog.txt | 1403 +++++ examples/positioning/geoflickr/geoflickr.pro | 15 + .../geoflickr/geoflickr.qmlproject | 16 + .../geoflickr/qmllocationflickr.cpp | 58 + .../clientapplication.cpp | 62 + .../logfilepositionsource/clientapplication.h | 64 + .../doc/src/logfilepositionsource.qdoc | 87 + .../logfilepositionsource/logfile.qrc | 5 + .../logfilepositionsource.cpp | 122 + .../logfilepositionsource.h | 77 + .../logfilepositionsource.pro | 16 + .../logfilepositionsource/main.cpp | 52 + .../logfilepositionsource/simplelog.txt | 156 + examples/positioning/positioning.pro | 4 + .../doc/images/example-satelliteinfo.png | Bin 0 -> 27371 bytes .../satelliteinfo/doc/src/satelliteinfo.qdoc | 75 + examples/positioning/satelliteinfo/main.cpp | 62 + .../satelliteinfo/satelliteinfo.pro | 22 + .../satelliteinfo/satelliteinfo.qml | 304 ++ .../satelliteinfo/satelliteinfo.qrc | 5 + .../satelliteinfo/satellitemodel.cpp | 309 ++ .../satelliteinfo/satellitemodel.h | 124 + examples/positioning/weatherinfo/appmodel.cpp | 580 +++ examples/positioning/weatherinfo/appmodel.h | 173 + .../components/BigForecastIcon.qml | 83 + .../weatherinfo/components/ForecastIcon.qml | 89 + .../weatherinfo/components/WeatherIcon.qml | 102 + .../doc/images/example-weatherinfo.png | Bin 0 -> 82081 bytes .../weatherinfo/doc/src/weatherinfo.qdoc | 113 + .../positioning/weatherinfo/icons/README.txt | 5 + .../weatherinfo/icons/weather-few-clouds.png | Bin 0 -> 79488 bytes .../weatherinfo/icons/weather-fog.png | Bin 0 -> 43896 bytes .../weatherinfo/icons/weather-haze.png | Bin 0 -> 130030 bytes .../weatherinfo/icons/weather-icy.png | Bin 0 -> 49362 bytes .../weatherinfo/icons/weather-overcast.png | Bin 0 -> 60290 bytes .../weatherinfo/icons/weather-showers.png | Bin 0 -> 76340 bytes .../weatherinfo/icons/weather-sleet.png | Bin 0 -> 87168 bytes .../weatherinfo/icons/weather-snow.png | Bin 0 -> 70939 bytes .../weatherinfo/icons/weather-storm.png | Bin 0 -> 93207 bytes .../icons/weather-sunny-very-few-clouds.png | Bin 0 -> 65731 bytes .../weatherinfo/icons/weather-sunny.png | Bin 0 -> 59084 bytes .../icons/weather-thundershower.png | Bin 0 -> 105643 bytes examples/positioning/weatherinfo/main.cpp | 72 + .../positioning/weatherinfo/weatherinfo.pro | 21 + .../positioning/weatherinfo/weatherinfo.qml | 230 + .../positioning/weatherinfo/weatherinfo.qrc | 20 + .../private/qabstractgeotilecache_p.h | 1 + .../5.7.1/QtLocation/private/qcache3q_p.h | 1 + .../private/qgeocameracapabilities_p.h | 1 + .../QtLocation/private/qgeocameradata_p.h | 1 + .../QtLocation/private/qgeocameratiles_p.h | 1 + .../QtLocation/private/qgeocodereply_p.h | 1 + .../QtLocation/private/qgeocodingmanager_p.h | 1 + .../private/qgeocodingmanagerengine_p.h | 1 + .../QtLocation/private/qgeofiletilecache_p.h | 1 + .../5.7.1/QtLocation/private/qgeomaneuver_p.h | 1 + .../5.7.1/QtLocation/private/qgeomap_p.h | 1 + .../5.7.1/QtLocation/private/qgeomap_p_p.h | 1 + .../QtLocation/private/qgeomappingmanager_p.h | 1 + .../private/qgeomappingmanager_p_p.h | 1 + .../private/qgeomappingmanagerengine_p.h | 1 + .../private/qgeomappingmanagerengine_p_p.h | 1 + .../5.7.1/QtLocation/private/qgeomaptype_p.h | 1 + .../QtLocation/private/qgeomaptype_p_p.h | 1 + .../5.7.1/QtLocation/private/qgeoroute_p.h | 1 + .../QtLocation/private/qgeorouteparser_p.h | 1 + .../QtLocation/private/qgeorouteparser_p_p.h | 1 + .../private/qgeorouteparserosrmv4_p.h | 1 + .../private/qgeorouteparserosrmv5_p.h | 1 + .../QtLocation/private/qgeoroutereply_p.h | 1 + .../QtLocation/private/qgeorouterequest_p.h | 1 + .../QtLocation/private/qgeoroutesegment_p.h | 1 + .../QtLocation/private/qgeoroutingmanager_p.h | 1 + .../private/qgeoroutingmanagerengine_p.h | 1 + .../private/qgeoserviceprovider_p.h | 1 + .../5.7.1/QtLocation/private/qgeotiledmap_p.h | 1 + .../QtLocation/private/qgeotiledmap_p_p.h | 1 + .../private/qgeotiledmappingmanagerengine_p.h | 1 + .../qgeotiledmappingmanagerengine_p_p.h | 1 + .../QtLocation/private/qgeotiledmapreply_p.h | 1 + .../private/qgeotiledmapreply_p_p.h | 1 + .../QtLocation/private/qgeotiledmapscene_p.h | 1 + .../QtLocation/private/qgeotilefetcher_p.h | 1 + .../QtLocation/private/qgeotilefetcher_p_p.h | 1 + .../private/qgeotilerequestmanager_p.h | 1 + .../5.7.1/QtLocation/private/qgeotilespec_p.h | 1 + .../QtLocation/private/qgeotilespec_p_p.h | 1 + .../5.7.1/QtLocation/private/qplace_p.h | 1 + .../QtLocation/private/qplaceattribute_p.h | 1 + .../QtLocation/private/qplacecategory_p.h | 1 + .../private/qplacecontactdetail_p.h | 1 + .../QtLocation/private/qplacecontent_p.h | 1 + .../private/qplacecontentrequest_p.h | 1 + .../QtLocation/private/qplaceeditorial_p.h | 1 + .../5.7.1/QtLocation/private/qplaceicon_p.h | 1 + .../5.7.1/QtLocation/private/qplaceimage_p.h | 1 + .../private/qplacemanagerengine_p.h | 1 + .../private/qplaceproposedsearchresult_p.h | 1 + .../QtLocation/private/qplaceratings_p.h | 1 + .../5.7.1/QtLocation/private/qplacereply_p.h | 1 + .../5.7.1/QtLocation/private/qplaceresult_p.h | 1 + .../5.7.1/QtLocation/private/qplacereview_p.h | 1 + .../QtLocation/private/qplacesearchresult_p.h | 1 + .../QtLocation/private/qplacesupplier_p.h | 1 + .../5.7.1/QtLocation/private/qplaceuser_p.h | 1 + .../QtLocation/private/unsupportedreplies_p.h | 1 + include/QtLocation/QGeoCodeReply | 1 + include/QtLocation/QGeoCodingManager | 1 + include/QtLocation/QGeoCodingManagerEngine | 1 + include/QtLocation/QGeoManeuver | 1 + include/QtLocation/QGeoRoute | 1 + include/QtLocation/QGeoRouteReply | 1 + include/QtLocation/QGeoRouteRequest | 1 + include/QtLocation/QGeoRouteSegment | 1 + include/QtLocation/QGeoRoutingManager | 1 + include/QtLocation/QGeoRoutingManagerEngine | 1 + include/QtLocation/QGeoServiceProvider | 1 + include/QtLocation/QGeoServiceProviderFactory | 1 + include/QtLocation/QLocation | 1 + include/QtLocation/QPlace | 1 + include/QtLocation/QPlaceAttribute | 1 + include/QtLocation/QPlaceCategory | 1 + include/QtLocation/QPlaceContactDetail | 1 + include/QtLocation/QPlaceContent | 1 + include/QtLocation/QPlaceContentReply | 1 + include/QtLocation/QPlaceContentRequest | 1 + include/QtLocation/QPlaceDetailsReply | 1 + include/QtLocation/QPlaceEditorial | 1 + include/QtLocation/QPlaceIcon | 1 + include/QtLocation/QPlaceIdReply | 1 + include/QtLocation/QPlaceImage | 1 + include/QtLocation/QPlaceManager | 1 + include/QtLocation/QPlaceManagerEngine | 1 + include/QtLocation/QPlaceMatchReply | 1 + include/QtLocation/QPlaceMatchRequest | 1 + include/QtLocation/QPlaceProposedSearchResult | 1 + include/QtLocation/QPlaceRatings | 1 + include/QtLocation/QPlaceReply | 1 + include/QtLocation/QPlaceResult | 1 + include/QtLocation/QPlaceReview | 1 + include/QtLocation/QPlaceSearchReply | 1 + include/QtLocation/QPlaceSearchRequest | 1 + include/QtLocation/QPlaceSearchResult | 1 + .../QtLocation/QPlaceSearchSuggestionReply | 1 + include/QtLocation/QPlaceSupplier | 1 + include/QtLocation/QPlaceUser | 1 + include/QtLocation/QtLocation | 47 + include/QtLocation/QtLocationVersion | 1 + include/QtLocation/headers.pri | 6 + include/QtLocation/placemacro.h | 1 + include/QtLocation/qgeocodereply.h | 1 + include/QtLocation/qgeocodingmanager.h | 1 + include/QtLocation/qgeocodingmanagerengine.h | 1 + include/QtLocation/qgeomaneuver.h | 1 + include/QtLocation/qgeoroute.h | 1 + include/QtLocation/qgeoroutereply.h | 1 + include/QtLocation/qgeorouterequest.h | 1 + include/QtLocation/qgeoroutesegment.h | 1 + include/QtLocation/qgeoroutingmanager.h | 1 + include/QtLocation/qgeoroutingmanagerengine.h | 1 + include/QtLocation/qgeoserviceprovider.h | 1 + .../QtLocation/qgeoserviceproviderfactory.h | 1 + include/QtLocation/qlocation.h | 1 + include/QtLocation/qlocationglobal.h | 1 + include/QtLocation/qplace.h | 1 + include/QtLocation/qplaceattribute.h | 1 + include/QtLocation/qplacecategory.h | 1 + include/QtLocation/qplacecontactdetail.h | 1 + include/QtLocation/qplacecontent.h | 1 + include/QtLocation/qplacecontentreply.h | 1 + include/QtLocation/qplacecontentrequest.h | 1 + include/QtLocation/qplacedetailsreply.h | 1 + include/QtLocation/qplaceeditorial.h | 1 + include/QtLocation/qplaceicon.h | 1 + include/QtLocation/qplaceidreply.h | 1 + include/QtLocation/qplaceimage.h | 1 + include/QtLocation/qplacemanager.h | 1 + include/QtLocation/qplacemanagerengine.h | 1 + include/QtLocation/qplacematchreply.h | 1 + include/QtLocation/qplacematchrequest.h | 1 + .../QtLocation/qplaceproposedsearchresult.h | 1 + include/QtLocation/qplaceratings.h | 1 + include/QtLocation/qplacereply.h | 1 + include/QtLocation/qplaceresult.h | 1 + include/QtLocation/qplacereview.h | 1 + include/QtLocation/qplacesearchreply.h | 1 + include/QtLocation/qplacesearchrequest.h | 1 + include/QtLocation/qplacesearchresult.h | 1 + .../QtLocation/qplacesearchsuggestionreply.h | 1 + include/QtLocation/qplacesupplier.h | 1 + include/QtLocation/qplaceuser.h | 1 + include/QtLocation/qtlocationversion.h | 9 + .../private/qdeclarativegeoaddress_p.h | 1 + .../private/qdeclarativegeolocation_p.h | 1 + .../QtPositioning/private/qdoublevector2d_p.h | 1 + .../QtPositioning/private/qdoublevector3d_p.h | 1 + .../QtPositioning/private/qgeoaddress_p.h | 1 + .../QtPositioning/private/qgeocircle_p.h | 1 + .../QtPositioning/private/qgeocoordinate_p.h | 1 + .../QtPositioning/private/qgeolocation_p.h | 1 + .../private/qgeopositioninfosource_p.h | 1 + .../QtPositioning/private/qgeoprojection_p.h | 1 + .../QtPositioning/private/qgeorectangle_p.h | 1 + .../5.7.1/QtPositioning/private/qgeoshape_p.h | 1 + .../private/qlocationdata_simulator_p.h | 1 + .../QtPositioning/private/qlocationutils_p.h | 1 + .../private/qnmeapositioninfosource_p.h | 1 + .../private/qpositioningglobal_p.h | 1 + include/QtPositioning/QGeoAddress | 1 + include/QtPositioning/QGeoAreaMonitorInfo | 1 + include/QtPositioning/QGeoAreaMonitorSource | 1 + include/QtPositioning/QGeoCircle | 1 + include/QtPositioning/QGeoCoordinate | 1 + include/QtPositioning/QGeoLocation | 1 + include/QtPositioning/QGeoPositionInfo | 1 + include/QtPositioning/QGeoPositionInfoSource | 1 + .../QGeoPositionInfoSourceFactory | 1 + include/QtPositioning/QGeoRectangle | 1 + include/QtPositioning/QGeoSatelliteInfo | 1 + include/QtPositioning/QGeoSatelliteInfoSource | 1 + include/QtPositioning/QGeoShape | 1 + include/QtPositioning/QNmeaPositionInfoSource | 1 + include/QtPositioning/QtPositioning | 20 + include/QtPositioning/QtPositioningVersion | 1 + include/QtPositioning/headers.pri | 6 + include/QtPositioning/qgeoaddress.h | 1 + include/QtPositioning/qgeoareamonitorinfo.h | 1 + include/QtPositioning/qgeoareamonitorsource.h | 1 + include/QtPositioning/qgeocircle.h | 1 + include/QtPositioning/qgeocoordinate.h | 1 + include/QtPositioning/qgeolocation.h | 1 + include/QtPositioning/qgeopositioninfo.h | 1 + .../QtPositioning/qgeopositioninfosource.h | 1 + .../qgeopositioninfosourcefactory.h | 1 + include/QtPositioning/qgeorectangle.h | 1 + include/QtPositioning/qgeosatelliteinfo.h | 1 + .../QtPositioning/qgeosatelliteinfosource.h | 1 + include/QtPositioning/qgeoshape.h | 1 + .../QtPositioning/qnmeapositioninfosource.h | 1 + include/QtPositioning/qpositioningglobal.h | 1 + include/QtPositioning/qtpositioningversion.h | 9 + qtlocation.pro | 4 + src/3rdparty/3rdparty.pro | 4 + src/3rdparty/clip2tri/LICENSE | 21 + src/3rdparty/clip2tri/clip2tri.cpp | 312 ++ src/3rdparty/clip2tri/clip2tri.h | 86 + src/3rdparty/clip2tri/clip2tri.pro | 18 + src/3rdparty/clip2tri_legal.qdoc | 32 + src/3rdparty/clipper/clipper.cpp | 4605 +++++++++++++++++ src/3rdparty/clipper/clipper.h | 398 ++ src/3rdparty/clipper/clipper.pro | 17 + src/3rdparty/clipper_legal.qdoc | 34 + src/3rdparty/poly2tri/AUTHORS | 8 + src/3rdparty/poly2tri/LICENSE | 27 + src/3rdparty/poly2tri/common/shapes.cpp | 363 ++ src/3rdparty/poly2tri/common/shapes.h | 325 ++ src/3rdparty/poly2tri/common/utils.h | 127 + src/3rdparty/poly2tri/poly2tri.h | 39 + src/3rdparty/poly2tri/poly2tri.pro | 26 + .../poly2tri/sweep/advancing_front.cpp | 109 + src/3rdparty/poly2tri/sweep/advancing_front.h | 118 + src/3rdparty/poly2tri/sweep/cdt.cpp | 72 + src/3rdparty/poly2tri/sweep/cdt.h | 105 + src/3rdparty/poly2tri/sweep/sweep.cpp | 814 +++ src/3rdparty/poly2tri/sweep/sweep.h | 285 + src/3rdparty/poly2tri/sweep/sweep_context.cpp | 216 + src/3rdparty/poly2tri/sweep/sweep_context.h | 186 + src/3rdparty/poly2tri_legal.qdoc | 37 + src/imports/imports.pro | 5 + .../declarativeplaces/declarativeplaces.pri | 50 + .../qdeclarativecategory.cpp | 456 ++ .../qdeclarativecategory_p.h | 152 + .../qdeclarativecontactdetail.cpp | 219 + .../qdeclarativecontactdetail_p.h | 102 + .../declarativeplaces/qdeclarativeperiod_p.h | 98 + .../declarativeplaces/qdeclarativeplace.cpp | 1227 +++++ .../declarativeplaces/qdeclarativeplace_p.h | 261 + .../qdeclarativeplaceattribute.cpp | 217 + .../qdeclarativeplaceattribute_p.h | 94 + .../qdeclarativeplacecontentmodel.cpp | 397 ++ .../qdeclarativeplacecontentmodel.h | 125 + .../qdeclarativeplaceeditorialmodel.cpp | 169 + .../qdeclarativeplaceeditorialmodel.h | 64 + .../qdeclarativeplaceicon.cpp | 248 + .../qdeclarativeplaceicon_p.h | 103 + .../qdeclarativeplaceimagemodel.cpp | 170 + .../qdeclarativeplaceimagemodel_p.h | 77 + .../qdeclarativeplaceuser.cpp | 138 + .../qdeclarativeplaceuser_p.h | 91 + .../declarativeplaces/qdeclarativeratings.cpp | 152 + .../declarativeplaces/qdeclarativeratings_p.h | 97 + .../qdeclarativereviewmodel.cpp | 183 + .../qdeclarativereviewmodel_p.h | 77 + .../qdeclarativesearchmodelbase.cpp | 358 ++ .../qdeclarativesearchmodelbase.h | 148 + .../qdeclarativesearchresultmodel.cpp | 916 ++++ .../qdeclarativesearchresultmodel_p.h | 178 + .../qdeclarativesearchsuggestionmodel.cpp | 351 ++ .../qdeclarativesearchsuggestionmodel_p.h | 103 + .../qdeclarativesupplier.cpp | 219 + .../qdeclarativesupplier_p.h | 110 + .../qdeclarativesupportedcategoriesmodel.cpp | 687 +++ .../qdeclarativesupportedcategoriesmodel_p.h | 165 + src/imports/location/error_messages.cpp | 52 + src/imports/location/error_messages.h | 55 + src/imports/location/location.cpp | 186 + src/imports/location/location.pro | 69 + .../location/locationvaluetypehelper.cpp | 117 + .../location/locationvaluetypehelper_p.h | 60 + .../location/mapitemviewdelegateincubator.cpp | 54 + .../location/mapitemviewdelegateincubator.h | 65 + src/imports/location/plugin.json | 2 + src/imports/location/plugins.qmltypes | 1205 +++++ .../location/qdeclarativecirclemapitem.cpp | 657 +++ .../location/qdeclarativecirclemapitem_p.h | 133 + .../location/qdeclarativegeocodemodel.cpp | 727 +++ .../location/qdeclarativegeocodemodel_p.h | 205 + .../location/qdeclarativegeomaneuver.cpp | 201 + .../location/qdeclarativegeomaneuver_p.h | 109 + src/imports/location/qdeclarativegeomap.cpp | 1551 ++++++ src/imports/location/qdeclarativegeomap_p.h | 220 + .../qdeclarativegeomapcopyrightsnotice.cpp | 168 + .../qdeclarativegeomapcopyrightsnotice_p.h | 92 + .../location/qdeclarativegeomapitembase.cpp | 267 + .../location/qdeclarativegeomapitembase_p.h | 116 + .../location/qdeclarativegeomapitemview.cpp | 540 ++ .../location/qdeclarativegeomapitemview_p.h | 150 + .../location/qdeclarativegeomapitemview_p_p.h | 85 + .../location/qdeclarativegeomapquickitem.cpp | 354 ++ .../location/qdeclarativegeomapquickitem_p.h | 114 + .../location/qdeclarativegeomaptype.cpp | 136 + .../location/qdeclarativegeomaptype_p.h | 103 + src/imports/location/qdeclarativegeoroute.cpp | 276 + src/imports/location/qdeclarativegeoroute_p.h | 105 + .../location/qdeclarativegeoroutemodel.cpp | 1322 +++++ .../location/qdeclarativegeoroutemodel_p.h | 344 ++ .../location/qdeclarativegeoroutesegment.cpp | 164 + .../location/qdeclarativegeoroutesegment_p.h | 85 + .../qdeclarativegeoserviceprovider.cpp | 822 +++ .../qdeclarativegeoserviceprovider_p.h | 282 + .../location/qdeclarativepolygonmapitem.cpp | 724 +++ .../location/qdeclarativepolygonmapitem_p.h | 162 + .../location/qdeclarativepolylinemapitem.cpp | 1013 ++++ .../location/qdeclarativepolylinemapitem_p.h | 191 + .../location/qdeclarativerectanglemapitem.cpp | 452 ++ .../location/qdeclarativerectanglemapitem_p.h | 131 + .../location/qdeclarativeroutemapitem.cpp | 145 + .../location/qdeclarativeroutemapitem_p.h | 91 + src/imports/location/qgeomapitemgeometry.cpp | 140 + src/imports/location/qgeomapitemgeometry_p.h | 148 + src/imports/location/qmldir | 4 + .../location/qquickgeomapgesturearea.cpp | 1286 +++++ .../location/qquickgeomapgesturearea_p.h | 317 ++ src/imports/positioning/locationsingleton.cpp | 206 + src/imports/positioning/locationsingleton.h | 78 + src/imports/positioning/plugin.json | 2 + src/imports/positioning/plugins.qmltypes | 224 + src/imports/positioning/positioning.cpp | 566 ++ src/imports/positioning/positioning.pro | 21 + .../positioning/qdeclarativeposition.cpp | 471 ++ .../positioning/qdeclarativeposition_p.h | 147 + .../qdeclarativepositionsource.cpp | 765 +++ .../qdeclarativepositionsource_p.h | 172 + src/imports/positioning/qmldir | 4 + .../qquickgeocoordinateanimation.cpp | 297 ++ .../qquickgeocoordinateanimation_p.h | 99 + .../qquickgeocoordinateanimation_p_p.h | 69 + src/location/doc/images/api-mapcircle.png | Bin 0 -> 11336 bytes src/location/doc/images/api-mappolygon.png | Bin 0 -> 42391 bytes src/location/doc/images/api-mappolyline.png | Bin 0 -> 101623 bytes .../doc/images/api-mapquickitem-anchor.png | Bin 0 -> 61695 bytes src/location/doc/images/api-mapquickitem.png | Bin 0 -> 23147 bytes src/location/doc/images/api-maprectangle.png | Bin 0 -> 38249 bytes src/location/doc/images/mapsdemo-finished.png | Bin 0 -> 65434 bytes src/location/doc/images/mapsdemo-routing.png | Bin 0 -> 55176 bytes .../doc/images/mapsdemo-searchgui.png | Bin 0 -> 80027 bytes .../doc/images/mapsdemo-verybasic.png | Bin 0 -> 63947 bytes src/location/doc/qtlocation.qdocconf | 54 + src/location/doc/snippets/cpp/cpp.pro | 8 + src/location/doc/snippets/cpp/cppqml.cpp | 118 + src/location/doc/snippets/cpp/main.cpp | 45 + .../doc/snippets/declarative/content/Cell.qml | 68 + .../declarative/declarative-location.qml | 85 + .../doc/snippets/declarative/declarative.pro | 18 + .../doc/snippets/declarative/maps.qml | 100 + .../doc/snippets/declarative/marker.png | Bin 0 -> 1855 bytes .../doc/snippets/declarative/nmealog.txt | 1403 +++++ .../doc/snippets/declarative/places.qml | 453 ++ .../snippets/declarative/places_loader.qml | 108 + .../doc/snippets/declarative/plugin.qml | 58 + .../doc/snippets/declarative/routing.qml | 111 + src/location/doc/snippets/places/main.cpp | 47 + src/location/doc/snippets/places/places.pro | 5 + .../doc/snippets/places/requesthandler.h | 580 +++ src/location/doc/snippets/snippets.pro | 2 + src/location/doc/src/cpp-qml.qdoc | 115 + .../doc/src/example-parameters.qdocinc | 12 + src/location/doc/src/maps.qdoc | 250 + src/location/doc/src/place-caveats.qdocinc | 21 + src/location/doc/src/place-crossref.qdocinc | 7 + src/location/doc/src/place-definition.qdocinc | 27 + src/location/doc/src/places.qdoc | 433 ++ src/location/doc/src/plugins/mapbox.qdoc | 81 + src/location/doc/src/plugins/nokia.qdoc | 287 + src/location/doc/src/plugins/osm.qdoc | 145 + .../doc/src/plugins/places-backend.qdoc | 151 + src/location/doc/src/qml-maps.qdoc | 229 + src/location/doc/src/qtlocation-changes.qdoc | 77 + src/location/doc/src/qtlocation-cpp.qdoc | 89 + src/location/doc/src/qtlocation-examples.qdoc | 45 + .../doc/src/qtlocation-geoservices.qdoc | 92 + src/location/doc/src/qtlocation-qml.qdoc | 108 + src/location/doc/src/qtlocation.qdoc | 187 + src/location/doc/src/src.pro | 9 + src/location/location.pro | 22 + src/location/maps/maps.pri | 99 + src/location/maps/qabstractgeotilecache.cpp | 147 + src/location/maps/qabstractgeotilecache_p.h | 125 + src/location/maps/qcache3q_p.h | 471 ++ src/location/maps/qgeocameracapabilities.cpp | 312 ++ src/location/maps/qgeocameracapabilities_p.h | 97 + src/location/maps/qgeocameradata.cpp | 213 + src/location/maps/qgeocameradata_p.h | 98 + src/location/maps/qgeocameratiles.cpp | 946 ++++ src/location/maps/qgeocameratiles_p.h | 84 + src/location/maps/qgeocodereply.cpp | 329 ++ src/location/maps/qgeocodereply.h | 105 + src/location/maps/qgeocodereply_p.h | 83 + src/location/maps/qgeocodingmanager.cpp | 327 ++ src/location/maps/qgeocodingmanager.h | 92 + src/location/maps/qgeocodingmanager_p.h | 76 + src/location/maps/qgeocodingmanagerengine.cpp | 336 ++ src/location/maps/qgeocodingmanagerengine.h | 89 + src/location/maps/qgeocodingmanagerengine_p.h | 75 + src/location/maps/qgeofiletilecache.cpp | 463 ++ src/location/maps/qgeofiletilecache_p.h | 156 + src/location/maps/qgeomaneuver.cpp | 318 ++ src/location/maps/qgeomaneuver.h | 104 + src/location/maps/qgeomaneuver_p.h | 80 + src/location/maps/qgeomap.cpp | 144 + src/location/maps/qgeomap_p.h | 117 + src/location/maps/qgeomap_p_p.h | 84 + src/location/maps/qgeomappingmanager.cpp | 198 + src/location/maps/qgeomappingmanager_p.h | 107 + src/location/maps/qgeomappingmanager_p_p.h | 73 + .../maps/qgeomappingmanagerengine.cpp | 208 + .../maps/qgeomappingmanagerengine_p.h | 124 + .../maps/qgeomappingmanagerengine_p_p.h | 87 + src/location/maps/qgeomaptype.cpp | 134 + src/location/maps/qgeomaptype_p.h | 101 + src/location/maps/qgeomaptype_p_p.h | 83 + src/location/maps/qgeoroute.cpp | 320 ++ src/location/maps/qgeoroute.h | 96 + src/location/maps/qgeoroute_p.h | 89 + src/location/maps/qgeorouteparser.cpp | 90 + src/location/maps/qgeorouteparser_p.h | 79 + src/location/maps/qgeorouteparser_p_p.h | 60 + src/location/maps/qgeorouteparserosrmv4.cpp | 404 ++ src/location/maps/qgeorouteparserosrmv4_p.h | 72 + src/location/maps/qgeorouteparserosrmv5.cpp | 979 ++++ src/location/maps/qgeorouteparserosrmv5_p.h | 72 + src/location/maps/qgeoroutereply.cpp | 271 + src/location/maps/qgeoroutereply.h | 95 + src/location/maps/qgeoroutereply_p.h | 80 + src/location/maps/qgeorouterequest.cpp | 486 ++ src/location/maps/qgeorouterequest.h | 161 + src/location/maps/qgeorouterequest_p.h | 82 + src/location/maps/qgeoroutesegment.cpp | 275 + src/location/maps/qgeoroutesegment.h | 89 + src/location/maps/qgeoroutesegment_p.h | 82 + src/location/maps/qgeoroutingmanager.cpp | 413 ++ src/location/maps/qgeoroutingmanager.h | 91 + src/location/maps/qgeoroutingmanager_p.h | 69 + .../maps/qgeoroutingmanagerengine.cpp | 425 ++ src/location/maps/qgeoroutingmanagerengine.h | 100 + .../maps/qgeoroutingmanagerengine_p.h | 83 + src/location/maps/qgeoserviceprovider.cpp | 730 +++ src/location/maps/qgeoserviceprovider.h | 163 + src/location/maps/qgeoserviceprovider_p.h | 121 + .../maps/qgeoserviceproviderfactory.cpp | 163 + .../maps/qgeoserviceproviderfactory.h | 72 + src/location/maps/qgeotiledmap.cpp | 414 ++ src/location/maps/qgeotiledmap_p.h | 113 + src/location/maps/qgeotiledmap_p_p.h | 108 + .../maps/qgeotiledmappingmanagerengine.cpp | 328 ++ .../maps/qgeotiledmappingmanagerengine_p.h | 130 + .../maps/qgeotiledmappingmanagerengine_p_p.h | 84 + src/location/maps/qgeotiledmapreply.cpp | 318 ++ src/location/maps/qgeotiledmapreply_p.h | 109 + src/location/maps/qgeotiledmapreply_p_p.h | 75 + src/location/maps/qgeotiledmapscene.cpp | 749 +++ src/location/maps/qgeotiledmapscene_p.h | 101 + src/location/maps/qgeotilefetcher.cpp | 204 + src/location/maps/qgeotilefetcher_p.h | 104 + src/location/maps/qgeotilefetcher_p_p.h | 85 + src/location/maps/qgeotilerequestmanager.cpp | 229 + src/location/maps/qgeotilerequestmanager_p.h | 80 + src/location/maps/qgeotilespec.cpp | 241 + src/location/maps/qgeotilespec_p.h | 103 + src/location/maps/qgeotilespec_p_p.h | 78 + src/location/places/placemacro.h | 71 + src/location/places/places.pri | 93 + src/location/places/qplace.cpp | 721 +++ src/location/places/qplace.h | 135 + src/location/places/qplace_p.h | 98 + src/location/places/qplaceattribute.cpp | 231 + src/location/places/qplaceattribute.h | 81 + src/location/places/qplaceattribute_p.h | 79 + src/location/places/qplacecategory.cpp | 220 + src/location/places/qplacecategory.h | 91 + src/location/places/qplacecategory_p.h | 78 + src/location/places/qplacecontactdetail.cpp | 211 + src/location/places/qplacecontactdetail.h | 83 + src/location/places/qplacecontactdetail_p.h | 72 + src/location/places/qplacecontent.cpp | 265 + src/location/places/qplacecontent.h | 107 + src/location/places/qplacecontent_p.h | 103 + src/location/places/qplacecontentreply.cpp | 191 + src/location/places/qplacecontentreply.h | 80 + src/location/places/qplacecontentrequest.cpp | 256 + src/location/places/qplacecontentrequest.h | 82 + src/location/places/qplacecontentrequest_p.h | 77 + src/location/places/qplacedetailsreply.cpp | 106 + src/location/places/qplacedetailsreply.h | 67 + src/location/places/qplaceeditorial.cpp | 164 + src/location/places/qplaceeditorial.h | 71 + src/location/places/qplaceeditorial_p.h | 77 + src/location/places/qplaceicon.cpp | 233 + src/location/places/qplaceicon.h | 87 + src/location/places/qplaceicon_p.h | 74 + src/location/places/qplaceidreply.cpp | 132 + src/location/places/qplaceidreply.h | 75 + src/location/places/qplaceimage.cpp | 159 + src/location/places/qplaceimage.h | 77 + src/location/places/qplaceimage_p.h | 78 + src/location/places/qplacemanager.cpp | 489 ++ src/location/places/qplacemanager.h | 127 + src/location/places/qplacemanagerengine.cpp | 462 ++ src/location/places/qplacemanagerengine.h | 121 + src/location/places/qplacemanagerengine_p.h | 72 + src/location/places/qplacematchreply.cpp | 135 + src/location/places/qplacematchreply.h | 68 + src/location/places/qplacematchrequest.cpp | 259 + src/location/places/qplacematchrequest.h | 81 + .../places/qplaceproposedsearchresult.cpp | 116 + .../places/qplaceproposedsearchresult.h | 70 + .../places/qplaceproposedsearchresult_p.h | 72 + src/location/places/qplaceratings.cpp | 189 + src/location/places/qplaceratings.h | 82 + src/location/places/qplaceratings_p.h | 74 + src/location/places/qplacereply.cpp | 233 + src/location/places/qplacereply.h | 106 + src/location/places/qplacereply_p.h | 70 + src/location/places/qplaceresult.cpp | 174 + src/location/places/qplaceresult.h | 77 + src/location/places/qplaceresult_p.h | 74 + src/location/places/qplacereview.cpp | 229 + src/location/places/qplacereview.h | 78 + src/location/places/qplacereview_p.h | 80 + src/location/places/qplacesearchreply.cpp | 173 + src/location/places/qplacesearchreply.h | 76 + src/location/places/qplacesearchrequest.cpp | 440 ++ src/location/places/qplacesearchrequest.h | 107 + src/location/places/qplacesearchresult.cpp | 214 + src/location/places/qplacesearchresult.h | 106 + src/location/places/qplacesearchresult_p.h | 107 + .../places/qplacesearchsuggestionreply.cpp | 109 + .../places/qplacesearchsuggestionreply.h | 68 + src/location/places/qplacesupplier.cpp | 223 + src/location/places/qplacesupplier.h | 85 + src/location/places/qplacesupplier_p.h | 79 + src/location/places/qplaceuser.cpp | 153 + src/location/places/qplaceuser.h | 76 + src/location/places/qplaceuser_p.h | 72 + src/location/places/unsupportedreplies_p.h | 227 + src/location/qlocation.cpp | 78 + src/location/qlocation.h | 65 + src/location/qlocationglobal.h | 56 + src/plugins/geoservices/geoservices.pro | 3 + src/plugins/geoservices/mapbox/mapbox.pro | 22 + .../geoservices/mapbox/mapbox_plugin.json | 9 + .../geoservices/mapbox/qgeomapreplymapbox.cpp | 97 + .../geoservices/mapbox/qgeomapreplymapbox.h | 69 + .../qgeoserviceproviderpluginmapbox.cpp | 90 + .../mapbox/qgeoserviceproviderpluginmapbox.h | 69 + .../qgeotiledmappingmanagerenginemapbox.cpp | 93 + .../qgeotiledmappingmanagerenginemapbox.h | 60 + .../mapbox/qgeotilefetchermapbox.cpp | 102 + .../mapbox/qgeotilefetchermapbox.h | 72 + src/plugins/geoservices/nokia/logo.png | Bin 0 -> 1217 bytes .../geoservices/nokia/marclanguagecodes.h | 312 ++ src/plugins/geoservices/nokia/nokia.pro | 61 + .../geoservices/nokia/nokia_plugin.json | 19 + .../nokia/placesv2/jsonparserhelpers.cpp | 242 + .../nokia/placesv2/jsonparserhelpers.h | 78 + .../geoservices/nokia/placesv2/placesv2.pri | 21 + .../placesv2/qplacecategoriesreplyhere.cpp | 62 + .../placesv2/qplacecategoriesreplyhere.h | 60 + .../nokia/placesv2/qplacecontentreplyimpl.cpp | 126 + .../nokia/placesv2/qplacecontentreplyimpl.h | 71 + .../nokia/placesv2/qplacedetailsreplyimpl.cpp | 347 ++ .../nokia/placesv2/qplacedetailsreplyimpl.h | 71 + .../nokia/placesv2/qplaceidreplyimpl.cpp | 66 + .../nokia/placesv2/qplaceidreplyimpl.h | 60 + .../nokia/placesv2/qplacesearchreplyhere.cpp | 230 + .../nokia/placesv2/qplacesearchreplyhere.h | 75 + .../qplacesearchsuggestionreplyimpl.cpp | 117 + .../qplacesearchsuggestionreplyimpl.h | 65 + .../geoservices/nokia/qgeocodereply_nokia.cpp | 135 + .../geoservices/nokia/qgeocodereply_nokia.h | 67 + .../geoservices/nokia/qgeocodexmlparser.cpp | 573 ++ .../geoservices/nokia/qgeocodexmlparser.h | 88 + .../nokia/qgeocodingmanagerengine_nokia.cpp | 295 ++ .../nokia/qgeocodingmanagerengine_nokia.h | 90 + .../geoservices/nokia/qgeoerror_messages.cpp | 52 + .../geoservices/nokia/qgeoerror_messages.h | 58 + .../qgeointrinsicnetworkaccessmanager.cpp | 100 + .../nokia/qgeointrinsicnetworkaccessmanager.h | 66 + .../geoservices/nokia/qgeomapreply_nokia.cpp | 104 + .../geoservices/nokia/qgeomapreply_nokia.h | 69 + .../geoservices/nokia/qgeomapversion.cpp | 79 + .../geoservices/nokia/qgeomapversion.h | 63 + .../nokia/qgeonetworkaccessmanager.h | 62 + .../nokia/qgeoroutereply_nokia.cpp | 148 + .../geoservices/nokia/qgeoroutereply_nokia.h | 69 + .../geoservices/nokia/qgeoroutexmlparser.cpp | 605 +++ .../geoservices/nokia/qgeoroutexmlparser.h | 126 + .../nokia/qgeoroutingmanagerengine_nokia.cpp | 491 ++ .../nokia/qgeoroutingmanagerengine_nokia.h | 86 + .../nokia/qgeoserviceproviderplugin_nokia.cpp | 141 + .../nokia/qgeoserviceproviderplugin_nokia.h | 71 + .../geoservices/nokia/qgeotiledmap_nokia.cpp | 112 + .../geoservices/nokia/qgeotiledmap_nokia.h | 69 + .../qgeotiledmappingmanagerengine_nokia.cpp | 384 ++ .../qgeotiledmappingmanagerengine_nokia.h | 110 + .../nokia/qgeotilefetcher_nokia.cpp | 323 ++ .../geoservices/nokia/qgeotilefetcher_nokia.h | 95 + .../geoservices/nokia/qgeouriprovider.cpp | 134 + .../geoservices/nokia/qgeouriprovider.h | 79 + .../nokia/qplacemanagerengine_nokiav2.cpp | 863 +++ .../nokia/qplacemanagerengine_nokiav2.h | 134 + src/plugins/geoservices/nokia/resource.qrc | 5 + .../geoservices/nokia/uri_constants.cpp | 48 + src/plugins/geoservices/nokia/uri_constants.h | 54 + src/plugins/geoservices/osm/osm.pro | 41 + src/plugins/geoservices/osm/osm_plugin.json | 13 + .../geoservices/osm/qgeocodereplyosm.cpp | 190 + .../geoservices/osm/qgeocodereplyosm.h | 68 + .../osm/qgeocodingmanagerengineosm.cpp | 182 + .../osm/qgeocodingmanagerengineosm.h | 78 + .../geoservices/osm/qgeomapreplyosm.cpp | 108 + src/plugins/geoservices/osm/qgeomapreplyosm.h | 74 + .../geoservices/osm/qgeoroutereplyosm.cpp | 122 + .../geoservices/osm/qgeoroutereplyosm.h | 70 + .../osm/qgeoroutingmanagerengineosm.cpp | 115 + .../osm/qgeoroutingmanagerengineosm.h | 78 + .../osm/qgeoserviceproviderpluginosm.cpp | 72 + .../osm/qgeoserviceproviderpluginosm.h | 72 + .../geoservices/osm/qgeotiledmaposm.cpp | 112 + src/plugins/geoservices/osm/qgeotiledmaposm.h | 71 + .../osm/qgeotiledmappingmanagerengineosm.cpp | 251 + .../osm/qgeotiledmappingmanagerengineosm.h | 80 + .../geoservices/osm/qgeotilefetcherosm.cpp | 138 + .../geoservices/osm/qgeotilefetcherosm.h | 87 + .../geoservices/osm/qgeotileproviderosm.cpp | 318 ++ .../geoservices/osm/qgeotileproviderosm.h | 256 + .../osm/qplacecategoriesreplyosm.cpp | 65 + .../osm/qplacecategoriesreplyosm.h | 61 + .../osm/qplacemanagerengineosm.cpp | 365 ++ .../geoservices/osm/qplacemanagerengineosm.h | 97 + .../geoservices/osm/qplacesearchreplyosm.cpp | 219 + .../geoservices/osm/qplacesearchreplyosm.h | 74 + src/plugins/plugins.pro | 3 + src/plugins/position/android/android.pro | 2 + .../position/android/jar/AndroidManifest.xml | 6 + .../position/android/jar/bundledjar.pro | 3 + .../position/android/jar/distributedjar.pro | 3 + src/plugins/position/android/jar/jar.pri | 14 + src/plugins/position/android/jar/jar.pro | 2 + .../android/positioning/QtPositioning.java | 582 +++ .../position/android/src/jnipositioning.cpp | 577 +++ .../position/android/src/jnipositioning.h | 63 + src/plugins/position/android/src/plugin.json | 9 + .../android/src/positionfactory_android.cpp | 60 + .../android/src/positionfactory_android.h | 58 + .../src/qgeopositioninfosource_android.cpp | 264 + .../src/qgeopositioninfosource_android_p.h | 97 + .../src/qgeosatelliteinfosource_android.cpp | 216 + .../src/qgeosatelliteinfosource_android_p.h | 97 + src/plugins/position/android/src/src.pro | 21 + .../position/corelocation/corelocation.pro | 22 + src/plugins/position/corelocation/plugin.json | 9 + .../corelocation/qgeopositioninfosource_cl.mm | 266 + .../qgeopositioninfosource_cl_p.h | 108 + .../qgeopositioninfosourcefactory_cl.h | 58 + .../qgeopositioninfosourcefactory_cl.mm | 58 + src/plugins/position/geoclue/geoclue.pro | 38 + src/plugins/position/geoclue/geocluetypes.cpp | 105 + src/plugins/position/geoclue/geocluetypes.h | 93 + .../org.freedesktop.Geoclue.Master.xml | 10 + .../org.freedesktop.Geoclue.MasterClient.xml | 41 + .../org.freedesktop.Geoclue.Position.xml | 25 + .../org.freedesktop.Geoclue.Satellite.xml | 33 + .../org.freedesktop.Geoclue.Velocity.xml | 20 + .../geoclue/org.freedesktop.Geoclue.xml | 24 + src/plugins/position/geoclue/plugin.json | 9 + .../position/geoclue/qgeocluemaster.cpp | 137 + src/plugins/position/geoclue/qgeocluemaster.h | 90 + .../qgeopositioninfosource_geocluemaster.cpp | 495 ++ .../qgeopositioninfosource_geocluemaster.h | 143 + .../qgeopositioninfosourcefactory_geoclue.cpp | 71 + .../qgeopositioninfosourcefactory_geoclue.h | 70 + .../qgeosatelliteinfosource_geocluemaster.cpp | 310 ++ .../qgeosatelliteinfosource_geocluemaster.h | 105 + src/plugins/position/gypsy/gypsy.pro | 21 + src/plugins/position/gypsy/plugin.json | 9 + .../qgeopositioninfosourcefactory_gypsy.cpp | 63 + .../qgeopositioninfosourcefactory_gypsy.h | 59 + .../gypsy/qgeosatelliteinfosource_gypsy.cpp | 374 ++ .../gypsy/qgeosatelliteinfosource_gypsy_p.h | 141 + src/plugins/position/position.pro | 12 + src/plugins/position/positionpoll/plugin.json | 9 + .../position/positionpoll/positionpoll.pro | 18 + .../positionpoll/positionpollfactory.cpp | 62 + .../positionpoll/positionpollfactory.h | 58 + .../positionpoll/qgeoareamonitor_polling.cpp | 497 ++ .../positionpoll/qgeoareamonitor_polling.h | 94 + src/plugins/position/serialnmea/plugin.json | 9 + ...eopositioninfosourcefactory_serialnmea.cpp | 127 + ...qgeopositioninfosourcefactory_serialnmea.h | 59 + .../position/serialnmea/serialnmea.pro | 16 + src/plugins/position/simulator/plugin.json | 9 + .../qgeopositioninfosource_simulator.cpp | 172 + .../qgeopositioninfosource_simulator_p.h | 95 + ...geopositioninfosourcefactory_simulator.cpp | 61 + .../qgeopositioninfosourcefactory_simulator.h | 61 + .../qgeosatelliteinfosource_simulator.cpp | 133 + .../qgeosatelliteinfosource_simulator_p.h | 94 + .../qlocationconnection_simulator.cpp | 128 + .../qlocationconnection_simulator_p.h | 97 + src/plugins/position/simulator/simulator.pro | 22 + src/plugins/position/winrt/plugin.json | 9 + .../winrt/qgeopositioninfosource_winrt.cpp | 535 ++ .../winrt/qgeopositioninfosource_winrt_p.h | 124 + .../qgeopositioninfosourcefactory_winrt.cpp | 55 + .../qgeopositioninfosourcefactory_winrt.h | 59 + src/plugins/position/winrt/winrt.pro | 16 + src/positioning/doc/qtpositioning.qdocconf | 55 + src/positioning/doc/snippets/cpp/cpp.pro | 8 + src/positioning/doc/snippets/cpp/cppqml.cpp | 108 + src/positioning/doc/snippets/cpp/main.cpp | 45 + .../doc/snippets/doc_src_qtpositioning.qml | 47 + src/positioning/doc/snippets/snippets.pro | 2 + src/positioning/doc/src/cpp-position.qdoc | 191 + .../doc/src/cpp-qml-positioning.qdoc | 96 + src/positioning/doc/src/qml-position.qdoc | 119 + .../doc/src/qtpositioning-examples.qdoc | 37 + .../doc/src/qtpositioning-plugins.qdoc | 82 + .../doc/src/qtpositioning-qml.qdoc | 65 + src/positioning/doc/src/qtpositioning.qdoc | 125 + src/positioning/positioning.pro | 81 + src/positioning/qdeclarativegeoaddress.cpp | 355 ++ src/positioning/qdeclarativegeoaddress_p.h | 120 + src/positioning/qdeclarativegeolocation.cpp | 190 + src/positioning/qdeclarativegeolocation_p.h | 99 + src/positioning/qdoublevector2d.cpp | 121 + src/positioning/qdoublevector2d_p.h | 248 + src/positioning/qdoublevector3d.cpp | 144 + src/positioning/qdoublevector3d_p.h | 302 ++ src/positioning/qgeoaddress.cpp | 635 +++ src/positioning/qgeoaddress.h | 106 + src/positioning/qgeoaddress_p.h | 80 + src/positioning/qgeoareamonitorinfo.cpp | 379 ++ src/positioning/qgeoareamonitorinfo.h | 103 + src/positioning/qgeoareamonitorsource.cpp | 389 ++ src/positioning/qgeoareamonitorsource.h | 108 + src/positioning/qgeocircle.cpp | 385 ++ src/positioning/qgeocircle.h | 95 + src/positioning/qgeocircle_p.h | 85 + src/positioning/qgeocoordinate.cpp | 783 +++ src/positioning/qgeocoordinate.h | 136 + src/positioning/qgeocoordinate_p.h | 92 + src/positioning/qgeolocation.cpp | 201 + src/positioning/qgeolocation.h | 88 + src/positioning/qgeolocation_p.h | 80 + src/positioning/qgeopositioninfo.cpp | 359 ++ src/positioning/qgeopositioninfo.h | 113 + src/positioning/qgeopositioninfosource.cpp | 483 ++ src/positioning/qgeopositioninfosource.h | 114 + src/positioning/qgeopositioninfosource_p.h | 82 + .../qgeopositioninfosourcefactory.cpp | 87 + .../qgeopositioninfosourcefactory.h | 66 + src/positioning/qgeoprojection.cpp | 137 + src/positioning/qgeoprojection_p.h | 79 + src/positioning/qgeorectangle.cpp | 1063 ++++ src/positioning/qgeorectangle.h | 130 + src/positioning/qgeorectangle_p.h | 85 + src/positioning/qgeosatelliteinfo.cpp | 314 ++ src/positioning/qgeosatelliteinfo.h | 112 + src/positioning/qgeosatelliteinfosource.cpp | 350 ++ src/positioning/qgeosatelliteinfosource.h | 98 + src/positioning/qgeoshape.cpp | 377 ++ src/positioning/qgeoshape.h | 112 + src/positioning/qgeoshape_p.h | 92 + src/positioning/qlocationdata_simulator.cpp | 119 + src/positioning/qlocationdata_simulator_p.h | 134 + src/positioning/qlocationutils.cpp | 403 ++ src/positioning/qlocationutils_p.h | 211 + src/positioning/qnmeapositioninfosource.cpp | 680 +++ src/positioning/qnmeapositioninfosource.h | 97 + src/positioning/qnmeapositioninfosource_p.h | 177 + src/positioning/qpositioningglobal.h | 60 + src/positioning/qpositioningglobal_p.h | 62 + src/src.pro | 33 + sync.profile | 20 + .../applications/positioning_backend/main.cpp | 48 + .../positioning_backend.pro | 14 + .../positioning_backend/widget.cpp | 155 + .../applications/positioning_backend/widget.h | 66 + .../positioning_backend/widget.ui | 295 ++ tests/auto/auto.pro | 83 + .../QtPositioning.5.3.0.linux-gcc-amd64.txt | 3822 ++++++++++++++ .../QtPositioning.5.4.0.linux-gcc-amd64.txt | 3854 ++++++++++++++ .../QtPositioning.5.6.0.linux-gcc-amd64.txt | 4118 +++++++++++++++ .../QtPositioning.5.7.0.linux-gcc-amd64.txt | 4400 ++++++++++++++++ tests/auto/cmake/CMakeLists.txt | 19 + tests/auto/cmake/cmake.pro | 7 + .../declarative_core/declarative_core.pro | 14 + tests/auto/declarative_core/main.cpp | 46 + tests/auto/declarative_core/tst_address.qml | 88 + tests/auto/declarative_core/tst_category.qml | 236 + .../declarative_core/tst_categorymodel.qml | 237 + .../declarative_core/tst_contactdetail.qml | 71 + .../auto/declarative_core/tst_coordinate.qml | 359 ++ .../declarative_core/tst_editorialmodel.qml | 186 + tests/auto/declarative_core/tst_geocoding.qml | 641 +++ .../auto/declarative_core/tst_imagemodel.qml | 186 + tests/auto/declarative_core/tst_place.qml | 627 +++ .../declarative_core/tst_placeattribute.qml | 53 + tests/auto/declarative_core/tst_placeicon.qml | 106 + .../declarative_core/tst_placesearchmodel.qml | 293 ++ .../tst_placesearchsuggestionmodel.qml | 153 + tests/auto/declarative_core/tst_plugin.qml | 143 + .../declarative_core/tst_plugin_error.qml | 61 + tests/auto/declarative_core/tst_position.qml | 99 + .../declarative_core/tst_positionsource.qml | 164 + tests/auto/declarative_core/tst_ratings.qml | 75 + .../auto/declarative_core/tst_reviewmodel.qml | 201 + tests/auto/declarative_core/tst_routing.qml | 805 +++ tests/auto/declarative_core/tst_supplier.qml | 100 + tests/auto/declarative_core/tst_user.qml | 71 + tests/auto/declarative_core/utils.js | 182 + .../declarative_geoshape.pro | 11 + tests/auto/declarative_geoshape/main.cpp | 30 + .../tst_locationsingleton.qml | 248 + tests/auto/declarative_ui/declarative_ui.pro | 18 + tests/auto/declarative_ui/main.cpp | 46 + tests/auto/declarative_ui/tst_map.qml | 343 ++ .../tst_map_coordinateanimation.qml | 137 + tests/auto/declarative_ui/tst_map_error.qml | 209 + tests/auto/declarative_ui/tst_map_flick.qml | 347 ++ tests/auto/declarative_ui/tst_map_item.qml | 615 +++ .../declarative_ui/tst_map_item_details.qml | 613 +++ .../tst_map_item_fit_viewport.qml | 690 +++ .../auto/declarative_ui/tst_map_itemview.qml | 490 ++ .../auto/declarative_ui/tst_map_keepgrab.qml | 156 + tests/auto/declarative_ui/tst_map_maptype.qml | 92 + tests/auto/declarative_ui/tst_map_mouse.qml | 723 +++ .../tst_map_pinch.qml.QTBUG-47970 | 576 +++ tests/auto/doublevectors/doublevectors.pro | 7 + .../auto/doublevectors/tst_doublevectors.cpp | 293 ++ tests/auto/geotestplugin/geotestplugin.json | 19 + tests/auto/geotestplugin/geotestplugin.pro | 22 + tests/auto/geotestplugin/place_data.json | 145 + .../qgeocodingmanagerengine_test.h | 274 + .../qgeomappingmanagerengine_test.h | 175 + .../qgeoroutingmanagerengine_test.h | 181 + .../qgeoserviceproviderplugin_test.cpp | 93 + .../qgeoserviceproviderplugin_test.h | 64 + tests/auto/geotestplugin/qgeotiledmap_test.h | 49 + .../qgeotiledmappingmanagerengine_test.h | 92 + .../auto/geotestplugin/qgeotilefetcher_test.h | 178 + .../geotestplugin/qplacemanagerengine_test.h | 693 +++ tests/auto/geotestplugin/testdata.qrc | 5 + tests/auto/maptype/maptype.pro | 7 + tests/auto/maptype/tst_maptype.cpp | 141 + tests/auto/nokia_services/nokia_services.pro | 2 + .../places_semiauto/places_semiauto.pro | 10 + .../places_semiauto/tst_places.cpp | 745 +++ .../nokia_services/routing/error-no-route.xml | 1 + .../invalid-response-half-way-through.xml | 150 + ...invalid-response-no-calculateroute-tag.xml | 1 + .../routing/invalid-response-no-route-tag.xml | 18 + .../routing/invalid-response-trash.xml | Bin 0 -> 785 bytes .../routing/littered-with-new-tags.xml | 648 +++ .../routing/multiple-routes-in-response.xml | 1 + .../nokia_services/routing/optim-fastest.xml | 628 +++ .../nokia_services/routing/optim-shortest.xml | 1 + tests/auto/nokia_services/routing/routing.pro | 13 + .../nokia_services/routing/travelmode-car.xml | 628 +++ .../routing/travelmode-pedestrian.xml | 798 +++ .../routing/travelmode-public-transport.xml | 343 ++ .../nokia_services/routing/tst_routing.cpp | 517 ++ .../placemanager_utils/placemanager_utils.cpp | 376 ++ .../placemanager_utils/placemanager_utils.h | 230 + .../placesplugin.json | 8 + .../placesplugin_unsupported.pro | 14 + .../qgeoserviceproviderplugin_test.cpp | 50 + .../qgeoserviceproviderplugin_test.h | 54 + tests/auto/positionplugin/plugin.cpp | 213 + tests/auto/positionplugin/plugin.json | 9 + tests/auto/positionplugin/positionplugin.pro | 12 + .../positionplugintest/positionplugintest.pro | 9 + .../positionplugintest/tst_positionplugin.cpp | 111 + tests/auto/qgeoaddress/qgeoaddress.pro | 7 + tests/auto/qgeoaddress/tst_qgeoaddress.cpp | 559 ++ .../qgeoareamonitor/logfilepositionsource.cpp | 112 + .../qgeoareamonitor/logfilepositionsource.h | 66 + .../auto/qgeoareamonitor/qgeoareamonitor.pro | 14 + tests/auto/qgeoareamonitor/simplelog.txt | 87 + .../qgeoareamonitor/tst_qgeoareamonitor.cpp | 760 +++ .../qgeocameracapabilities.pro | 9 + .../tst_qgeocameracapabilities.cpp | 297 ++ tests/auto/qgeocameradata/qgeocameradata.pro | 9 + .../qgeocameradata/tst_qgeocameradata.cpp | 221 + .../auto/qgeocameratiles/qgeocameratiles.pro | 8 + .../qgeocameratiles/tst_qgeocameratiles.cpp | 1806 +++++++ tests/auto/qgeocircle/qgeocircle.pro | 8 + tests/auto/qgeocircle/tst_qgeocircle.cpp | 417 ++ tests/auto/qgeocodereply/qgeocodereply.pro | 10 + .../auto/qgeocodereply/tst_qgeocodereply.cpp | 275 + tests/auto/qgeocodereply/tst_qgeocodereply.h | 105 + .../qgeocodingmanager/qgeocodingmanager.pro | 12 + .../tst_qgeocodingmanager.cpp | 181 + .../qgeocodingmanager/tst_qgeocodingmanager.h | 76 + .../geocoding_plugin.json | 10 + .../qgeocodingmanagerengine_test.h | 107 + .../qgeocodingmanagerplugins.pro | 15 + .../qgeoserviceproviderplugin_test.cpp | 47 + .../qgeoserviceproviderplugin_test.h | 55 + tests/auto/qgeocoordinate/qgeocoordinate.pro | 8 + .../qgeocoordinate/tst_qgeocoordinate.cpp | 951 ++++ tests/auto/qgeolocation/qgeolocation.pro | 9 + tests/auto/qgeolocation/tst_qgeolocation.cpp | 249 + tests/auto/qgeolocation/tst_qgeolocation.h | 84 + tests/auto/qgeomaneuver/qgeomaneuver.pro | 11 + tests/auto/qgeomaneuver/tst_qgeomaneuver.cpp | 257 + tests/auto/qgeomaneuver/tst_qgeomaneuver.h | 84 + .../qgeopositioninfo/qgeopositioninfo.pro | 7 + .../qgeopositioninfo/tst_qgeopositioninfo.cpp | 386 ++ .../qgeopositioninfosource.pro | 15 + .../testqgeopositioninfosource.cpp | 777 +++ .../testqgeopositioninfosource_p.h | 127 + .../tst_qgeopositioninfosource.cpp | 38 + tests/auto/qgeorectangle/qgeorectangle.pro | 8 + .../auto/qgeorectangle/tst_qgeorectangle.cpp | 2351 +++++++++ tests/auto/qgeoroute/qgeoroute.pro | 9 + tests/auto/qgeoroute/tst_qgeoroute.cpp | 290 ++ tests/auto/qgeoroute/tst_qgeoroute.h | 90 + tests/auto/qgeoroutereply/qgeoroutereply.pro | 9 + .../qgeoroutereply/tst_qgeoroutereply.cpp | 242 + .../auto/qgeoroutereply/tst_qgeoroutereply.h | 96 + .../qgeorouterequest/qgeorouterequest.pro | 9 + .../qgeorouterequest/tst_qgeorouterequest.cpp | 323 ++ .../qgeorouterequest/tst_qgeorouterequest.h | 93 + .../qgeoroutesegment/qgeoroutesegment.pro | 9 + .../qgeoroutesegment/tst_qgeoroutesegment.cpp | 302 ++ .../qgeoroutesegment/tst_qgeoroutesegment.h | 82 + tests/auto/qgeoroutexmlparser/fixtures.qrc | 6 + .../qgeoroutexmlparser/qgeoroutexmlparser.pro | 13 + tests/auto/qgeoroutexmlparser/route1.xml | 1 + tests/auto/qgeoroutexmlparser/route2.xml | 1 + .../tst_qgeoroutexmlparser.cpp | 172 + .../qgeoroutingmanager/qgeoroutingmanager.pro | 11 + .../tst_qgeoroutingmanager.cpp | 163 + .../tst_qgeoroutingmanager.h | 74 + .../qgeoroutingmanagerengine_test.h | 76 + .../qgeoroutingmanagerplugins.pro | 15 + .../qgeoserviceproviderplugin_test.cpp | 47 + .../qgeoserviceproviderplugin_test.h | 55 + .../routing_plugin.json | 12 + .../qgeosatelliteinfo/qgeosatelliteinfo.pro | 7 + .../tst_qgeosatelliteinfo.cpp | 405 ++ .../qgeosatelliteinfosource.pro | 12 + .../testqgeosatelliteinfosource.cpp | 767 +++ .../testqgeosatelliteinfosource_p.h | 114 + .../tst_qgeosatelliteinfosource.cpp | 37 + .../qgeoserviceprovider.pro | 9 + .../tst_qgeoserviceprovider.cpp | 217 + tests/auto/qgeoshape/qgeoshape.pro | 5 + tests/auto/qgeoshape/tst_qgeoshape.cpp | 130 + tests/auto/qgeotiledmap/qgeotiledmap.pro | 9 + tests/auto/qgeotiledmap/tst_qgeotiledmap.cpp | 207 + .../qgeotiledmapscene/qgeotiledmapscene.pro | 8 + .../tst_qgeotiledmapscene.cpp | 384 ++ tests/auto/qgeotilespec/qgeotilespec.pro | 9 + tests/auto/qgeotilespec/tst_qgeotilespec.cpp | 319 ++ tests/auto/qmlinterface/data/TestAddress.qml | 38 + tests/auto/qmlinterface/data/TestCategory.qml | 34 + .../qmlinterface/data/TestContactDetail.qml | 34 + tests/auto/qmlinterface/data/TestIcon.qml | 36 + tests/auto/qmlinterface/data/TestLocation.qml | 49 + tests/auto/qmlinterface/data/TestPlace.qml | 50 + .../qmlinterface/data/TestPlaceAttribute.qml | 34 + tests/auto/qmlinterface/data/TestRatings.qml | 35 + tests/auto/qmlinterface/data/TestSupplier.qml | 36 + tests/auto/qmlinterface/data/TestUser.qml | 34 + tests/auto/qmlinterface/qmlinterface.pro | 26 + tests/auto/qmlinterface/tst_qmlinterface.cpp | 353 ++ .../dummynmeapositioninfosource.pro | 23 + .../tst_dummynmeapositioninfosource.cpp | 149 + .../qnmeapositioninfosource.pro | 8 + .../qnmeapositioninfosource_realtime.pro | 25 + .../tst_qnmeapositioninfosource_realtime.cpp | 44 + ...meapositioninfosource_realtime_generic.pro | 28 + ...meapositioninfosource_realtime_generic.cpp | 74 + .../qnmeapositioninfosource_simulation.pro | 25 + ...tst_qnmeapositioninfosource_simulation.cpp | 43 + ...apositioninfosource_simulation_generic.pro | 28 + ...apositioninfosource_simulation_generic.cpp | 62 + .../qnmeapositioninfosourceproxyfactory.cpp | 95 + .../qnmeapositioninfosourceproxyfactory.h | 75 + .../tst_qnmeapositioninfosource.cpp | 573 ++ .../tst_qnmeapositioninfosource.h | 179 + tests/auto/qplace/qplace.pro | 7 + tests/auto/qplace/tst_qplace.cpp | 681 +++ .../auto/qplaceattribute/qplaceattribute.pro | 7 + .../qplaceattribute/tst_qplaceattribute.cpp | 95 + tests/auto/qplacecategory/qplacecategory.pro | 7 + .../qplacecategory/tst_qplacecategory.cpp | 142 + .../qplacecontactdetail.pro | 7 + .../tst_qplacecontactdetail.cpp | 146 + .../qplacecontentrequest.pro | 6 + .../tst_qplacecontentrequest.cpp | 90 + .../qplacedetailsreply/qplacedetailsreply.pro | 7 + .../tst_qplacedetailsreply.cpp | 89 + .../auto/qplaceeditorial/qplaceeditorial.pro | 7 + .../qplaceeditorial/tst_qplaceeditorial.cpp | 166 + tests/auto/qplaceimage/qplaceimage.pro | 7 + tests/auto/qplaceimage/tst_qplaceimage.cpp | 168 + tests/auto/qplacemanager/qplacemanager.pro | 9 + .../auto/qplacemanager/tst_qplacemanager.cpp | 234 + .../qplacemanager_nokia.pro | 7 + .../tst_qplacemanager_nokia.cpp | 201 + .../qplacemanager_unsupported.pro | 9 + .../tst_qplacemanager_unsupported.cpp | 277 + .../qplacematchreply/qplacematchreply.pro | 7 + .../qplacematchreply/tst_qplacematchreply.cpp | 111 + .../qplacematchrequest/qplacematchrequest.pro | 6 + .../tst_qplacematchrequest.cpp | 166 + tests/auto/qplaceperiod/qplaceperiod.pro | 8 + tests/auto/qplaceperiod/tst_qplaceperiod.cpp | 116 + tests/auto/qplaceratings/qplaceratings.pro | 7 + .../auto/qplaceratings/tst_qplaceratings.cpp | 119 + tests/auto/qplacereply/qplacereply.pro | 7 + tests/auto/qplacereply/tst_qplacereply.cpp | 109 + tests/auto/qplaceresult/qplaceresult.pro | 7 + tests/auto/qplaceresult/tst_qplaceresult.cpp | 270 + tests/auto/qplacereview/qplacereview.pro | 7 + tests/auto/qplacereview/tst_qplacereview.cpp | 220 + .../qplacesearchreply/qplacesearchreply.pro | 7 + .../tst_qplacesearchreply.cpp | 135 + .../qplacesearchrequest.pro | 6 + .../tst_qplacesearchrequest.cpp | 268 + .../qplacesearchresult/qplacesearchresult.pro | 7 + .../tst_qplacesearchresult.cpp | 112 + .../qplacesearchsuggestionreply.pro | 7 + .../tst_qplacesearchsuggestionreply.cpp | 95 + tests/auto/qplacesupplier/qplacesupplier.pro | 7 + .../qplacesupplier/tst_qplacesupplier.cpp | 167 + tests/auto/qplaceuser/qplaceuser.pro | 7 + tests/auto/qplaceuser/tst_qplaceuser.cpp | 139 + .../qproposedsearchresult.pro | 7 + .../tst_qproposedsearchresult.cpp | 221 + tests/auto/utils/qlocationtestutils.cpp | 87 + tests/auto/utils/qlocationtestutils_p.h | 170 + tests/global/global.cfg | 5 + .../declarativetestplugin.pro | 27 + .../declarativetestplugin/locationtest.cpp | 68 + .../qdeclarativelocationtestmodel.cpp | 248 + .../qdeclarativelocationtestmodel_p.h | 130 + .../qdeclarativepinchgenerator.cpp | 382 ++ .../qdeclarativepinchgenerator_p.h | 139 + tests/plugins/declarativetestplugin/qmldir | 3 + .../declarativetestplugin/testhelper.h | 58 + tests/plugins/imports.pri | 5 + tests/tests.pro | 3 + 1255 files changed, 181311 insertions(+) create mode 100644 .qmake.conf create mode 100644 .tag create mode 100644 LGPL_EXCEPTION.txt create mode 100644 LICENSE.FDL create mode 100644 LICENSE.GPL2 create mode 100644 LICENSE.GPL3 create mode 100644 LICENSE.GPL3-EXCEPT create mode 100644 LICENSE.GPLv2 create mode 100644 LICENSE.GPLv3 create mode 100644 LICENSE.LGPL3 create mode 100644 LICENSE.LGPLv21 create mode 100644 LICENSE.LGPLv3 create mode 100644 config.tests/gypsy/gypsy.pro create mode 100644 config.tests/gypsy/main.cpp create mode 100644 dist/changes-5.2.1 create mode 100644 dist/changes-5.3.0 create mode 100644 dist/changes-5.3.1 create mode 100644 dist/changes-5.3.2 create mode 100644 dist/changes-5.4.0 create mode 100644 dist/changes-5.4.1 create mode 100644 dist/changes-5.4.2 create mode 100644 dist/changes-5.5.0 create mode 100644 dist/changes-5.5.1 create mode 100644 dist/changes-5.6.0 create mode 100644 dist/changes-5.6.1 create mode 100644 dist/changes-5.6.2 create mode 100644 dist/changes-5.7.0 create mode 100644 dist/changes-5.7.1 create mode 100644 examples/examples.pro create mode 100644 examples/location/location.pro create mode 100644 examples/location/mapviewer/doc/images/mapviewer.png create mode 100644 examples/location/mapviewer/doc/src/mapviewer.qdoc create mode 100644 examples/location/mapviewer/forms/Geocode.qml create mode 100644 examples/location/mapviewer/forms/GeocodeForm.ui.qml create mode 100644 examples/location/mapviewer/forms/Locale.qml create mode 100644 examples/location/mapviewer/forms/LocaleForm.ui.qml create mode 100644 examples/location/mapviewer/forms/Message.qml create mode 100644 examples/location/mapviewer/forms/MessageForm.ui.qml create mode 100644 examples/location/mapviewer/forms/ReverseGeocode.qml create mode 100644 examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml create mode 100644 examples/location/mapviewer/forms/RouteAddress.qml create mode 100644 examples/location/mapviewer/forms/RouteAddressForm.ui.qml create mode 100644 examples/location/mapviewer/forms/RouteCoordinate.qml create mode 100644 examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml create mode 100644 examples/location/mapviewer/forms/RouteList.qml create mode 100644 examples/location/mapviewer/forms/RouteListDelegate.qml create mode 100644 examples/location/mapviewer/forms/RouteListHeader.qml create mode 100644 examples/location/mapviewer/helper.js create mode 100644 examples/location/mapviewer/main.cpp create mode 100644 examples/location/mapviewer/map/CircleItem.qml create mode 100644 examples/location/mapviewer/map/ImageItem.qml create mode 100644 examples/location/mapviewer/map/MapComponent.qml create mode 100644 examples/location/mapviewer/map/Marker.qml create mode 100644 examples/location/mapviewer/map/MiniMap.qml create mode 100644 examples/location/mapviewer/map/PolygonItem.qml create mode 100644 examples/location/mapviewer/map/PolylineItem.qml create mode 100644 examples/location/mapviewer/map/RectangleItem.qml create mode 100644 examples/location/mapviewer/mapviewer.pro create mode 100644 examples/location/mapviewer/mapviewer.qml create mode 100644 examples/location/mapviewer/mapviewer.qrc create mode 100644 examples/location/mapviewer/menus/ItemPopupMenu.qml create mode 100644 examples/location/mapviewer/menus/MainMenu.qml create mode 100644 examples/location/mapviewer/menus/MapPopupMenu.qml create mode 100644 examples/location/mapviewer/menus/MarkerPopupMenu.qml create mode 100644 examples/location/mapviewer/resources/icon.png create mode 100644 examples/location/mapviewer/resources/marker.png create mode 100644 examples/location/mapviewer/resources/scale.png create mode 100644 examples/location/mapviewer/resources/scale_end.png create mode 100644 examples/location/minimal_map/doc/images/minimal_map.png create mode 100644 examples/location/minimal_map/doc/src/minimal_map.qdoc create mode 100644 examples/location/minimal_map/main.cpp create mode 100644 examples/location/minimal_map/main.qml create mode 100644 examples/location/minimal_map/minimal_map.pro create mode 100644 examples/location/minimal_map/qml.qrc create mode 100644 examples/location/places/doc/images/places.png create mode 100644 examples/location/places/doc/src/places.qdoc create mode 100644 examples/location/places/forms/Message.qml create mode 100644 examples/location/places/forms/MessageForm.ui.qml create mode 100644 examples/location/places/forms/PlaceDetails.qml create mode 100644 examples/location/places/forms/PlaceDetailsForm.ui.qml create mode 100644 examples/location/places/forms/SearchBoundingBox.qml create mode 100644 examples/location/places/forms/SearchBoundingBoxForm.ui.qml create mode 100644 examples/location/places/forms/SearchBoundingCircle.qml create mode 100644 examples/location/places/forms/SearchBoundingCircleForm.ui.qml create mode 100644 examples/location/places/forms/SearchCenter.qml create mode 100644 examples/location/places/forms/SearchCenterForm.ui.qml create mode 100644 examples/location/places/forms/SearchOptions.qml create mode 100644 examples/location/places/forms/SearchOptionsForm.ui.qml create mode 100644 examples/location/places/helper.js create mode 100644 examples/location/places/items/MainMenu.qml create mode 100644 examples/location/places/items/MapComponent.qml create mode 100644 examples/location/places/items/SearchBar.qml create mode 100644 examples/location/places/main.cpp create mode 100644 examples/location/places/places.pro create mode 100644 examples/location/places/places.qml create mode 100644 examples/location/places/places.qrc create mode 100644 examples/location/places/resources/categories.png create mode 100644 examples/location/places/resources/left.png create mode 100644 examples/location/places/resources/marker.png create mode 100644 examples/location/places/resources/right.png create mode 100644 examples/location/places/resources/scale.png create mode 100644 examples/location/places/resources/scale_end.png create mode 100644 examples/location/places/resources/search.png create mode 100644 examples/location/places/resources/star.png create mode 100644 examples/location/places/views/CategoryDelegate.qml create mode 100644 examples/location/places/views/CategoryView.qml create mode 100644 examples/location/places/views/EditorialDelegate.qml create mode 100644 examples/location/places/views/EditorialPage.qml create mode 100644 examples/location/places/views/EditorialView.qml create mode 100644 examples/location/places/views/ImageView.qml create mode 100644 examples/location/places/views/RatingView.qml create mode 100644 examples/location/places/views/ReviewDelegate.qml create mode 100644 examples/location/places/views/ReviewPage.qml create mode 100644 examples/location/places/views/ReviewView.qml create mode 100644 examples/location/places/views/SearchResultDelegate.qml create mode 100644 examples/location/places/views/SearchResultView.qml create mode 100644 examples/location/places/views/SuggestionView.qml create mode 100644 examples/location/places_list/Marker.qml create mode 100644 examples/location/places_list/doc/images/places_list.png create mode 100644 examples/location/places_list/doc/src/places_list.qdoc create mode 100644 examples/location/places_list/main.cpp create mode 100644 examples/location/places_list/marker.png create mode 100644 examples/location/places_list/places_list.pro create mode 100644 examples/location/places_list/places_list.qml create mode 100644 examples/location/places_list/places_list.qrc create mode 100644 examples/location/places_map/doc/images/places_map.png create mode 100644 examples/location/places_map/doc/src/places_map.qdoc create mode 100644 examples/location/places_map/main.cpp create mode 100644 examples/location/places_map/marker.png create mode 100644 examples/location/places_map/places_map.pro create mode 100644 examples/location/places_map/places_map.qml create mode 100644 examples/location/places_map/places_map.qrc create mode 100644 examples/location/planespotter/Plane.qml create mode 100644 examples/location/planespotter/airplane.png create mode 100644 examples/location/planespotter/doc/images/planespotter.png create mode 100644 examples/location/planespotter/doc/src/planespotter.qdoc create mode 100644 examples/location/planespotter/main.cpp create mode 100644 examples/location/planespotter/planespotter.pro create mode 100644 examples/location/planespotter/planespotter.qml create mode 100644 examples/location/planespotter/qml.qrc create mode 100644 examples/positioning/geoflickr/doc/images/qml-flickr-1.jpg create mode 100644 examples/positioning/geoflickr/doc/src/geoflickr.qdoc create mode 100644 examples/positioning/geoflickr/flickr-90.qml create mode 100644 examples/positioning/geoflickr/flickr.qml create mode 100644 examples/positioning/geoflickr/flickr.qrc create mode 100644 examples/positioning/geoflickr/flickrcommon/Progress.qml create mode 100644 examples/positioning/geoflickr/flickrcommon/RestModel.qml create mode 100644 examples/positioning/geoflickr/flickrcommon/ScrollBar.qml create mode 100644 examples/positioning/geoflickr/flickrcommon/Slider.qml create mode 100644 examples/positioning/geoflickr/flickrmobile/Button.qml create mode 100644 examples/positioning/geoflickr/flickrmobile/GeoTab.qml create mode 100644 examples/positioning/geoflickr/flickrmobile/GridDelegate.qml create mode 100644 examples/positioning/geoflickr/flickrmobile/ImageDetails.qml create mode 100644 examples/positioning/geoflickr/flickrmobile/ListDelegate.qml create mode 100644 examples/positioning/geoflickr/flickrmobile/TitleBar.qml create mode 100644 examples/positioning/geoflickr/flickrmobile/ToolBar.qml create mode 100644 examples/positioning/geoflickr/flickrmobile/images/gloss.png create mode 100644 examples/positioning/geoflickr/flickrmobile/images/lineedit.png create mode 100644 examples/positioning/geoflickr/flickrmobile/images/lineedit.sci create mode 100644 examples/positioning/geoflickr/flickrmobile/images/moon.png create mode 100644 examples/positioning/geoflickr/flickrmobile/images/quit.png create mode 100644 examples/positioning/geoflickr/flickrmobile/images/star.png create mode 100644 examples/positioning/geoflickr/flickrmobile/images/stripes.png create mode 100644 examples/positioning/geoflickr/flickrmobile/images/sun.png create mode 100644 examples/positioning/geoflickr/flickrmobile/images/titlebar.png create mode 100644 examples/positioning/geoflickr/flickrmobile/images/titlebar.sci create mode 100644 examples/positioning/geoflickr/flickrmobile/images/toolbutton.png create mode 100644 examples/positioning/geoflickr/flickrmobile/images/toolbutton.sci create mode 100644 examples/positioning/geoflickr/flickrmobile/nmealog.txt create mode 100644 examples/positioning/geoflickr/geoflickr.pro create mode 100644 examples/positioning/geoflickr/geoflickr.qmlproject create mode 100644 examples/positioning/geoflickr/qmllocationflickr.cpp create mode 100644 examples/positioning/logfilepositionsource/clientapplication.cpp create mode 100644 examples/positioning/logfilepositionsource/clientapplication.h create mode 100644 examples/positioning/logfilepositionsource/doc/src/logfilepositionsource.qdoc create mode 100644 examples/positioning/logfilepositionsource/logfile.qrc create mode 100644 examples/positioning/logfilepositionsource/logfilepositionsource.cpp create mode 100644 examples/positioning/logfilepositionsource/logfilepositionsource.h create mode 100644 examples/positioning/logfilepositionsource/logfilepositionsource.pro create mode 100644 examples/positioning/logfilepositionsource/main.cpp create mode 100644 examples/positioning/logfilepositionsource/simplelog.txt create mode 100644 examples/positioning/positioning.pro create mode 100644 examples/positioning/satelliteinfo/doc/images/example-satelliteinfo.png create mode 100644 examples/positioning/satelliteinfo/doc/src/satelliteinfo.qdoc create mode 100644 examples/positioning/satelliteinfo/main.cpp create mode 100644 examples/positioning/satelliteinfo/satelliteinfo.pro create mode 100644 examples/positioning/satelliteinfo/satelliteinfo.qml create mode 100644 examples/positioning/satelliteinfo/satelliteinfo.qrc create mode 100644 examples/positioning/satelliteinfo/satellitemodel.cpp create mode 100644 examples/positioning/satelliteinfo/satellitemodel.h create mode 100644 examples/positioning/weatherinfo/appmodel.cpp create mode 100644 examples/positioning/weatherinfo/appmodel.h create mode 100644 examples/positioning/weatherinfo/components/BigForecastIcon.qml create mode 100644 examples/positioning/weatherinfo/components/ForecastIcon.qml create mode 100644 examples/positioning/weatherinfo/components/WeatherIcon.qml create mode 100644 examples/positioning/weatherinfo/doc/images/example-weatherinfo.png create mode 100644 examples/positioning/weatherinfo/doc/src/weatherinfo.qdoc create mode 100644 examples/positioning/weatherinfo/icons/README.txt create mode 100644 examples/positioning/weatherinfo/icons/weather-few-clouds.png create mode 100644 examples/positioning/weatherinfo/icons/weather-fog.png create mode 100644 examples/positioning/weatherinfo/icons/weather-haze.png create mode 100644 examples/positioning/weatherinfo/icons/weather-icy.png create mode 100644 examples/positioning/weatherinfo/icons/weather-overcast.png create mode 100644 examples/positioning/weatherinfo/icons/weather-showers.png create mode 100644 examples/positioning/weatherinfo/icons/weather-sleet.png create mode 100644 examples/positioning/weatherinfo/icons/weather-snow.png create mode 100644 examples/positioning/weatherinfo/icons/weather-storm.png create mode 100644 examples/positioning/weatherinfo/icons/weather-sunny-very-few-clouds.png create mode 100644 examples/positioning/weatherinfo/icons/weather-sunny.png create mode 100644 examples/positioning/weatherinfo/icons/weather-thundershower.png create mode 100644 examples/positioning/weatherinfo/main.cpp create mode 100644 examples/positioning/weatherinfo/weatherinfo.pro create mode 100644 examples/positioning/weatherinfo/weatherinfo.qml create mode 100644 examples/positioning/weatherinfo/weatherinfo.qrc create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qabstractgeotilecache_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qcache3q_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeocameracapabilities_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeocameradata_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeocameratiles_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeocodereply_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeocodingmanager_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeocodingmanagerengine_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeofiletilecache_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeomaneuver_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeomap_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeomap_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanager_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanager_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanagerengine_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanagerengine_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeomaptype_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeomaptype_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeoroute_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeorouteparser_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeorouteparser_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeorouteparserosrmv4_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeorouteparserosrmv5_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeoroutereply_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeorouterequest_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeoroutesegment_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeoroutingmanager_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeoroutingmanagerengine_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeoserviceprovider_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotiledmap_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotiledmap_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotiledmappingmanagerengine_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotiledmappingmanagerengine_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapreply_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapreply_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapscene_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotilefetcher_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotilefetcher_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotilerequestmanager_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotilespec_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qgeotilespec_p_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplace_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplaceattribute_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplacecategory_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplacecontactdetail_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplacecontent_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplacecontentrequest_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplaceeditorial_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplaceicon_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplaceimage_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplacemanagerengine_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplaceproposedsearchresult_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplaceratings_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplacereply_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplaceresult_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplacereview_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplacesearchresult_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplacesupplier_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/qplaceuser_p.h create mode 100644 include/QtLocation/5.7.1/QtLocation/private/unsupportedreplies_p.h create mode 100644 include/QtLocation/QGeoCodeReply create mode 100644 include/QtLocation/QGeoCodingManager create mode 100644 include/QtLocation/QGeoCodingManagerEngine create mode 100644 include/QtLocation/QGeoManeuver create mode 100644 include/QtLocation/QGeoRoute create mode 100644 include/QtLocation/QGeoRouteReply create mode 100644 include/QtLocation/QGeoRouteRequest create mode 100644 include/QtLocation/QGeoRouteSegment create mode 100644 include/QtLocation/QGeoRoutingManager create mode 100644 include/QtLocation/QGeoRoutingManagerEngine create mode 100644 include/QtLocation/QGeoServiceProvider create mode 100644 include/QtLocation/QGeoServiceProviderFactory create mode 100644 include/QtLocation/QLocation create mode 100644 include/QtLocation/QPlace create mode 100644 include/QtLocation/QPlaceAttribute create mode 100644 include/QtLocation/QPlaceCategory create mode 100644 include/QtLocation/QPlaceContactDetail create mode 100644 include/QtLocation/QPlaceContent create mode 100644 include/QtLocation/QPlaceContentReply create mode 100644 include/QtLocation/QPlaceContentRequest create mode 100644 include/QtLocation/QPlaceDetailsReply create mode 100644 include/QtLocation/QPlaceEditorial create mode 100644 include/QtLocation/QPlaceIcon create mode 100644 include/QtLocation/QPlaceIdReply create mode 100644 include/QtLocation/QPlaceImage create mode 100644 include/QtLocation/QPlaceManager create mode 100644 include/QtLocation/QPlaceManagerEngine create mode 100644 include/QtLocation/QPlaceMatchReply create mode 100644 include/QtLocation/QPlaceMatchRequest create mode 100644 include/QtLocation/QPlaceProposedSearchResult create mode 100644 include/QtLocation/QPlaceRatings create mode 100644 include/QtLocation/QPlaceReply create mode 100644 include/QtLocation/QPlaceResult create mode 100644 include/QtLocation/QPlaceReview create mode 100644 include/QtLocation/QPlaceSearchReply create mode 100644 include/QtLocation/QPlaceSearchRequest create mode 100644 include/QtLocation/QPlaceSearchResult create mode 100644 include/QtLocation/QPlaceSearchSuggestionReply create mode 100644 include/QtLocation/QPlaceSupplier create mode 100644 include/QtLocation/QPlaceUser create mode 100644 include/QtLocation/QtLocation create mode 100644 include/QtLocation/QtLocationVersion create mode 100644 include/QtLocation/headers.pri create mode 100644 include/QtLocation/placemacro.h create mode 100644 include/QtLocation/qgeocodereply.h create mode 100644 include/QtLocation/qgeocodingmanager.h create mode 100644 include/QtLocation/qgeocodingmanagerengine.h create mode 100644 include/QtLocation/qgeomaneuver.h create mode 100644 include/QtLocation/qgeoroute.h create mode 100644 include/QtLocation/qgeoroutereply.h create mode 100644 include/QtLocation/qgeorouterequest.h create mode 100644 include/QtLocation/qgeoroutesegment.h create mode 100644 include/QtLocation/qgeoroutingmanager.h create mode 100644 include/QtLocation/qgeoroutingmanagerengine.h create mode 100644 include/QtLocation/qgeoserviceprovider.h create mode 100644 include/QtLocation/qgeoserviceproviderfactory.h create mode 100644 include/QtLocation/qlocation.h create mode 100644 include/QtLocation/qlocationglobal.h create mode 100644 include/QtLocation/qplace.h create mode 100644 include/QtLocation/qplaceattribute.h create mode 100644 include/QtLocation/qplacecategory.h create mode 100644 include/QtLocation/qplacecontactdetail.h create mode 100644 include/QtLocation/qplacecontent.h create mode 100644 include/QtLocation/qplacecontentreply.h create mode 100644 include/QtLocation/qplacecontentrequest.h create mode 100644 include/QtLocation/qplacedetailsreply.h create mode 100644 include/QtLocation/qplaceeditorial.h create mode 100644 include/QtLocation/qplaceicon.h create mode 100644 include/QtLocation/qplaceidreply.h create mode 100644 include/QtLocation/qplaceimage.h create mode 100644 include/QtLocation/qplacemanager.h create mode 100644 include/QtLocation/qplacemanagerengine.h create mode 100644 include/QtLocation/qplacematchreply.h create mode 100644 include/QtLocation/qplacematchrequest.h create mode 100644 include/QtLocation/qplaceproposedsearchresult.h create mode 100644 include/QtLocation/qplaceratings.h create mode 100644 include/QtLocation/qplacereply.h create mode 100644 include/QtLocation/qplaceresult.h create mode 100644 include/QtLocation/qplacereview.h create mode 100644 include/QtLocation/qplacesearchreply.h create mode 100644 include/QtLocation/qplacesearchrequest.h create mode 100644 include/QtLocation/qplacesearchresult.h create mode 100644 include/QtLocation/qplacesearchsuggestionreply.h create mode 100644 include/QtLocation/qplacesupplier.h create mode 100644 include/QtLocation/qplaceuser.h create mode 100644 include/QtLocation/qtlocationversion.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qdeclarativegeoaddress_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qdeclarativegeolocation_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qdoublevector2d_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qdoublevector3d_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qgeoaddress_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qgeocircle_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qgeocoordinate_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qgeolocation_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qgeopositioninfosource_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qgeoprojection_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qgeorectangle_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qgeoshape_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qlocationdata_simulator_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qlocationutils_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qnmeapositioninfosource_p.h create mode 100644 include/QtPositioning/5.7.1/QtPositioning/private/qpositioningglobal_p.h create mode 100644 include/QtPositioning/QGeoAddress create mode 100644 include/QtPositioning/QGeoAreaMonitorInfo create mode 100644 include/QtPositioning/QGeoAreaMonitorSource create mode 100644 include/QtPositioning/QGeoCircle create mode 100644 include/QtPositioning/QGeoCoordinate create mode 100644 include/QtPositioning/QGeoLocation create mode 100644 include/QtPositioning/QGeoPositionInfo create mode 100644 include/QtPositioning/QGeoPositionInfoSource create mode 100644 include/QtPositioning/QGeoPositionInfoSourceFactory create mode 100644 include/QtPositioning/QGeoRectangle create mode 100644 include/QtPositioning/QGeoSatelliteInfo create mode 100644 include/QtPositioning/QGeoSatelliteInfoSource create mode 100644 include/QtPositioning/QGeoShape create mode 100644 include/QtPositioning/QNmeaPositionInfoSource create mode 100644 include/QtPositioning/QtPositioning create mode 100644 include/QtPositioning/QtPositioningVersion create mode 100644 include/QtPositioning/headers.pri create mode 100644 include/QtPositioning/qgeoaddress.h create mode 100644 include/QtPositioning/qgeoareamonitorinfo.h create mode 100644 include/QtPositioning/qgeoareamonitorsource.h create mode 100644 include/QtPositioning/qgeocircle.h create mode 100644 include/QtPositioning/qgeocoordinate.h create mode 100644 include/QtPositioning/qgeolocation.h create mode 100644 include/QtPositioning/qgeopositioninfo.h create mode 100644 include/QtPositioning/qgeopositioninfosource.h create mode 100644 include/QtPositioning/qgeopositioninfosourcefactory.h create mode 100644 include/QtPositioning/qgeorectangle.h create mode 100644 include/QtPositioning/qgeosatelliteinfo.h create mode 100644 include/QtPositioning/qgeosatelliteinfosource.h create mode 100644 include/QtPositioning/qgeoshape.h create mode 100644 include/QtPositioning/qnmeapositioninfosource.h create mode 100644 include/QtPositioning/qpositioningglobal.h create mode 100644 include/QtPositioning/qtpositioningversion.h create mode 100644 qtlocation.pro create mode 100644 src/3rdparty/3rdparty.pro create mode 100644 src/3rdparty/clip2tri/LICENSE create mode 100644 src/3rdparty/clip2tri/clip2tri.cpp create mode 100644 src/3rdparty/clip2tri/clip2tri.h create mode 100644 src/3rdparty/clip2tri/clip2tri.pro create mode 100644 src/3rdparty/clip2tri_legal.qdoc create mode 100644 src/3rdparty/clipper/clipper.cpp create mode 100644 src/3rdparty/clipper/clipper.h create mode 100644 src/3rdparty/clipper/clipper.pro create mode 100644 src/3rdparty/clipper_legal.qdoc create mode 100644 src/3rdparty/poly2tri/AUTHORS create mode 100644 src/3rdparty/poly2tri/LICENSE create mode 100644 src/3rdparty/poly2tri/common/shapes.cpp create mode 100644 src/3rdparty/poly2tri/common/shapes.h create mode 100644 src/3rdparty/poly2tri/common/utils.h create mode 100644 src/3rdparty/poly2tri/poly2tri.h create mode 100644 src/3rdparty/poly2tri/poly2tri.pro create mode 100644 src/3rdparty/poly2tri/sweep/advancing_front.cpp create mode 100644 src/3rdparty/poly2tri/sweep/advancing_front.h create mode 100644 src/3rdparty/poly2tri/sweep/cdt.cpp create mode 100644 src/3rdparty/poly2tri/sweep/cdt.h create mode 100644 src/3rdparty/poly2tri/sweep/sweep.cpp create mode 100644 src/3rdparty/poly2tri/sweep/sweep.h create mode 100644 src/3rdparty/poly2tri/sweep/sweep_context.cpp create mode 100644 src/3rdparty/poly2tri/sweep/sweep_context.h create mode 100644 src/3rdparty/poly2tri_legal.qdoc create mode 100644 src/imports/imports.pro create mode 100644 src/imports/location/declarativeplaces/declarativeplaces.pri create mode 100644 src/imports/location/declarativeplaces/qdeclarativecategory.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativecategory_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativecontactdetail.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativecontactdetail_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativeperiod_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplace.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplace_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceattribute.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceattribute_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplacecontentmodel.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplacecontentmodel.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceeditorialmodel.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceeditorialmodel.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceicon.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceicon_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceimagemodel.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceimagemodel_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceuser.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativeplaceuser_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativeratings.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativeratings_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativereviewmodel.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativereviewmodel_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativesearchmodelbase.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativesearchmodelbase.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativesearchresultmodel.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativesearchresultmodel_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativesearchsuggestionmodel.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativesearchsuggestionmodel_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativesupplier.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativesupplier_p.h create mode 100644 src/imports/location/declarativeplaces/qdeclarativesupportedcategoriesmodel.cpp create mode 100644 src/imports/location/declarativeplaces/qdeclarativesupportedcategoriesmodel_p.h create mode 100644 src/imports/location/error_messages.cpp create mode 100644 src/imports/location/error_messages.h create mode 100644 src/imports/location/location.cpp create mode 100644 src/imports/location/location.pro create mode 100644 src/imports/location/locationvaluetypehelper.cpp create mode 100644 src/imports/location/locationvaluetypehelper_p.h create mode 100644 src/imports/location/mapitemviewdelegateincubator.cpp create mode 100644 src/imports/location/mapitemviewdelegateincubator.h create mode 100644 src/imports/location/plugin.json create mode 100644 src/imports/location/plugins.qmltypes create mode 100644 src/imports/location/qdeclarativecirclemapitem.cpp create mode 100644 src/imports/location/qdeclarativecirclemapitem_p.h create mode 100644 src/imports/location/qdeclarativegeocodemodel.cpp create mode 100644 src/imports/location/qdeclarativegeocodemodel_p.h create mode 100644 src/imports/location/qdeclarativegeomaneuver.cpp create mode 100644 src/imports/location/qdeclarativegeomaneuver_p.h create mode 100644 src/imports/location/qdeclarativegeomap.cpp create mode 100644 src/imports/location/qdeclarativegeomap_p.h create mode 100644 src/imports/location/qdeclarativegeomapcopyrightsnotice.cpp create mode 100644 src/imports/location/qdeclarativegeomapcopyrightsnotice_p.h create mode 100644 src/imports/location/qdeclarativegeomapitembase.cpp create mode 100644 src/imports/location/qdeclarativegeomapitembase_p.h create mode 100644 src/imports/location/qdeclarativegeomapitemview.cpp create mode 100644 src/imports/location/qdeclarativegeomapitemview_p.h create mode 100644 src/imports/location/qdeclarativegeomapitemview_p_p.h create mode 100644 src/imports/location/qdeclarativegeomapquickitem.cpp create mode 100644 src/imports/location/qdeclarativegeomapquickitem_p.h create mode 100644 src/imports/location/qdeclarativegeomaptype.cpp create mode 100644 src/imports/location/qdeclarativegeomaptype_p.h create mode 100644 src/imports/location/qdeclarativegeoroute.cpp create mode 100644 src/imports/location/qdeclarativegeoroute_p.h create mode 100644 src/imports/location/qdeclarativegeoroutemodel.cpp create mode 100644 src/imports/location/qdeclarativegeoroutemodel_p.h create mode 100644 src/imports/location/qdeclarativegeoroutesegment.cpp create mode 100644 src/imports/location/qdeclarativegeoroutesegment_p.h create mode 100644 src/imports/location/qdeclarativegeoserviceprovider.cpp create mode 100644 src/imports/location/qdeclarativegeoserviceprovider_p.h create mode 100644 src/imports/location/qdeclarativepolygonmapitem.cpp create mode 100644 src/imports/location/qdeclarativepolygonmapitem_p.h create mode 100644 src/imports/location/qdeclarativepolylinemapitem.cpp create mode 100644 src/imports/location/qdeclarativepolylinemapitem_p.h create mode 100644 src/imports/location/qdeclarativerectanglemapitem.cpp create mode 100644 src/imports/location/qdeclarativerectanglemapitem_p.h create mode 100644 src/imports/location/qdeclarativeroutemapitem.cpp create mode 100644 src/imports/location/qdeclarativeroutemapitem_p.h create mode 100644 src/imports/location/qgeomapitemgeometry.cpp create mode 100644 src/imports/location/qgeomapitemgeometry_p.h create mode 100644 src/imports/location/qmldir create mode 100644 src/imports/location/qquickgeomapgesturearea.cpp create mode 100644 src/imports/location/qquickgeomapgesturearea_p.h create mode 100644 src/imports/positioning/locationsingleton.cpp create mode 100644 src/imports/positioning/locationsingleton.h create mode 100644 src/imports/positioning/plugin.json create mode 100644 src/imports/positioning/plugins.qmltypes create mode 100644 src/imports/positioning/positioning.cpp create mode 100644 src/imports/positioning/positioning.pro create mode 100644 src/imports/positioning/qdeclarativeposition.cpp create mode 100644 src/imports/positioning/qdeclarativeposition_p.h create mode 100644 src/imports/positioning/qdeclarativepositionsource.cpp create mode 100644 src/imports/positioning/qdeclarativepositionsource_p.h create mode 100644 src/imports/positioning/qmldir create mode 100644 src/imports/positioning/qquickgeocoordinateanimation.cpp create mode 100644 src/imports/positioning/qquickgeocoordinateanimation_p.h create mode 100644 src/imports/positioning/qquickgeocoordinateanimation_p_p.h create mode 100644 src/location/doc/images/api-mapcircle.png create mode 100644 src/location/doc/images/api-mappolygon.png create mode 100644 src/location/doc/images/api-mappolyline.png create mode 100644 src/location/doc/images/api-mapquickitem-anchor.png create mode 100644 src/location/doc/images/api-mapquickitem.png create mode 100644 src/location/doc/images/api-maprectangle.png create mode 100644 src/location/doc/images/mapsdemo-finished.png create mode 100644 src/location/doc/images/mapsdemo-routing.png create mode 100644 src/location/doc/images/mapsdemo-searchgui.png create mode 100644 src/location/doc/images/mapsdemo-verybasic.png create mode 100644 src/location/doc/qtlocation.qdocconf create mode 100644 src/location/doc/snippets/cpp/cpp.pro create mode 100644 src/location/doc/snippets/cpp/cppqml.cpp create mode 100644 src/location/doc/snippets/cpp/main.cpp create mode 100644 src/location/doc/snippets/declarative/content/Cell.qml create mode 100644 src/location/doc/snippets/declarative/declarative-location.qml create mode 100644 src/location/doc/snippets/declarative/declarative.pro create mode 100644 src/location/doc/snippets/declarative/maps.qml create mode 100644 src/location/doc/snippets/declarative/marker.png create mode 100644 src/location/doc/snippets/declarative/nmealog.txt create mode 100644 src/location/doc/snippets/declarative/places.qml create mode 100644 src/location/doc/snippets/declarative/places_loader.qml create mode 100644 src/location/doc/snippets/declarative/plugin.qml create mode 100644 src/location/doc/snippets/declarative/routing.qml create mode 100644 src/location/doc/snippets/places/main.cpp create mode 100644 src/location/doc/snippets/places/places.pro create mode 100644 src/location/doc/snippets/places/requesthandler.h create mode 100644 src/location/doc/snippets/snippets.pro create mode 100644 src/location/doc/src/cpp-qml.qdoc create mode 100644 src/location/doc/src/example-parameters.qdocinc create mode 100644 src/location/doc/src/maps.qdoc create mode 100644 src/location/doc/src/place-caveats.qdocinc create mode 100644 src/location/doc/src/place-crossref.qdocinc create mode 100644 src/location/doc/src/place-definition.qdocinc create mode 100644 src/location/doc/src/places.qdoc create mode 100644 src/location/doc/src/plugins/mapbox.qdoc create mode 100644 src/location/doc/src/plugins/nokia.qdoc create mode 100644 src/location/doc/src/plugins/osm.qdoc create mode 100644 src/location/doc/src/plugins/places-backend.qdoc create mode 100644 src/location/doc/src/qml-maps.qdoc create mode 100644 src/location/doc/src/qtlocation-changes.qdoc create mode 100644 src/location/doc/src/qtlocation-cpp.qdoc create mode 100644 src/location/doc/src/qtlocation-examples.qdoc create mode 100644 src/location/doc/src/qtlocation-geoservices.qdoc create mode 100644 src/location/doc/src/qtlocation-qml.qdoc create mode 100644 src/location/doc/src/qtlocation.qdoc create mode 100644 src/location/doc/src/src.pro create mode 100644 src/location/location.pro create mode 100644 src/location/maps/maps.pri create mode 100644 src/location/maps/qabstractgeotilecache.cpp create mode 100644 src/location/maps/qabstractgeotilecache_p.h create mode 100644 src/location/maps/qcache3q_p.h create mode 100644 src/location/maps/qgeocameracapabilities.cpp create mode 100644 src/location/maps/qgeocameracapabilities_p.h create mode 100644 src/location/maps/qgeocameradata.cpp create mode 100644 src/location/maps/qgeocameradata_p.h create mode 100644 src/location/maps/qgeocameratiles.cpp create mode 100644 src/location/maps/qgeocameratiles_p.h create mode 100644 src/location/maps/qgeocodereply.cpp create mode 100644 src/location/maps/qgeocodereply.h create mode 100644 src/location/maps/qgeocodereply_p.h create mode 100644 src/location/maps/qgeocodingmanager.cpp create mode 100644 src/location/maps/qgeocodingmanager.h create mode 100644 src/location/maps/qgeocodingmanager_p.h create mode 100644 src/location/maps/qgeocodingmanagerengine.cpp create mode 100644 src/location/maps/qgeocodingmanagerengine.h create mode 100644 src/location/maps/qgeocodingmanagerengine_p.h create mode 100644 src/location/maps/qgeofiletilecache.cpp create mode 100644 src/location/maps/qgeofiletilecache_p.h create mode 100644 src/location/maps/qgeomaneuver.cpp create mode 100644 src/location/maps/qgeomaneuver.h create mode 100644 src/location/maps/qgeomaneuver_p.h create mode 100644 src/location/maps/qgeomap.cpp create mode 100644 src/location/maps/qgeomap_p.h create mode 100644 src/location/maps/qgeomap_p_p.h create mode 100644 src/location/maps/qgeomappingmanager.cpp create mode 100644 src/location/maps/qgeomappingmanager_p.h create mode 100644 src/location/maps/qgeomappingmanager_p_p.h create mode 100644 src/location/maps/qgeomappingmanagerengine.cpp create mode 100644 src/location/maps/qgeomappingmanagerengine_p.h create mode 100644 src/location/maps/qgeomappingmanagerengine_p_p.h create mode 100644 src/location/maps/qgeomaptype.cpp create mode 100644 src/location/maps/qgeomaptype_p.h create mode 100644 src/location/maps/qgeomaptype_p_p.h create mode 100644 src/location/maps/qgeoroute.cpp create mode 100644 src/location/maps/qgeoroute.h create mode 100644 src/location/maps/qgeoroute_p.h create mode 100644 src/location/maps/qgeorouteparser.cpp create mode 100644 src/location/maps/qgeorouteparser_p.h create mode 100644 src/location/maps/qgeorouteparser_p_p.h create mode 100644 src/location/maps/qgeorouteparserosrmv4.cpp create mode 100644 src/location/maps/qgeorouteparserosrmv4_p.h create mode 100644 src/location/maps/qgeorouteparserosrmv5.cpp create mode 100644 src/location/maps/qgeorouteparserosrmv5_p.h create mode 100644 src/location/maps/qgeoroutereply.cpp create mode 100644 src/location/maps/qgeoroutereply.h create mode 100644 src/location/maps/qgeoroutereply_p.h create mode 100644 src/location/maps/qgeorouterequest.cpp create mode 100644 src/location/maps/qgeorouterequest.h create mode 100644 src/location/maps/qgeorouterequest_p.h create mode 100644 src/location/maps/qgeoroutesegment.cpp create mode 100644 src/location/maps/qgeoroutesegment.h create mode 100644 src/location/maps/qgeoroutesegment_p.h create mode 100644 src/location/maps/qgeoroutingmanager.cpp create mode 100644 src/location/maps/qgeoroutingmanager.h create mode 100644 src/location/maps/qgeoroutingmanager_p.h create mode 100644 src/location/maps/qgeoroutingmanagerengine.cpp create mode 100644 src/location/maps/qgeoroutingmanagerengine.h create mode 100644 src/location/maps/qgeoroutingmanagerengine_p.h create mode 100644 src/location/maps/qgeoserviceprovider.cpp create mode 100644 src/location/maps/qgeoserviceprovider.h create mode 100644 src/location/maps/qgeoserviceprovider_p.h create mode 100644 src/location/maps/qgeoserviceproviderfactory.cpp create mode 100644 src/location/maps/qgeoserviceproviderfactory.h create mode 100644 src/location/maps/qgeotiledmap.cpp create mode 100644 src/location/maps/qgeotiledmap_p.h create mode 100644 src/location/maps/qgeotiledmap_p_p.h create mode 100644 src/location/maps/qgeotiledmappingmanagerengine.cpp create mode 100644 src/location/maps/qgeotiledmappingmanagerengine_p.h create mode 100644 src/location/maps/qgeotiledmappingmanagerengine_p_p.h create mode 100644 src/location/maps/qgeotiledmapreply.cpp create mode 100644 src/location/maps/qgeotiledmapreply_p.h create mode 100644 src/location/maps/qgeotiledmapreply_p_p.h create mode 100644 src/location/maps/qgeotiledmapscene.cpp create mode 100644 src/location/maps/qgeotiledmapscene_p.h create mode 100644 src/location/maps/qgeotilefetcher.cpp create mode 100644 src/location/maps/qgeotilefetcher_p.h create mode 100644 src/location/maps/qgeotilefetcher_p_p.h create mode 100644 src/location/maps/qgeotilerequestmanager.cpp create mode 100644 src/location/maps/qgeotilerequestmanager_p.h create mode 100644 src/location/maps/qgeotilespec.cpp create mode 100644 src/location/maps/qgeotilespec_p.h create mode 100644 src/location/maps/qgeotilespec_p_p.h create mode 100644 src/location/places/placemacro.h create mode 100644 src/location/places/places.pri create mode 100644 src/location/places/qplace.cpp create mode 100644 src/location/places/qplace.h create mode 100644 src/location/places/qplace_p.h create mode 100644 src/location/places/qplaceattribute.cpp create mode 100644 src/location/places/qplaceattribute.h create mode 100644 src/location/places/qplaceattribute_p.h create mode 100644 src/location/places/qplacecategory.cpp create mode 100644 src/location/places/qplacecategory.h create mode 100644 src/location/places/qplacecategory_p.h create mode 100644 src/location/places/qplacecontactdetail.cpp create mode 100644 src/location/places/qplacecontactdetail.h create mode 100644 src/location/places/qplacecontactdetail_p.h create mode 100644 src/location/places/qplacecontent.cpp create mode 100644 src/location/places/qplacecontent.h create mode 100644 src/location/places/qplacecontent_p.h create mode 100644 src/location/places/qplacecontentreply.cpp create mode 100644 src/location/places/qplacecontentreply.h create mode 100644 src/location/places/qplacecontentrequest.cpp create mode 100644 src/location/places/qplacecontentrequest.h create mode 100644 src/location/places/qplacecontentrequest_p.h create mode 100644 src/location/places/qplacedetailsreply.cpp create mode 100644 src/location/places/qplacedetailsreply.h create mode 100644 src/location/places/qplaceeditorial.cpp create mode 100644 src/location/places/qplaceeditorial.h create mode 100644 src/location/places/qplaceeditorial_p.h create mode 100644 src/location/places/qplaceicon.cpp create mode 100644 src/location/places/qplaceicon.h create mode 100644 src/location/places/qplaceicon_p.h create mode 100644 src/location/places/qplaceidreply.cpp create mode 100644 src/location/places/qplaceidreply.h create mode 100644 src/location/places/qplaceimage.cpp create mode 100644 src/location/places/qplaceimage.h create mode 100644 src/location/places/qplaceimage_p.h create mode 100644 src/location/places/qplacemanager.cpp create mode 100644 src/location/places/qplacemanager.h create mode 100644 src/location/places/qplacemanagerengine.cpp create mode 100644 src/location/places/qplacemanagerengine.h create mode 100644 src/location/places/qplacemanagerengine_p.h create mode 100644 src/location/places/qplacematchreply.cpp create mode 100644 src/location/places/qplacematchreply.h create mode 100644 src/location/places/qplacematchrequest.cpp create mode 100644 src/location/places/qplacematchrequest.h create mode 100644 src/location/places/qplaceproposedsearchresult.cpp create mode 100644 src/location/places/qplaceproposedsearchresult.h create mode 100644 src/location/places/qplaceproposedsearchresult_p.h create mode 100644 src/location/places/qplaceratings.cpp create mode 100644 src/location/places/qplaceratings.h create mode 100644 src/location/places/qplaceratings_p.h create mode 100644 src/location/places/qplacereply.cpp create mode 100644 src/location/places/qplacereply.h create mode 100644 src/location/places/qplacereply_p.h create mode 100644 src/location/places/qplaceresult.cpp create mode 100644 src/location/places/qplaceresult.h create mode 100644 src/location/places/qplaceresult_p.h create mode 100644 src/location/places/qplacereview.cpp create mode 100644 src/location/places/qplacereview.h create mode 100644 src/location/places/qplacereview_p.h create mode 100644 src/location/places/qplacesearchreply.cpp create mode 100644 src/location/places/qplacesearchreply.h create mode 100644 src/location/places/qplacesearchrequest.cpp create mode 100644 src/location/places/qplacesearchrequest.h create mode 100644 src/location/places/qplacesearchresult.cpp create mode 100644 src/location/places/qplacesearchresult.h create mode 100644 src/location/places/qplacesearchresult_p.h create mode 100644 src/location/places/qplacesearchsuggestionreply.cpp create mode 100644 src/location/places/qplacesearchsuggestionreply.h create mode 100644 src/location/places/qplacesupplier.cpp create mode 100644 src/location/places/qplacesupplier.h create mode 100644 src/location/places/qplacesupplier_p.h create mode 100644 src/location/places/qplaceuser.cpp create mode 100644 src/location/places/qplaceuser.h create mode 100644 src/location/places/qplaceuser_p.h create mode 100644 src/location/places/unsupportedreplies_p.h create mode 100644 src/location/qlocation.cpp create mode 100644 src/location/qlocation.h create mode 100644 src/location/qlocationglobal.h create mode 100644 src/plugins/geoservices/geoservices.pro create mode 100644 src/plugins/geoservices/mapbox/mapbox.pro create mode 100644 src/plugins/geoservices/mapbox/mapbox_plugin.json create mode 100644 src/plugins/geoservices/mapbox/qgeomapreplymapbox.cpp create mode 100644 src/plugins/geoservices/mapbox/qgeomapreplymapbox.h create mode 100644 src/plugins/geoservices/mapbox/qgeoserviceproviderpluginmapbox.cpp create mode 100644 src/plugins/geoservices/mapbox/qgeoserviceproviderpluginmapbox.h create mode 100644 src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.cpp create mode 100644 src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.h create mode 100644 src/plugins/geoservices/mapbox/qgeotilefetchermapbox.cpp create mode 100644 src/plugins/geoservices/mapbox/qgeotilefetchermapbox.h create mode 100644 src/plugins/geoservices/nokia/logo.png create mode 100644 src/plugins/geoservices/nokia/marclanguagecodes.h create mode 100644 src/plugins/geoservices/nokia/nokia.pro create mode 100644 src/plugins/geoservices/nokia/nokia_plugin.json create mode 100644 src/plugins/geoservices/nokia/placesv2/jsonparserhelpers.cpp create mode 100644 src/plugins/geoservices/nokia/placesv2/jsonparserhelpers.h create mode 100644 src/plugins/geoservices/nokia/placesv2/placesv2.pri create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacecategoriesreplyhere.cpp create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacecategoriesreplyhere.h create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacecontentreplyimpl.cpp create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacecontentreplyimpl.h create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacedetailsreplyimpl.cpp create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacedetailsreplyimpl.h create mode 100644 src/plugins/geoservices/nokia/placesv2/qplaceidreplyimpl.cpp create mode 100644 src/plugins/geoservices/nokia/placesv2/qplaceidreplyimpl.h create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacesearchreplyhere.cpp create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacesearchreplyhere.h create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacesearchsuggestionreplyimpl.cpp create mode 100644 src/plugins/geoservices/nokia/placesv2/qplacesearchsuggestionreplyimpl.h create mode 100644 src/plugins/geoservices/nokia/qgeocodereply_nokia.cpp create mode 100644 src/plugins/geoservices/nokia/qgeocodereply_nokia.h create mode 100644 src/plugins/geoservices/nokia/qgeocodexmlparser.cpp create mode 100644 src/plugins/geoservices/nokia/qgeocodexmlparser.h create mode 100644 src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.cpp create mode 100644 src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.h create mode 100644 src/plugins/geoservices/nokia/qgeoerror_messages.cpp create mode 100644 src/plugins/geoservices/nokia/qgeoerror_messages.h create mode 100644 src/plugins/geoservices/nokia/qgeointrinsicnetworkaccessmanager.cpp create mode 100644 src/plugins/geoservices/nokia/qgeointrinsicnetworkaccessmanager.h create mode 100644 src/plugins/geoservices/nokia/qgeomapreply_nokia.cpp create mode 100644 src/plugins/geoservices/nokia/qgeomapreply_nokia.h create mode 100644 src/plugins/geoservices/nokia/qgeomapversion.cpp create mode 100644 src/plugins/geoservices/nokia/qgeomapversion.h create mode 100644 src/plugins/geoservices/nokia/qgeonetworkaccessmanager.h create mode 100644 src/plugins/geoservices/nokia/qgeoroutereply_nokia.cpp create mode 100644 src/plugins/geoservices/nokia/qgeoroutereply_nokia.h create mode 100644 src/plugins/geoservices/nokia/qgeoroutexmlparser.cpp create mode 100644 src/plugins/geoservices/nokia/qgeoroutexmlparser.h create mode 100644 src/plugins/geoservices/nokia/qgeoroutingmanagerengine_nokia.cpp create mode 100644 src/plugins/geoservices/nokia/qgeoroutingmanagerengine_nokia.h create mode 100644 src/plugins/geoservices/nokia/qgeoserviceproviderplugin_nokia.cpp create mode 100644 src/plugins/geoservices/nokia/qgeoserviceproviderplugin_nokia.h create mode 100644 src/plugins/geoservices/nokia/qgeotiledmap_nokia.cpp create mode 100644 src/plugins/geoservices/nokia/qgeotiledmap_nokia.h create mode 100644 src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.cpp create mode 100644 src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.h create mode 100644 src/plugins/geoservices/nokia/qgeotilefetcher_nokia.cpp create mode 100644 src/plugins/geoservices/nokia/qgeotilefetcher_nokia.h create mode 100644 src/plugins/geoservices/nokia/qgeouriprovider.cpp create mode 100644 src/plugins/geoservices/nokia/qgeouriprovider.h create mode 100644 src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.cpp create mode 100644 src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.h create mode 100644 src/plugins/geoservices/nokia/resource.qrc create mode 100644 src/plugins/geoservices/nokia/uri_constants.cpp create mode 100644 src/plugins/geoservices/nokia/uri_constants.h create mode 100644 src/plugins/geoservices/osm/osm.pro create mode 100644 src/plugins/geoservices/osm/osm_plugin.json create mode 100644 src/plugins/geoservices/osm/qgeocodereplyosm.cpp create mode 100644 src/plugins/geoservices/osm/qgeocodereplyosm.h create mode 100644 src/plugins/geoservices/osm/qgeocodingmanagerengineosm.cpp create mode 100644 src/plugins/geoservices/osm/qgeocodingmanagerengineosm.h create mode 100644 src/plugins/geoservices/osm/qgeomapreplyosm.cpp create mode 100644 src/plugins/geoservices/osm/qgeomapreplyosm.h create mode 100644 src/plugins/geoservices/osm/qgeoroutereplyosm.cpp create mode 100644 src/plugins/geoservices/osm/qgeoroutereplyosm.h create mode 100644 src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.cpp create mode 100644 src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.h create mode 100644 src/plugins/geoservices/osm/qgeoserviceproviderpluginosm.cpp create mode 100644 src/plugins/geoservices/osm/qgeoserviceproviderpluginosm.h create mode 100644 src/plugins/geoservices/osm/qgeotiledmaposm.cpp create mode 100644 src/plugins/geoservices/osm/qgeotiledmaposm.h create mode 100644 src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp create mode 100644 src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.h create mode 100644 src/plugins/geoservices/osm/qgeotilefetcherosm.cpp create mode 100644 src/plugins/geoservices/osm/qgeotilefetcherosm.h create mode 100644 src/plugins/geoservices/osm/qgeotileproviderosm.cpp create mode 100644 src/plugins/geoservices/osm/qgeotileproviderosm.h create mode 100644 src/plugins/geoservices/osm/qplacecategoriesreplyosm.cpp create mode 100644 src/plugins/geoservices/osm/qplacecategoriesreplyosm.h create mode 100644 src/plugins/geoservices/osm/qplacemanagerengineosm.cpp create mode 100644 src/plugins/geoservices/osm/qplacemanagerengineosm.h create mode 100644 src/plugins/geoservices/osm/qplacesearchreplyosm.cpp create mode 100644 src/plugins/geoservices/osm/qplacesearchreplyosm.h create mode 100644 src/plugins/plugins.pro create mode 100644 src/plugins/position/android/android.pro create mode 100644 src/plugins/position/android/jar/AndroidManifest.xml create mode 100644 src/plugins/position/android/jar/bundledjar.pro create mode 100644 src/plugins/position/android/jar/distributedjar.pro create mode 100644 src/plugins/position/android/jar/jar.pri create mode 100644 src/plugins/position/android/jar/jar.pro create mode 100644 src/plugins/position/android/jar/src/org/qtproject/qt5/android/positioning/QtPositioning.java create mode 100644 src/plugins/position/android/src/jnipositioning.cpp create mode 100644 src/plugins/position/android/src/jnipositioning.h create mode 100644 src/plugins/position/android/src/plugin.json create mode 100644 src/plugins/position/android/src/positionfactory_android.cpp create mode 100644 src/plugins/position/android/src/positionfactory_android.h create mode 100644 src/plugins/position/android/src/qgeopositioninfosource_android.cpp create mode 100644 src/plugins/position/android/src/qgeopositioninfosource_android_p.h create mode 100644 src/plugins/position/android/src/qgeosatelliteinfosource_android.cpp create mode 100644 src/plugins/position/android/src/qgeosatelliteinfosource_android_p.h create mode 100644 src/plugins/position/android/src/src.pro create mode 100644 src/plugins/position/corelocation/corelocation.pro create mode 100644 src/plugins/position/corelocation/plugin.json create mode 100644 src/plugins/position/corelocation/qgeopositioninfosource_cl.mm create mode 100644 src/plugins/position/corelocation/qgeopositioninfosource_cl_p.h create mode 100644 src/plugins/position/corelocation/qgeopositioninfosourcefactory_cl.h create mode 100644 src/plugins/position/corelocation/qgeopositioninfosourcefactory_cl.mm create mode 100644 src/plugins/position/geoclue/geoclue.pro create mode 100644 src/plugins/position/geoclue/geocluetypes.cpp create mode 100644 src/plugins/position/geoclue/geocluetypes.h create mode 100644 src/plugins/position/geoclue/org.freedesktop.Geoclue.Master.xml create mode 100644 src/plugins/position/geoclue/org.freedesktop.Geoclue.MasterClient.xml create mode 100644 src/plugins/position/geoclue/org.freedesktop.Geoclue.Position.xml create mode 100644 src/plugins/position/geoclue/org.freedesktop.Geoclue.Satellite.xml create mode 100644 src/plugins/position/geoclue/org.freedesktop.Geoclue.Velocity.xml create mode 100644 src/plugins/position/geoclue/org.freedesktop.Geoclue.xml create mode 100644 src/plugins/position/geoclue/plugin.json create mode 100644 src/plugins/position/geoclue/qgeocluemaster.cpp create mode 100644 src/plugins/position/geoclue/qgeocluemaster.h create mode 100644 src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp create mode 100644 src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.h create mode 100644 src/plugins/position/geoclue/qgeopositioninfosourcefactory_geoclue.cpp create mode 100644 src/plugins/position/geoclue/qgeopositioninfosourcefactory_geoclue.h create mode 100644 src/plugins/position/geoclue/qgeosatelliteinfosource_geocluemaster.cpp create mode 100644 src/plugins/position/geoclue/qgeosatelliteinfosource_geocluemaster.h create mode 100644 src/plugins/position/gypsy/gypsy.pro create mode 100644 src/plugins/position/gypsy/plugin.json create mode 100644 src/plugins/position/gypsy/qgeopositioninfosourcefactory_gypsy.cpp create mode 100644 src/plugins/position/gypsy/qgeopositioninfosourcefactory_gypsy.h create mode 100644 src/plugins/position/gypsy/qgeosatelliteinfosource_gypsy.cpp create mode 100644 src/plugins/position/gypsy/qgeosatelliteinfosource_gypsy_p.h create mode 100644 src/plugins/position/position.pro create mode 100644 src/plugins/position/positionpoll/plugin.json create mode 100644 src/plugins/position/positionpoll/positionpoll.pro create mode 100644 src/plugins/position/positionpoll/positionpollfactory.cpp create mode 100644 src/plugins/position/positionpoll/positionpollfactory.h create mode 100644 src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp create mode 100644 src/plugins/position/positionpoll/qgeoareamonitor_polling.h create mode 100644 src/plugins/position/serialnmea/plugin.json create mode 100644 src/plugins/position/serialnmea/qgeopositioninfosourcefactory_serialnmea.cpp create mode 100644 src/plugins/position/serialnmea/qgeopositioninfosourcefactory_serialnmea.h create mode 100644 src/plugins/position/serialnmea/serialnmea.pro create mode 100644 src/plugins/position/simulator/plugin.json create mode 100644 src/plugins/position/simulator/qgeopositioninfosource_simulator.cpp create mode 100644 src/plugins/position/simulator/qgeopositioninfosource_simulator_p.h create mode 100644 src/plugins/position/simulator/qgeopositioninfosourcefactory_simulator.cpp create mode 100644 src/plugins/position/simulator/qgeopositioninfosourcefactory_simulator.h create mode 100644 src/plugins/position/simulator/qgeosatelliteinfosource_simulator.cpp create mode 100644 src/plugins/position/simulator/qgeosatelliteinfosource_simulator_p.h create mode 100644 src/plugins/position/simulator/qlocationconnection_simulator.cpp create mode 100644 src/plugins/position/simulator/qlocationconnection_simulator_p.h create mode 100644 src/plugins/position/simulator/simulator.pro create mode 100644 src/plugins/position/winrt/plugin.json create mode 100644 src/plugins/position/winrt/qgeopositioninfosource_winrt.cpp create mode 100644 src/plugins/position/winrt/qgeopositioninfosource_winrt_p.h create mode 100644 src/plugins/position/winrt/qgeopositioninfosourcefactory_winrt.cpp create mode 100644 src/plugins/position/winrt/qgeopositioninfosourcefactory_winrt.h create mode 100644 src/plugins/position/winrt/winrt.pro create mode 100644 src/positioning/doc/qtpositioning.qdocconf create mode 100644 src/positioning/doc/snippets/cpp/cpp.pro create mode 100644 src/positioning/doc/snippets/cpp/cppqml.cpp create mode 100644 src/positioning/doc/snippets/cpp/main.cpp create mode 100644 src/positioning/doc/snippets/doc_src_qtpositioning.qml create mode 100644 src/positioning/doc/snippets/snippets.pro create mode 100644 src/positioning/doc/src/cpp-position.qdoc create mode 100644 src/positioning/doc/src/cpp-qml-positioning.qdoc create mode 100644 src/positioning/doc/src/qml-position.qdoc create mode 100644 src/positioning/doc/src/qtpositioning-examples.qdoc create mode 100644 src/positioning/doc/src/qtpositioning-plugins.qdoc create mode 100644 src/positioning/doc/src/qtpositioning-qml.qdoc create mode 100644 src/positioning/doc/src/qtpositioning.qdoc create mode 100644 src/positioning/positioning.pro create mode 100644 src/positioning/qdeclarativegeoaddress.cpp create mode 100644 src/positioning/qdeclarativegeoaddress_p.h create mode 100644 src/positioning/qdeclarativegeolocation.cpp create mode 100644 src/positioning/qdeclarativegeolocation_p.h create mode 100644 src/positioning/qdoublevector2d.cpp create mode 100644 src/positioning/qdoublevector2d_p.h create mode 100644 src/positioning/qdoublevector3d.cpp create mode 100644 src/positioning/qdoublevector3d_p.h create mode 100644 src/positioning/qgeoaddress.cpp create mode 100644 src/positioning/qgeoaddress.h create mode 100644 src/positioning/qgeoaddress_p.h create mode 100644 src/positioning/qgeoareamonitorinfo.cpp create mode 100644 src/positioning/qgeoareamonitorinfo.h create mode 100644 src/positioning/qgeoareamonitorsource.cpp create mode 100644 src/positioning/qgeoareamonitorsource.h create mode 100644 src/positioning/qgeocircle.cpp create mode 100644 src/positioning/qgeocircle.h create mode 100644 src/positioning/qgeocircle_p.h create mode 100644 src/positioning/qgeocoordinate.cpp create mode 100644 src/positioning/qgeocoordinate.h create mode 100644 src/positioning/qgeocoordinate_p.h create mode 100644 src/positioning/qgeolocation.cpp create mode 100644 src/positioning/qgeolocation.h create mode 100644 src/positioning/qgeolocation_p.h create mode 100644 src/positioning/qgeopositioninfo.cpp create mode 100644 src/positioning/qgeopositioninfo.h create mode 100644 src/positioning/qgeopositioninfosource.cpp create mode 100644 src/positioning/qgeopositioninfosource.h create mode 100644 src/positioning/qgeopositioninfosource_p.h create mode 100644 src/positioning/qgeopositioninfosourcefactory.cpp create mode 100644 src/positioning/qgeopositioninfosourcefactory.h create mode 100644 src/positioning/qgeoprojection.cpp create mode 100644 src/positioning/qgeoprojection_p.h create mode 100644 src/positioning/qgeorectangle.cpp create mode 100644 src/positioning/qgeorectangle.h create mode 100644 src/positioning/qgeorectangle_p.h create mode 100644 src/positioning/qgeosatelliteinfo.cpp create mode 100644 src/positioning/qgeosatelliteinfo.h create mode 100644 src/positioning/qgeosatelliteinfosource.cpp create mode 100644 src/positioning/qgeosatelliteinfosource.h create mode 100644 src/positioning/qgeoshape.cpp create mode 100644 src/positioning/qgeoshape.h create mode 100644 src/positioning/qgeoshape_p.h create mode 100644 src/positioning/qlocationdata_simulator.cpp create mode 100644 src/positioning/qlocationdata_simulator_p.h create mode 100644 src/positioning/qlocationutils.cpp create mode 100644 src/positioning/qlocationutils_p.h create mode 100644 src/positioning/qnmeapositioninfosource.cpp create mode 100644 src/positioning/qnmeapositioninfosource.h create mode 100644 src/positioning/qnmeapositioninfosource_p.h create mode 100644 src/positioning/qpositioningglobal.h create mode 100644 src/positioning/qpositioningglobal_p.h create mode 100644 src/src.pro create mode 100644 sync.profile create mode 100644 tests/applications/positioning_backend/main.cpp create mode 100644 tests/applications/positioning_backend/positioning_backend.pro create mode 100644 tests/applications/positioning_backend/widget.cpp create mode 100644 tests/applications/positioning_backend/widget.h create mode 100644 tests/applications/positioning_backend/widget.ui create mode 100644 tests/auto/auto.pro create mode 100644 tests/auto/bic/data/QtPositioning.5.3.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtPositioning.5.4.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtPositioning.5.6.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtPositioning.5.7.0.linux-gcc-amd64.txt create mode 100644 tests/auto/cmake/CMakeLists.txt create mode 100644 tests/auto/cmake/cmake.pro create mode 100644 tests/auto/declarative_core/declarative_core.pro create mode 100644 tests/auto/declarative_core/main.cpp create mode 100644 tests/auto/declarative_core/tst_address.qml create mode 100644 tests/auto/declarative_core/tst_category.qml create mode 100644 tests/auto/declarative_core/tst_categorymodel.qml create mode 100644 tests/auto/declarative_core/tst_contactdetail.qml create mode 100644 tests/auto/declarative_core/tst_coordinate.qml create mode 100644 tests/auto/declarative_core/tst_editorialmodel.qml create mode 100644 tests/auto/declarative_core/tst_geocoding.qml create mode 100644 tests/auto/declarative_core/tst_imagemodel.qml create mode 100644 tests/auto/declarative_core/tst_place.qml create mode 100644 tests/auto/declarative_core/tst_placeattribute.qml create mode 100644 tests/auto/declarative_core/tst_placeicon.qml create mode 100644 tests/auto/declarative_core/tst_placesearchmodel.qml create mode 100644 tests/auto/declarative_core/tst_placesearchsuggestionmodel.qml create mode 100644 tests/auto/declarative_core/tst_plugin.qml create mode 100644 tests/auto/declarative_core/tst_plugin_error.qml create mode 100644 tests/auto/declarative_core/tst_position.qml create mode 100644 tests/auto/declarative_core/tst_positionsource.qml create mode 100644 tests/auto/declarative_core/tst_ratings.qml create mode 100644 tests/auto/declarative_core/tst_reviewmodel.qml create mode 100644 tests/auto/declarative_core/tst_routing.qml create mode 100644 tests/auto/declarative_core/tst_supplier.qml create mode 100644 tests/auto/declarative_core/tst_user.qml create mode 100644 tests/auto/declarative_core/utils.js create mode 100644 tests/auto/declarative_geoshape/declarative_geoshape.pro create mode 100644 tests/auto/declarative_geoshape/main.cpp create mode 100644 tests/auto/declarative_geoshape/tst_locationsingleton.qml create mode 100644 tests/auto/declarative_ui/declarative_ui.pro create mode 100644 tests/auto/declarative_ui/main.cpp create mode 100644 tests/auto/declarative_ui/tst_map.qml create mode 100644 tests/auto/declarative_ui/tst_map_coordinateanimation.qml create mode 100644 tests/auto/declarative_ui/tst_map_error.qml create mode 100644 tests/auto/declarative_ui/tst_map_flick.qml create mode 100644 tests/auto/declarative_ui/tst_map_item.qml create mode 100644 tests/auto/declarative_ui/tst_map_item_details.qml create mode 100644 tests/auto/declarative_ui/tst_map_item_fit_viewport.qml create mode 100644 tests/auto/declarative_ui/tst_map_itemview.qml create mode 100644 tests/auto/declarative_ui/tst_map_keepgrab.qml create mode 100644 tests/auto/declarative_ui/tst_map_maptype.qml create mode 100644 tests/auto/declarative_ui/tst_map_mouse.qml create mode 100644 tests/auto/declarative_ui/tst_map_pinch.qml.QTBUG-47970 create mode 100644 tests/auto/doublevectors/doublevectors.pro create mode 100644 tests/auto/doublevectors/tst_doublevectors.cpp create mode 100644 tests/auto/geotestplugin/geotestplugin.json create mode 100644 tests/auto/geotestplugin/geotestplugin.pro create mode 100644 tests/auto/geotestplugin/place_data.json create mode 100644 tests/auto/geotestplugin/qgeocodingmanagerengine_test.h create mode 100644 tests/auto/geotestplugin/qgeomappingmanagerengine_test.h create mode 100644 tests/auto/geotestplugin/qgeoroutingmanagerengine_test.h create mode 100644 tests/auto/geotestplugin/qgeoserviceproviderplugin_test.cpp create mode 100644 tests/auto/geotestplugin/qgeoserviceproviderplugin_test.h create mode 100644 tests/auto/geotestplugin/qgeotiledmap_test.h create mode 100644 tests/auto/geotestplugin/qgeotiledmappingmanagerengine_test.h create mode 100644 tests/auto/geotestplugin/qgeotilefetcher_test.h create mode 100644 tests/auto/geotestplugin/qplacemanagerengine_test.h create mode 100644 tests/auto/geotestplugin/testdata.qrc create mode 100644 tests/auto/maptype/maptype.pro create mode 100644 tests/auto/maptype/tst_maptype.cpp create mode 100644 tests/auto/nokia_services/nokia_services.pro create mode 100644 tests/auto/nokia_services/places_semiauto/places_semiauto.pro create mode 100644 tests/auto/nokia_services/places_semiauto/tst_places.cpp create mode 100644 tests/auto/nokia_services/routing/error-no-route.xml create mode 100644 tests/auto/nokia_services/routing/invalid-response-half-way-through.xml create mode 100644 tests/auto/nokia_services/routing/invalid-response-no-calculateroute-tag.xml create mode 100644 tests/auto/nokia_services/routing/invalid-response-no-route-tag.xml create mode 100644 tests/auto/nokia_services/routing/invalid-response-trash.xml create mode 100644 tests/auto/nokia_services/routing/littered-with-new-tags.xml create mode 100644 tests/auto/nokia_services/routing/multiple-routes-in-response.xml create mode 100644 tests/auto/nokia_services/routing/optim-fastest.xml create mode 100644 tests/auto/nokia_services/routing/optim-shortest.xml create mode 100644 tests/auto/nokia_services/routing/routing.pro create mode 100644 tests/auto/nokia_services/routing/travelmode-car.xml create mode 100644 tests/auto/nokia_services/routing/travelmode-pedestrian.xml create mode 100644 tests/auto/nokia_services/routing/travelmode-public-transport.xml create mode 100644 tests/auto/nokia_services/routing/tst_routing.cpp create mode 100644 tests/auto/placemanager_utils/placemanager_utils.cpp create mode 100644 tests/auto/placemanager_utils/placemanager_utils.h create mode 100644 tests/auto/placesplugin_unsupported/placesplugin.json create mode 100644 tests/auto/placesplugin_unsupported/placesplugin_unsupported.pro create mode 100644 tests/auto/placesplugin_unsupported/qgeoserviceproviderplugin_test.cpp create mode 100644 tests/auto/placesplugin_unsupported/qgeoserviceproviderplugin_test.h create mode 100644 tests/auto/positionplugin/plugin.cpp create mode 100644 tests/auto/positionplugin/plugin.json create mode 100644 tests/auto/positionplugin/positionplugin.pro create mode 100644 tests/auto/positionplugintest/positionplugintest.pro create mode 100644 tests/auto/positionplugintest/tst_positionplugin.cpp create mode 100644 tests/auto/qgeoaddress/qgeoaddress.pro create mode 100644 tests/auto/qgeoaddress/tst_qgeoaddress.cpp create mode 100644 tests/auto/qgeoareamonitor/logfilepositionsource.cpp create mode 100644 tests/auto/qgeoareamonitor/logfilepositionsource.h create mode 100644 tests/auto/qgeoareamonitor/qgeoareamonitor.pro create mode 100644 tests/auto/qgeoareamonitor/simplelog.txt create mode 100644 tests/auto/qgeoareamonitor/tst_qgeoareamonitor.cpp create mode 100644 tests/auto/qgeocameracapabilities/qgeocameracapabilities.pro create mode 100644 tests/auto/qgeocameracapabilities/tst_qgeocameracapabilities.cpp create mode 100644 tests/auto/qgeocameradata/qgeocameradata.pro create mode 100644 tests/auto/qgeocameradata/tst_qgeocameradata.cpp create mode 100644 tests/auto/qgeocameratiles/qgeocameratiles.pro create mode 100644 tests/auto/qgeocameratiles/tst_qgeocameratiles.cpp create mode 100644 tests/auto/qgeocircle/qgeocircle.pro create mode 100644 tests/auto/qgeocircle/tst_qgeocircle.cpp create mode 100644 tests/auto/qgeocodereply/qgeocodereply.pro create mode 100644 tests/auto/qgeocodereply/tst_qgeocodereply.cpp create mode 100644 tests/auto/qgeocodereply/tst_qgeocodereply.h create mode 100644 tests/auto/qgeocodingmanager/qgeocodingmanager.pro create mode 100644 tests/auto/qgeocodingmanager/tst_qgeocodingmanager.cpp create mode 100644 tests/auto/qgeocodingmanager/tst_qgeocodingmanager.h create mode 100644 tests/auto/qgeocodingmanagerplugins/geocoding_plugin.json create mode 100644 tests/auto/qgeocodingmanagerplugins/qgeocodingmanagerengine_test.h create mode 100644 tests/auto/qgeocodingmanagerplugins/qgeocodingmanagerplugins.pro create mode 100644 tests/auto/qgeocodingmanagerplugins/qgeoserviceproviderplugin_test.cpp create mode 100644 tests/auto/qgeocodingmanagerplugins/qgeoserviceproviderplugin_test.h create mode 100644 tests/auto/qgeocoordinate/qgeocoordinate.pro create mode 100644 tests/auto/qgeocoordinate/tst_qgeocoordinate.cpp create mode 100644 tests/auto/qgeolocation/qgeolocation.pro create mode 100644 tests/auto/qgeolocation/tst_qgeolocation.cpp create mode 100644 tests/auto/qgeolocation/tst_qgeolocation.h create mode 100644 tests/auto/qgeomaneuver/qgeomaneuver.pro create mode 100644 tests/auto/qgeomaneuver/tst_qgeomaneuver.cpp create mode 100644 tests/auto/qgeomaneuver/tst_qgeomaneuver.h create mode 100644 tests/auto/qgeopositioninfo/qgeopositioninfo.pro create mode 100644 tests/auto/qgeopositioninfo/tst_qgeopositioninfo.cpp create mode 100644 tests/auto/qgeopositioninfosource/qgeopositioninfosource.pro create mode 100644 tests/auto/qgeopositioninfosource/testqgeopositioninfosource.cpp create mode 100644 tests/auto/qgeopositioninfosource/testqgeopositioninfosource_p.h create mode 100644 tests/auto/qgeopositioninfosource/tst_qgeopositioninfosource.cpp create mode 100644 tests/auto/qgeorectangle/qgeorectangle.pro create mode 100644 tests/auto/qgeorectangle/tst_qgeorectangle.cpp create mode 100644 tests/auto/qgeoroute/qgeoroute.pro create mode 100644 tests/auto/qgeoroute/tst_qgeoroute.cpp create mode 100644 tests/auto/qgeoroute/tst_qgeoroute.h create mode 100644 tests/auto/qgeoroutereply/qgeoroutereply.pro create mode 100644 tests/auto/qgeoroutereply/tst_qgeoroutereply.cpp create mode 100644 tests/auto/qgeoroutereply/tst_qgeoroutereply.h create mode 100644 tests/auto/qgeorouterequest/qgeorouterequest.pro create mode 100644 tests/auto/qgeorouterequest/tst_qgeorouterequest.cpp create mode 100644 tests/auto/qgeorouterequest/tst_qgeorouterequest.h create mode 100644 tests/auto/qgeoroutesegment/qgeoroutesegment.pro create mode 100644 tests/auto/qgeoroutesegment/tst_qgeoroutesegment.cpp create mode 100644 tests/auto/qgeoroutesegment/tst_qgeoroutesegment.h create mode 100644 tests/auto/qgeoroutexmlparser/fixtures.qrc create mode 100644 tests/auto/qgeoroutexmlparser/qgeoroutexmlparser.pro create mode 100644 tests/auto/qgeoroutexmlparser/route1.xml create mode 100644 tests/auto/qgeoroutexmlparser/route2.xml create mode 100644 tests/auto/qgeoroutexmlparser/tst_qgeoroutexmlparser.cpp create mode 100644 tests/auto/qgeoroutingmanager/qgeoroutingmanager.pro create mode 100644 tests/auto/qgeoroutingmanager/tst_qgeoroutingmanager.cpp create mode 100644 tests/auto/qgeoroutingmanager/tst_qgeoroutingmanager.h create mode 100644 tests/auto/qgeoroutingmanagerplugins/qgeoroutingmanagerengine_test.h create mode 100644 tests/auto/qgeoroutingmanagerplugins/qgeoroutingmanagerplugins.pro create mode 100644 tests/auto/qgeoroutingmanagerplugins/qgeoserviceproviderplugin_test.cpp create mode 100644 tests/auto/qgeoroutingmanagerplugins/qgeoserviceproviderplugin_test.h create mode 100644 tests/auto/qgeoroutingmanagerplugins/routing_plugin.json create mode 100644 tests/auto/qgeosatelliteinfo/qgeosatelliteinfo.pro create mode 100644 tests/auto/qgeosatelliteinfo/tst_qgeosatelliteinfo.cpp create mode 100644 tests/auto/qgeosatelliteinfosource/qgeosatelliteinfosource.pro create mode 100644 tests/auto/qgeosatelliteinfosource/testqgeosatelliteinfosource.cpp create mode 100644 tests/auto/qgeosatelliteinfosource/testqgeosatelliteinfosource_p.h create mode 100644 tests/auto/qgeosatelliteinfosource/tst_qgeosatelliteinfosource.cpp create mode 100644 tests/auto/qgeoserviceprovider/qgeoserviceprovider.pro create mode 100644 tests/auto/qgeoserviceprovider/tst_qgeoserviceprovider.cpp create mode 100644 tests/auto/qgeoshape/qgeoshape.pro create mode 100644 tests/auto/qgeoshape/tst_qgeoshape.cpp create mode 100644 tests/auto/qgeotiledmap/qgeotiledmap.pro create mode 100644 tests/auto/qgeotiledmap/tst_qgeotiledmap.cpp create mode 100644 tests/auto/qgeotiledmapscene/qgeotiledmapscene.pro create mode 100644 tests/auto/qgeotiledmapscene/tst_qgeotiledmapscene.cpp create mode 100644 tests/auto/qgeotilespec/qgeotilespec.pro create mode 100644 tests/auto/qgeotilespec/tst_qgeotilespec.cpp create mode 100644 tests/auto/qmlinterface/data/TestAddress.qml create mode 100644 tests/auto/qmlinterface/data/TestCategory.qml create mode 100644 tests/auto/qmlinterface/data/TestContactDetail.qml create mode 100644 tests/auto/qmlinterface/data/TestIcon.qml create mode 100644 tests/auto/qmlinterface/data/TestLocation.qml create mode 100644 tests/auto/qmlinterface/data/TestPlace.qml create mode 100644 tests/auto/qmlinterface/data/TestPlaceAttribute.qml create mode 100644 tests/auto/qmlinterface/data/TestRatings.qml create mode 100644 tests/auto/qmlinterface/data/TestSupplier.qml create mode 100644 tests/auto/qmlinterface/data/TestUser.qml create mode 100644 tests/auto/qmlinterface/qmlinterface.pro create mode 100644 tests/auto/qmlinterface/tst_qmlinterface.cpp create mode 100644 tests/auto/qnmeapositioninfosource/dummynmeapositioninfosource/dummynmeapositioninfosource.pro create mode 100644 tests/auto/qnmeapositioninfosource/dummynmeapositioninfosource/tst_dummynmeapositioninfosource.cpp create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosource.pro create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime/qnmeapositioninfosource_realtime.pro create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime/tst_qnmeapositioninfosource_realtime.cpp create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime_generic/qnmeapositioninfosource_realtime_generic.pro create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime_generic/tst_qnmeapositioninfosource_realtime_generic.cpp create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation/qnmeapositioninfosource_simulation.pro create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation/tst_qnmeapositioninfosource_simulation.cpp create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation_generic/qnmeapositioninfosource_simulation_generic.pro create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation_generic/tst_qnmeapositioninfosource_simulation_generic.cpp create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosourceproxyfactory.cpp create mode 100644 tests/auto/qnmeapositioninfosource/qnmeapositioninfosourceproxyfactory.h create mode 100644 tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.cpp create mode 100644 tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.h create mode 100644 tests/auto/qplace/qplace.pro create mode 100644 tests/auto/qplace/tst_qplace.cpp create mode 100644 tests/auto/qplaceattribute/qplaceattribute.pro create mode 100644 tests/auto/qplaceattribute/tst_qplaceattribute.cpp create mode 100644 tests/auto/qplacecategory/qplacecategory.pro create mode 100644 tests/auto/qplacecategory/tst_qplacecategory.cpp create mode 100644 tests/auto/qplacecontactdetail/qplacecontactdetail.pro create mode 100644 tests/auto/qplacecontactdetail/tst_qplacecontactdetail.cpp create mode 100644 tests/auto/qplacecontentrequest/qplacecontentrequest.pro create mode 100644 tests/auto/qplacecontentrequest/tst_qplacecontentrequest.cpp create mode 100644 tests/auto/qplacedetailsreply/qplacedetailsreply.pro create mode 100644 tests/auto/qplacedetailsreply/tst_qplacedetailsreply.cpp create mode 100644 tests/auto/qplaceeditorial/qplaceeditorial.pro create mode 100644 tests/auto/qplaceeditorial/tst_qplaceeditorial.cpp create mode 100644 tests/auto/qplaceimage/qplaceimage.pro create mode 100644 tests/auto/qplaceimage/tst_qplaceimage.cpp create mode 100644 tests/auto/qplacemanager/qplacemanager.pro create mode 100644 tests/auto/qplacemanager/tst_qplacemanager.cpp create mode 100644 tests/auto/qplacemanager_nokia/qplacemanager_nokia.pro create mode 100644 tests/auto/qplacemanager_nokia/tst_qplacemanager_nokia.cpp create mode 100644 tests/auto/qplacemanager_unsupported/qplacemanager_unsupported.pro create mode 100644 tests/auto/qplacemanager_unsupported/tst_qplacemanager_unsupported.cpp create mode 100644 tests/auto/qplacematchreply/qplacematchreply.pro create mode 100644 tests/auto/qplacematchreply/tst_qplacematchreply.cpp create mode 100644 tests/auto/qplacematchrequest/qplacematchrequest.pro create mode 100644 tests/auto/qplacematchrequest/tst_qplacematchrequest.cpp create mode 100644 tests/auto/qplaceperiod/qplaceperiod.pro create mode 100644 tests/auto/qplaceperiod/tst_qplaceperiod.cpp create mode 100644 tests/auto/qplaceratings/qplaceratings.pro create mode 100644 tests/auto/qplaceratings/tst_qplaceratings.cpp create mode 100644 tests/auto/qplacereply/qplacereply.pro create mode 100644 tests/auto/qplacereply/tst_qplacereply.cpp create mode 100644 tests/auto/qplaceresult/qplaceresult.pro create mode 100644 tests/auto/qplaceresult/tst_qplaceresult.cpp create mode 100644 tests/auto/qplacereview/qplacereview.pro create mode 100644 tests/auto/qplacereview/tst_qplacereview.cpp create mode 100644 tests/auto/qplacesearchreply/qplacesearchreply.pro create mode 100644 tests/auto/qplacesearchreply/tst_qplacesearchreply.cpp create mode 100644 tests/auto/qplacesearchrequest/qplacesearchrequest.pro create mode 100644 tests/auto/qplacesearchrequest/tst_qplacesearchrequest.cpp create mode 100644 tests/auto/qplacesearchresult/qplacesearchresult.pro create mode 100644 tests/auto/qplacesearchresult/tst_qplacesearchresult.cpp create mode 100644 tests/auto/qplacesearchsuggestionreply/qplacesearchsuggestionreply.pro create mode 100644 tests/auto/qplacesearchsuggestionreply/tst_qplacesearchsuggestionreply.cpp create mode 100644 tests/auto/qplacesupplier/qplacesupplier.pro create mode 100644 tests/auto/qplacesupplier/tst_qplacesupplier.cpp create mode 100644 tests/auto/qplaceuser/qplaceuser.pro create mode 100644 tests/auto/qplaceuser/tst_qplaceuser.cpp create mode 100644 tests/auto/qproposedsearchresult/qproposedsearchresult.pro create mode 100644 tests/auto/qproposedsearchresult/tst_qproposedsearchresult.cpp create mode 100644 tests/auto/utils/qlocationtestutils.cpp create mode 100644 tests/auto/utils/qlocationtestutils_p.h create mode 100644 tests/global/global.cfg create mode 100644 tests/plugins/declarativetestplugin/declarativetestplugin.pro create mode 100644 tests/plugins/declarativetestplugin/locationtest.cpp create mode 100644 tests/plugins/declarativetestplugin/qdeclarativelocationtestmodel.cpp create mode 100644 tests/plugins/declarativetestplugin/qdeclarativelocationtestmodel_p.h create mode 100644 tests/plugins/declarativetestplugin/qdeclarativepinchgenerator.cpp create mode 100644 tests/plugins/declarativetestplugin/qdeclarativepinchgenerator_p.h create mode 100644 tests/plugins/declarativetestplugin/qmldir create mode 100644 tests/plugins/declarativetestplugin/testhelper.h create mode 100644 tests/plugins/imports.pri create mode 100644 tests/tests.pro diff --git a/.qmake.conf b/.qmake.conf new file mode 100644 index 0000000..a2a0d41 --- /dev/null +++ b/.qmake.conf @@ -0,0 +1,3 @@ +load(qt_build_config) + +MODULE_VERSION = 5.7.1 diff --git a/.tag b/.tag new file mode 100644 index 0000000..8294ae9 --- /dev/null +++ b/.tag @@ -0,0 +1 @@ +de5be121d84b062f1b72c91dddc877e5e88babc0 diff --git a/LGPL_EXCEPTION.txt b/LGPL_EXCEPTION.txt new file mode 100644 index 0000000..5cdacb9 --- /dev/null +++ b/LGPL_EXCEPTION.txt @@ -0,0 +1,22 @@ +The Qt Company Qt LGPL Exception version 1.1 + +As an additional permission to the GNU Lesser General Public License version +2.1, the object code form of a "work that uses the Library" may incorporate +material from a header file that is part of the Library. You may distribute +such object code under terms of your choice, provided that: + (i) the header files of the Library have not been modified; and + (ii) the incorporated material is limited to numerical parameters, data + structure layouts, accessors, macros, inline functions and + templates; and + (iii) you comply with the terms of Section 6 of the GNU Lesser General + Public License version 2.1. + +Moreover, you may apply this exception to a modified version of the Library, +provided that such modification does not involve copying material from the +Library into the modified Library's header files unless such material is +limited to (i) numerical parameters; (ii) data structure layouts; +(iii) accessors; and (iv) small macros, templates and inline functions of +five lines or less in length. + +Furthermore, you are not required to apply this additional permission to a +modified version of the Library. diff --git a/LICENSE.FDL b/LICENSE.FDL new file mode 100644 index 0000000..938bb8d --- /dev/null +++ b/LICENSE.FDL @@ -0,0 +1,450 @@ + GNU Free Documentation License + Version 1.3, 3 November 2008 + + + Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall +subject (or to related matters) and contains nothing that could fall +directly within that overall subject. (Thus, if the Document is in +part a textbook of mathematics, a Secondary Section may not explain +any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +The "publisher" means any person or entity that distributes copies of +the Document to the public. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no +other conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to +give them a chance to provide you with an updated version of the +Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other +documents released under this License, and replace the individual +copies of this License in the various documents with a single copy +that is included in the collection, provided that you follow the rules +of this License for verbatim copying of each of the documents in all +other respects. + +You may extract a single document from such a collection, and +distribute it individually under this License, provided you insert a +copy of this License into the extracted document, and follow this +License in all other respects regarding verbatim copying of that +document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense, or distribute it is void, and +will automatically terminate your rights under this License. + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, receipt of a copy of some or all of the same material does +not give you any rights to use it. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the +GNU Free Documentation License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. If the Document +specifies that a proxy can decide which future versions of this +License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the +Document. + +11. RELICENSING + +"Massive Multiauthor Collaboration Site" (or "MMC Site") means any +World Wide Web server that publishes copyrightable works and also +provides prominent facilities for anybody to edit those works. A +public wiki that anybody can edit is an example of such a server. A +"Massive Multiauthor Collaboration" (or "MMC") contained in the site +means any set of copyrightable works thus published on the MMC site. + +"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 +license published by Creative Commons Corporation, a not-for-profit +corporation with a principal place of business in San Francisco, +California, as well as future copyleft versions of that license +published by that same organization. + +"Incorporate" means to publish or republish a Document, in whole or in +part, as part of another Document. + +An MMC is "eligible for relicensing" if it is licensed under this +License, and if all works that were first published under this License +somewhere other than this MMC, and subsequently incorporated in whole or +in part into the MMC, (1) had no cover texts or invariant sections, and +(2) were thus incorporated prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site +under CC-BY-SA on the same site at any time before August 1, 2009, +provided the MMC is eligible for relicensing. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/LICENSE.GPL2 b/LICENSE.GPL2 new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE.GPL2 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/LICENSE.GPL3 b/LICENSE.GPL3 new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE.GPL3 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE.GPL3-EXCEPT b/LICENSE.GPL3-EXCEPT new file mode 100644 index 0000000..b1cb1be --- /dev/null +++ b/LICENSE.GPL3-EXCEPT @@ -0,0 +1,704 @@ +This is the GNU General Public License version 3, annotated with The +Qt Company GPL Exception 1.0: + +------------------------------------------------------------------------- + +The Qt Company GPL Exception 1.0 + +Exception 1: + +As a special exception you may create a larger work which contains the +output of this application and distribute that work under terms of your +choice, so long as the work is not otherwise derived from or based on +this application and so long as the work does not in itself generate +output that contains the output from this application in its original +or modified form. + +Exception 2: + +As a special exception, you have permission to combine this application +with Plugins licensed under the terms of your choice, to produce an +executable, and to copy and distribute the resulting executable under +the terms of your choice. However, the executable must be accompanied +by a prominent notice offering all users of the executable the entire +source code to this application, excluding the source code of the +independent modules, but including any changes you have made to this +application, under the terms of this license. + + +------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE.GPLv2 b/LICENSE.GPLv2 new file mode 100644 index 0000000..a424477 --- /dev/null +++ b/LICENSE.GPLv2 @@ -0,0 +1,292 @@ + GNU GENERAL PUBLIC LICENSE + + The Qt Toolkit is Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + You may use, distribute and copy the Qt Toolkit under the terms of + GNU General Public License version 2, which is displayed below. + +------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Preamble + + The licenses for most software are designed to take away your freedom +to share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free software +--to make sure the software is free for all its users. This General +Public License applies to most of the Free Software Foundation's +software and to any other program whose authors commit to using it. +(Some other Free Software Foundation software is covered by the GNU +Lesser General Public License instead.) You can apply it to your +programs, too. + +When we speak of free software, we are referring to freedom, not price. +Our General Public Licenses are designed to make sure that you have the +freedom to distribute copies of free software (and charge for this +service if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone +to deny you these rights or to ask you to surrender the rights. These +restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you +have. You must make sure that they, too, receive or can get the source +code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + +Finally, any free program is threatened constantly by software patents. +We wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program +proprietary. To prevent this, we have made it clear that any patent +must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and +modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a +notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of running +the Program is not restricted, and the output from the Program is +covered only if its contents constitute a work based on the Program +(independent of having been made by running the Program). Whether that +is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously +and appropriately publish on each copy an appropriate copyright notice +and disclaimer of warranty; keep intact all the notices that refer to +this License and to the absence of any warranty; and give any other +recipients of the Program a copy of this License along with the +Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of +it, thus forming a work based on the Program, and copy and distribute +such modifications or work under the terms of Section 1 above, provided +that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the + Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of a +storage or distribution medium does not bring the other work under the +scope of this License. + +3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software + interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to control +compilation and installation of the executable. However, as a special +exception, the source code distributed need not include anything that +is normally distributed (in either source or binary form) with the +major components (compiler, kernel, and so on) of the operating system +on which the executable runs, unless that component itself accompanies +the executable. + +If distribution of executable or object code is made by offering access +to copy from a designated place, then offering equivalent access to +copy the source code from the same place counts as distribution of the +source code, even though third parties are not compelled to copy the +source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt otherwise +to copy, modify, sublicense or distribute the Program is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this License +will not have their licenses terminated so long as such parties remain +in full compliance. + +5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further restrictions +on the recipients' exercise of the rights granted herein. You are not +responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent license +would not permit royalty-free redistribution of the Program by all +those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain +entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License may +add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among countries +not thus excluded. In such case, this License incorporates the limitation +as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail +to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a version +number of this License, you may choose any version ever published by +the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the +author to ask for permission. For software which is copyrighted by +the Free Software Foundation, write to the Free Software Foundation; +we sometimes make exceptions for this. Our decision will be guided by +the two goals of preserving the free status of all derivatives of our +free software and of promoting the sharing and reuse of software +generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH +YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY +MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE +TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +END OF TERMS AND CONDITIONS diff --git a/LICENSE.GPLv3 b/LICENSE.GPLv3 new file mode 100644 index 0000000..71c4ad4 --- /dev/null +++ b/LICENSE.GPLv3 @@ -0,0 +1,686 @@ + GNU GENERAL PUBLIC LICENSE + + The Qt Toolkit is Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + You may use, distribute and copy the Qt Toolkit under the terms of + GNU Lesser General Public License version 3. That license references + the General Public License version 3, that is displayed below. Other + portions of the Qt Toolkit may be licensed directly under this license. + +------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE.LGPL3 b/LICENSE.LGPL3 new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/LICENSE.LGPL3 @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/LICENSE.LGPLv21 b/LICENSE.LGPLv21 new file mode 100644 index 0000000..dfcab5e --- /dev/null +++ b/LICENSE.LGPLv21 @@ -0,0 +1,514 @@ + GNU LESSER GENERAL PUBLIC LICENSE + + The Qt Toolkit is Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + You may use, distribute and copy the Qt Toolkit under the terms of + GNU Lesser General Public License version 2.1, which is displayed below. + +------------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/LICENSE.LGPLv3 b/LICENSE.LGPLv3 new file mode 100644 index 0000000..6bf924c --- /dev/null +++ b/LICENSE.LGPLv3 @@ -0,0 +1,175 @@ + GNU LESSER GENERAL PUBLIC LICENSE + + The Qt Toolkit is Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + You may use, distribute and copy the Qt Toolkit under the terms of + GNU Lesser General Public License version 3, which is displayed below. + This license makes reference to the version 3 of the GNU General + Public License, which you can find in the LICENSE.GPLv3 file. + +------------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright © 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies of this +licensedocument, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + + As used herein, “this License” refers to version 3 of the GNU Lesser +General Public License, and the “GNU GPL” refers to version 3 of the +GNU General Public License. + + “The Library” refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An “Application” is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A “Combined Work” is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the “Linked +Version”. + + The “Minimal Corresponding Source” for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The “Corresponding Application Code” for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this + license document. + +4. Combined Works. + + You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. + + e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +5. Combined Libraries. + + You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of + it is a work based on the Library, and explaining where to find + the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License “or any later version” applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, +you may choose any version of the GNU Lesser General Public License +ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the Library. + diff --git a/config.tests/gypsy/gypsy.pro b/config.tests/gypsy/gypsy.pro new file mode 100644 index 0000000..f9dcffa --- /dev/null +++ b/config.tests/gypsy/gypsy.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +unix { + CONFIG += link_pkgconfig + PKGCONFIG += gypsy gconf-2.0 +} +SOURCES += main.cpp diff --git a/config.tests/gypsy/main.cpp b/config.tests/gypsy/main.cpp new file mode 100644 index 0000000..2b8d3ca --- /dev/null +++ b/config.tests/gypsy/main.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +int main() +{ + GypsyControl *control = gypsy_control_get_default(); + GypsyDevice *device = gypsy_device_new("test"); + GypsySatellite *satellite = gypsy_satellite_new("test"); + + GConfClient *client = gconf_client_get_default(); + g_object_unref(client); + + return 0; +} diff --git a/dist/changes-5.2.1 b/dist/changes-5.2.1 new file mode 100644 index 0000000..37a1f55 --- /dev/null +++ b/dist/changes-5.2.1 @@ -0,0 +1,53 @@ +Qt 5.2.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.2.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.2 + +The Qt version 5.2 series is binary compatible with the 5.1.x series. +Applications compiled for 5.1 will continue to run with 5.2. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* General * +**************************************************************************** + +General Improvements +-------------------- + + - [QTBUG-34910] Fixed too long weather status string in weatherinfo + example (UI change). + - [QTBUG-36187] Byte order marker removed from positionpoll plugin + which caused compile errors on some older compilers. + - Fixed make install rules for all examples. + - declarative_core unit test was fixed and re-enabled. + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - [QTBUG-33220] Removed QtQml dependency from QtPositioning. + +**************************************************************************** +* Plugin Specific Changes * +**************************************************************************** + +GeoClue +------- + + - Delay satellite provider until requested. + - Sets the default preferred positioning method to AllPositioningMethods. + - Fix for incorrect accuracy values reported by the plug-in. + diff --git a/dist/changes-5.3.0 b/dist/changes-5.3.0 new file mode 100644 index 0000000..1bf445a --- /dev/null +++ b/dist/changes-5.3.0 @@ -0,0 +1,74 @@ +Qt 5.3 introduces many new features and improvements as well as bugfixes +over the 5.2.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.3 + +The Qt version 5.3 series is binary compatible with the 5.2.x series. +Applications compiled for 5.2 will continue to run with 5.3. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* General * +**************************************************************************** + +General Improvements +-------------------- + + - New SatelliteInfo example added. The example displays the signal + strength of surrounding satellites. The example employs a demo mode on + those platforms which don't provide satellite information + + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - Position (QML): + * Added direction and vertical speed properties. + + - QGeoPositionInfoSource: + * iOS position backend added. + * [QTBUG-34102] Android backend added. Android devices can retrieve + their current position. Network- and Satellite-based providers are + supported. + + - QGeoSatelliteInfoSource: + * [QTBUG-34102] Android backend added. Android devices can retrieve + information about the currently accessible GPS and GLONASS satellites. + + - QGeoRectangle: + * Added QGeoRectangle(QList) constructor added. + * Improved class documentation. + + - QGeoShape: + * Added extendShape(QGeoCoordinate) function. + + - QNmeaPositionInfoSource: + * Added support for reporting position accuracy. + + +**************************************************************************** +* Plugin Specific Changes * +**************************************************************************** + +GeoClue +------- + - [QTBUG-36298] Reports direction of travel and + vertical speed attributes, if supported by the active Geoclue + provider. + - Fixed the emission of the updateTimeout() signal when + position updates do not arrive in a timely manner. + - Position info source provider no longer internally filters + position updates to the requested update interval. + diff --git a/dist/changes-5.3.1 b/dist/changes-5.3.1 new file mode 100644 index 0000000..8913770 --- /dev/null +++ b/dist/changes-5.3.1 @@ -0,0 +1,57 @@ +Qt 5.3.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.3.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.3 + +The Qt version 5.3 series is binary compatible with the 5.2.x series. +Applications compiled for 5.2 will continue to run with 5.3. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* General * +**************************************************************************** + +General Improvements +-------------------- + + - Added PLUGIN_CLASS_NAME variable to all plug-in based projects. This + enables the automatic static linking and deployment of this plug-ins. + + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - QGeoPositionInfoSource: + * WinRT backend added + + +**************************************************************************** +* Platform Specific Changes * +**************************************************************************** + +Android +------- + + - [QTBUG-39082] Fixed crash on Galaxy S4 when running satellite and position + updates at the same time. + +iOS +--- + + - [QTBUG-38770] Added "classname" entry to all qmldir files enabling QML + plug-ins when doing static builds on iOS. + diff --git a/dist/changes-5.3.2 b/dist/changes-5.3.2 new file mode 100644 index 0000000..b1981f6 --- /dev/null +++ b/dist/changes-5.3.2 @@ -0,0 +1,50 @@ +Qt 5.3.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.3.0 and Qt 5.3.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.3 + +The Qt version 5.3 series is binary compatible with the 5.2.x series. +Applications compiled for 5.2 will continue to run with 5.3. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - [QTBUG-39843] [iOS] Fixed link error of positioncl and positionpoll + plug-in. + + - [QTBUG-40198] Fixed symbol clash between sensor and position plug-in. + This happened when using static builds of Qt (e.g. on iOS) and the + application used QtSensors and QtPositioning at the same time. + + - Improved weatherinfo example to not hang on the "Loading weather data" + screen if the current QGeoPositionInfoSource instance returned an error + during its startup. + +**************************************************************************** +* Platform Specific Changes * +**************************************************************************** + +[iOS] + + - [QTBUG-38300] Fixed wrong value of QGeoPositionInfo attributes if those + attributes were not provided by the platforms CoreLocation framework. + +[Linux] + + - [QTBUG-40425] Fixed missing error report when failing to connect to GeoClue. + diff --git a/dist/changes-5.4.0 b/dist/changes-5.4.0 new file mode 100644 index 0000000..3a14766 --- /dev/null +++ b/dist/changes-5.4.0 @@ -0,0 +1,54 @@ +Qt 5.4 introduces many new features and improvements as well as bugfixes +over the 5.3.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.4 + +The Qt version 5.4 series is binary compatible with the 5.3.x series. +Applications compiled for 5.3 will continue to run with 5.4. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - Position: + * [QTBUG-39547] Added magneticVariation and magneticVariationValid + property to the QML Position type. + + - General: + * Weatherinfo end Flickr example improved. + + - QGeoCircle: + * [QTBUG-41447] Fixed contains() when rounding errors occur + + - QGeoCoordinate: + * [QTBUG-41739] Fixed toString() output in cases when rounding + of longitude/latitude is necessary. + +**************************************************************************** +* Platform Specific Changes * +**************************************************************************** + +Android +------- + + - [QTBUG-41873] Fixed wrong initialization of QGeoPositionInfoSource::error() + after class instantiation. + +iOS +--- + + - [QTBUG-41827] Fixed position retrieval on iOS 8. This was required due + to the new authorization scheme in CLLocationManager. + diff --git a/dist/changes-5.4.1 b/dist/changes-5.4.1 new file mode 100644 index 0000000..4876ab3 --- /dev/null +++ b/dist/changes-5.4.1 @@ -0,0 +1,29 @@ +Qt 5.4.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.4.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://doc.qt.io/qt-5.4 + +The Qt version 5.4 series is binary compatible with the 5.3.x series. +Applications compiled for 5.3 will continue to run with 5.4. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - PositionSource: + * Fixed a case where an NMEA source file, which is integrated + using qrc, was not loaded. diff --git a/dist/changes-5.4.2 b/dist/changes-5.4.2 new file mode 100644 index 0000000..4f256ef --- /dev/null +++ b/dist/changes-5.4.2 @@ -0,0 +1,28 @@ +Qt 5.4.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.4.0 and Qt 5.4.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://doc.qt.io/qt-5.4 + +The Qt version 5.4 series is binary compatible with the 5.3.x series. +Applications compiled for 5.3 will continue to run with 5.4. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - [QTBUG-44572] Fixed issue where the default timeout for position update + requests was not set on WinRT. diff --git a/dist/changes-5.5.0 b/dist/changes-5.5.0 new file mode 100644 index 0000000..737ce13 --- /dev/null +++ b/dist/changes-5.5.0 @@ -0,0 +1,40 @@ +Qt 5.5 introduces many new features and improvements as well as bugfixes +over the 5.4.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + + http://doc.qt.io/qt-5/index.html + +The Qt version 5.5 series is binary compatible with the 5.4.x series. +Applications compiled for 5.4 will continue to run with 5.5. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - Added QGeoShape::center() function + - Converted QGeoShape, QGeoCircle, QGeoRectangle & QGeoCoordinate to Q_GADGET. + It simplifies the integration of the above classes into QML as the extensive + QML value type wrapper conversion is implicitly done by Qt QML. The existing + custom wrappers were removed. + - Fixed Debug stream operators for QGeoAreaMonitorInfo, QGeoCoordinate, + QGeoPositionInfo, QGeoSatelliteInfo & QGeoShape following a QDebug related + change in Qt Base. + - [QTBUG-44663] Added updateTimeout() signal to QML PositionSource type + - [QTBUG-44983] Fixed broken build of qtlocation repo when using qmake -r + on OS X + +QtLocation +---------- + + - First Technology Preview release of this module diff --git a/dist/changes-5.5.1 b/dist/changes-5.5.1 new file mode 100644 index 0000000..8b2ce35 --- /dev/null +++ b/dist/changes-5.5.1 @@ -0,0 +1,54 @@ +Qt 5.5.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.5.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://doc.qt.io/qt-5.5/ + +The Qt version 5.5 series is binary compatible with the 5.4.x series. +Applications compiled for 5.4 will continue to run with 5.5. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtLocation +----------- + + - Fixed change signal emission for Map.activeMapType property. + - Improved places-list and places-map examples. + - [QTBUG-47188] Fixed incorrect naming of OSM plugin's parameter. + The documentation mandates the osm prefix which was not used in + the case of the geocoding.host, useragent and routing.host parameters. + The required osm.places.host parameter was not handled at all. + - [QTBUG-47322] Added support for zoom level 19 to OSM plugin. + - Fixed an issue where the incorrect prefetch algorithm was used for map + tile fetching. + +QtPositioning +------------- + + - Improved minor documentation issues. + +**************************************************************************** +* Platform Specific Changes * +**************************************************************************** + +Windows +------- + + - Added missing QtQuick.Controls import to mapviewer example. + +Linux/gcc +--------- + + - [QTBUG-47427] Fixed compile issue in geoclue plugin when using gcc 4.5. diff --git a/dist/changes-5.6.0 b/dist/changes-5.6.0 new file mode 100644 index 0000000..1402343 --- /dev/null +++ b/dist/changes-5.6.0 @@ -0,0 +1,69 @@ +Qt 5.6 introduces many new features and improvements as well as bugfixes +over the 5.5.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + + http://doc.qt.io/qt-5/index.html + +The Qt version 5.6 series is binary compatible with the 5.5.x series. +Applications compiled for 5.5 will continue to run with 5.6. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - Removed Blackberry 10 support. + - [QTBUG-40702] Removed dependency towards libgeoclue. The plugin uses the + GeoClue DBus interface. + - Enabled Position plugin for OS X. Previously only the plugin was only working + for iOS. + - [QTBUG-48082] Added QtSerialPort based NMEA position plugin for Windows. + - Improved and fixed the WinRT position backend. Various changes on the WinRT + platform caused the necessity for such and update. + - Fixed endless requests towards Android's LocationManager when stopUpdates() was not + called in pair with startUpdates(). + +QtLocation +---------- + + - First stable release of the Qt Location API. The QML API changed significantly + compared to the Qt 5.5 release. The changes are documented in the official + module documentation under "API changes". + - Improved the Flick and Pinch implementation for QML Map. + - [QTBUG-44809] Fixed tile version handling + - Fixed crash during Map.setVisibleRegion while no plugin has been set. + - Improved error reporting in map, geocoding and routing model APIs. + - QTBUG[36919] Added means to prevent stealing of mouse/touch grabs on map. + - Improved coordinate related animation handling. Previously an animation along + the longitude always used a shortest path direction. Now it is possible to set + the direction for the animation. + - [QTBUG-46147] Fixed crash in QML Map element. + - [QTBUG-44311] Fixed bouncing Map flick on zoom level 2. + - [QTBUG-47020] & [QTBUG-47019] Improved Mouse and keyboard integration of QML Map + - [QTBUG-46388] Fixed Pinch when no MouseArea overlaps QML Map element. + - Fixed various unit test. + - Fixed a variety of minor documentation issues. + - Fixed dysfunctional tile cache for OSM plugin due to the server delivering JPEG + file when PNG files were expected. + - [QTBUG-41187] Unified the default tile cache location on disk. + - Fixed crash in gesture area while receiving wheel events. + - Fixed route requests with excluded areas for HERE plugin. + - Added support for custom map background color. + - Improved Map rendering performance throughout the QML API. + - [QTBUG-49772] Fixed weatherinfo example due to missing appid handling for + openweatermap.org. + - [QTBUG-47292] Added ability to clear the map cache. + - [QTBUG-50060] Improved performance of QML MapPolyline. + - [QTBUG-50240] Fixed OSM routing to handle changed server status code for a + successful route retrieval. + - [QTBUG-50519] Fixed map in mapviewer example when displaying the minimap. diff --git a/dist/changes-5.6.1 b/dist/changes-5.6.1 new file mode 100644 index 0000000..cd579b6 --- /dev/null +++ b/dist/changes-5.6.1 @@ -0,0 +1,38 @@ +Qt 5.6.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.6.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://doc.qt.io/qt-5.6/ + +The Qt version 5.6 series is binary compatible with the 5.5.x series. +Applications compiled for 5.5 will continue to run with 5.6. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtLocation +---------- + + - Improved the example and class documentation + - Consolidated various qmake project files across the module + - [QTBUG-51541] Fixed compile issue on linux/gcc-4.8 when using C++98 + - Improved compression rate of various screenshots throughout the documentation + +QtPositioning +------------- + + - [QTBUG-38802] Added default capability (location) for WinRT + - [QTBUG-53059] Fixed crash on iOS/OS X due to incorrectly initialized member + variable in the positioning backend + diff --git a/dist/changes-5.6.2 b/dist/changes-5.6.2 new file mode 100644 index 0000000..9f2a6f9 --- /dev/null +++ b/dist/changes-5.6.2 @@ -0,0 +1,55 @@ +Qt 5.6.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.6.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://doc.qt.io/qt-5/index.html + +The Qt version 5.6 series is binary compatible with the 5.5.x series. +Applications compiled for 5.5 will continue to run with 5.6. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtLocation +---------- + + - Fixed autotests containing bugs not showing in the tests + - [QTBUG-31797][QTBUG-53455] Fixed the QtLocation autotest framework by + replacing waitForRendering() with waitForPolished() + - Disabled explicit example installation + - [QTBUG-52514] Fixed QML Map not working with repeaters + - [QTBUG-53128] Fixed QML Map items not updating on window resize + - [QTBUG-52075] Fixed QML Map items losing focus during fast dragging + - [QTBUG-52301] Prevented flickering upon MapItemView's model reset + - [QTBUG-54141] Fixed QML Map setVisibleRegion failing in some cases + - Fixed a potential memory leak in QGeoTiledMappingManagerEngine + - [QTBUG-19929] Prevented generating MapCircles with invalid radius + - [QTBUG-54599] Added support for fetching OSM provider information from + a remote repository + - [QTBUG-54337] Fixed bounding calculation for QGeoCircle + - Added a minimal QML Map usage example + - Added support for dynamically remove unavailable providers from the OSM + plugin + - [QTBUG-55081] Fixed accessing MapPolyline before it is added to a Map + - [QTBUG-54964] Fixed invisible copyright notice on Android using Holo theme + - Improved macOS documentation + - Fixed the geocoding in the MapViewer example + - [QTBUG-55371] Fixed OSM plugin geocoding feature + +QtPositioning +------------- + + - [QTBUG-54026] Reduced Android minimum update interval + - Improved Android 5.0+ compatibility + diff --git a/dist/changes-5.7.0 b/dist/changes-5.7.0 new file mode 100644 index 0000000..d1fdb43 --- /dev/null +++ b/dist/changes-5.7.0 @@ -0,0 +1,53 @@ +Qt 5.7 introduces many new features and improvements as well as bugfixes +over the 5.6.x series. Also, there is a change in the licensing terms. +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://doc.qt.io/qt-5/index.html + +The Qt version 5.7 series is binary compatible with the 5.6.x series. +Applications compiled for 5.6 will continue to run with 5.7. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Important License Changes * +**************************************************************************** + + This module is no longer available under LGPLv2.1. The libraries are + now available under the following licenses: + * Commercial License + * GNU General Public License v2.0 (LICENSE.GPL2) and later + * GNU Lesser General Public License v3.0 (LICENSE.LGPL3) + + The tools are now available under the following licenses: + * Commercial License + * GNU General Public License 3.0 (LICENSE.GPL3) with exceptions + described in The Qt Company GPL Exception 1.0 (LICENSE.GPL3-EXCEPT) + +**************************************************************************** +* Library * +**************************************************************************** + +QtPositioning +------------- + + - Added support for tvOs to iOS/OS X positioning engine + - Added qHash() function for QGeoCoordinate + - Fixed negative QGeoCoordinate.azimuthTo results + - Added experimental support for GeoPosition API on Windows desktop + running Windows 8 or later + +QtLocation +---------- + + - [QTBUG-47025] Added option to hide map data copyright information + - Adjusted minimum map zoom level to prevent gray bounds. It prevents the map + from being smaller than the canvas size. + - Fixed several internal performance issues diff --git a/dist/changes-5.7.1 b/dist/changes-5.7.1 new file mode 100644 index 0000000..2d111d5 --- /dev/null +++ b/dist/changes-5.7.1 @@ -0,0 +1,45 @@ +Qt 5.7.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.7.0. + +Qt 5.7.1 contains a merge from Qt 5.6.2 and all changes in Qt 5.6.2 are +also in Qt 5.7.1. For more see changes-5.6.2. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.7 series is binary compatible with the 5.6.x series. +Applications compiled for 5.6 will continue to run with 5.7. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtLocation +---------- + +- [QTBUG-55535] Fixed map polygon not working correctly when zooming. +- [QTBUG-55964] Fixed rendering of polylines and polygons at the edge + of the map. +- [QTBUG-52610] Fixed incorrect map polyline drawing. +- [QTBUG-52076] Fixed crashes on tessellation of self-intersecting + GeoPolygons. +- [QTBUG-52030] Fixed that map's center and zoom were not initialized when + used in a layout. +- [QTBUG-52514] Fixed not working MapPolygon inside Repeater. +- [QTBUG-53128] Fixed map objects moving incorrectly on map resize. +- [QTBUG-52075] Fixed losing map item focus while dragging. + +QtPositioning +------------- + +- [QTBUG-54506] Fixed unneeded dependency on Activity for Android Platform. diff --git a/examples/examples.pro b/examples/examples.pro new file mode 100644 index 0000000..da770da --- /dev/null +++ b/examples/examples.pro @@ -0,0 +1,7 @@ +TEMPLATE = subdirs + +qtHaveModule(positioning) { + SUBDIRS += positioning + + qtHaveModule(location): SUBDIRS += location +} diff --git a/examples/location/location.pro b/examples/location/location.pro new file mode 100644 index 0000000..0b4b233 --- /dev/null +++ b/examples/location/location.pro @@ -0,0 +1,10 @@ +TEMPLATE = subdirs + +qtHaveModule(quick) { + SUBDIRS += places \ + places_list \ + places_map \ + mapviewer \ + minimal_map \ + planespotter +} diff --git a/examples/location/mapviewer/doc/images/mapviewer.png b/examples/location/mapviewer/doc/images/mapviewer.png new file mode 100644 index 0000000000000000000000000000000000000000..4dc13f7220cb92c633597a44737a0ac8827239d0 GIT binary patch literal 359712 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�sj8~=3{QUkW=SEWyz2|ELMz4g?O$lULx?FV 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

DSU^$QYZb?x(#^anU6O?Pbv3cB81JqIY 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

-Es`7N<_RShZT8Q;M!UmlZ>IN9ZE&U;|ZhD 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 b+3J4*?vTH?9fHP>00000NkvXXu0mjf&B2pT literal 0 HcmV?d00001 diff --git a/examples/location/mapviewer/doc/src/mapviewer.qdoc b/examples/location/mapviewer/doc/src/mapviewer.qdoc new file mode 100644 index 0000000..1fe0411 --- /dev/null +++ b/examples/location/mapviewer/doc/src/mapviewer.qdoc @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example mapviewer + \title Map Viewer (QML) + \ingroup qtlocation-examples + + \brief The Map Viewer example shows how to display and interact with a map, + search for an address, and find driving directions. + + \image mapviewer.png + + This is a large example covering many basic uses of maps, positioning, and + navigation services in Qt Location. This page is divided into sections + covering each of these areas of functionality with snippets from the code. + + \include examples-run.qdocinc + + \include example-parameters.qdocinc + + \section1 Overview + + QML types shown in this example: + + \list + \li Displaying a map + \list + \li \l{QtLocation::Map}{Map} + \li \l{QtLocation::MapGestureArea}{MapGestureArea} + \li \l[QML]{coordinate} + \endlist + \li Finding an address + \list + \li \l{QtLocation::GeocodeModel}{GeocodeModel} + \li \l{QtLocation::MapItemView}{MapItemView} + \li \l{QtLocation::MapCircle}{MapCircle} + \endlist + \li Directions and travel routes + \list + \li \l{QtLocation::RouteModel}{RouteModel} + \li \l{QtLocation::MapRoute}{MapRoute} + \endlist + \endlist + + \section1 Displaying a Map + + Drawing a map on-screen is accomplished using the Map type, as shown + below. + + \snippet mapviewer/map/MapComponent.qml top + \snippet mapviewer/map/MapComponent.qml coord + \snippet mapviewer/map/MapComponent.qml end + + In this example, we give the map an initial center \l [QML]{coordinate} + with a set latitude and longitude. We also set the initial zoom level to 50% (halfway between + the maximum and minimum). + + \section1 Finding an Address (Geocoding) + + To locate a certain address or place on the map uses a process called + geocoding. In order to perform a geocode operation, we first need to adjust + our Map object to be able to receive the result. + + Receiving results of geocoding is done through a GeocodeModel: + + \snippet mapviewer/map/MapComponent.qml geocodemodel0 + + To display the contents of the GeocodeModel we use a MapItemView: + + \snippet mapviewer/map/MapComponent.qml geocodeview + + MapItemView uses an object called a "delegate" to act as a template for the + items it creates. This can contain any map object desired, but in this case + we show a MapCircle: + + \snippet mapviewer/map/MapComponent.qml pointdel0 + \snippet mapviewer/map/MapComponent.qml pointdel1 + + With these three objects, we have enough to receive Geocode responses and + display them on our Map. The final piece is to send the actual Geocode + request. + + To send a geocode request, first we create an \l [QML]{Address} object, and fill it + in with the desired parameters. + + \snippet mapviewer/mapviewer.qml geocode0 + + Then we set "geocodeModel.query" to the filled in \l [QML]{Address}, + and call update() on the GeocodeModel. + + \snippet mapviewer/map/MapComponent.qml geocode1 + + \section1 Directions and Travel Routes + + Similar to the GeocodeModel, Qt Location also features the RouteModel type, + which allows information about routes (for example driving directions) between two + or more points, to be received and used with a Map. + + Here again, we instantiate the RouteModel as a property of our Map: + + \snippet mapviewer/map/MapComponent.qml routemodel0 + + To display the contents of a model to the user, we need a view. Once again + we will use a MapItemView, to display the Routes as objects on the Map: + + \snippet mapviewer/map/MapComponent.qml routeview0 + \snippet mapviewer/map/MapComponent.qml routeview1 + + To act as a template for the objects we wish the view to create, we create + a delegate component: + + \snippet mapviewer/map/MapComponent.qml routedelegate0 + \snippet mapviewer/map/MapComponent.qml routedelegate1 + + With the model, view and delegate now complete, the only missing component + is some kind of control over the model to begin the Route request process. + In the simplest case, we can fill out a Route request using two already + available \l [QML]{coordinate}{coordinates}: + + \snippet mapviewer/mapviewer.qml routecoordinate + + In the next snippet, we show how to set up the request object and instruct + the model to update. We also instruct the map to center on the start + coordinate for our routing request. + + \snippet mapviewer/map/MapComponent.qml routerequest0 + \snippet mapviewer/map/MapComponent.qml routerequest1 + \snippet mapviewer/map/MapComponent.qml routerequest2 + + This is all that is required to display a Route on the Map. However, it is + also useful to be able to retrieve the written directions and explanation + of the travel route. In the example, these are displayed in a \l {ListView} element. + To create this content, we use a standard \l {Models and Views in Qt Quick#ListModel}{ListModel} and + \l {ListView} pair. The data in the \l {Models and Views in Qt Quick#ListModel}{ListModel} is + built from the routeModel's output: + + \snippet mapviewer/forms/RouteList.qml routeinfomodel0 + \snippet mapviewer/forms/RouteList.qml routeinfomodel1 + \snippet mapviewer/forms/RouteList.qml routeinfomodel3 + + Inside the RouteModel, as you can see above, we add an + \l{QtLocation::RouteModel::status}{onStatusChanged} handler, which + calls the \c{showRouteList()} which updates the \c{routeInfoModel}: + + \snippet mapviewer/forms/RouteList.qml routeinfomodel2 +*/ diff --git a/examples/location/mapviewer/forms/Geocode.qml b/examples/location/mapviewer/forms/Geocode.qml new file mode 100644 index 0000000..9e27325 --- /dev/null +++ b/examples/location/mapviewer/forms/Geocode.qml @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtPositioning 5.5 + +GeocodeForm { + + property variant address + signal showPlace(variant address) + signal closeForm() + + goButton.onClicked: { + // fill out the Address element + address.street = street.text + address.city = city.text + address.state = state.text + address.country = country.text + address.postalCode = postalCode.text + showPlace(address) + } + + clearButton.onClicked: { + street.text = "" + city.text = "" + state.text = "" + country.text = "" + postalCode.text = "" + } + + cancelButton.onClicked: { + closeForm() + } + + Component.onCompleted: { + street.text = address.street + city.text = address.city + state.text = address.state + country.text = address.country + postalCode.text = address.postalCode + } +} + + diff --git a/examples/location/mapviewer/forms/GeocodeForm.ui.qml b/examples/location/mapviewer/forms/GeocodeForm.ui.qml new file mode 100644 index 0000000..3ed715a --- /dev/null +++ b/examples/location/mapviewer/forms/GeocodeForm.ui.qml @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + property alias goButton: goButton + property alias clearButton: clearButton + property alias postalCode: postalCode + property alias street: street + property alias city: city + property alias state: state + property alias country: country + property alias cancelButton: cancelButton + Rectangle { + id: tabRectangle + y: 20 + height: tabTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: qsTr("Geocode") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + id: item2 + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + + GridLayout { + id: gridLayout3 + anchors.rightMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.topMargin: 0 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + id: label2 + text: qsTr("Street") + } + + TextField { + id: street + Layout.fillWidth: true + } + + Label { + id: label3 + text: qsTr("City") + } + + TextField { + id: city + Layout.fillWidth: true + } + + Label { + id: label4 + text: qsTr("State") + } + + TextField { + id: state + Layout.fillWidth: true + } + + Label { + id: label5 + text: qsTr("Country") + } + + TextField { + id: country + Layout.fillWidth: true + } + + Label { + id: label6 + text: qsTr("Postal Code") + } + + TextField { + id: postalCode + Layout.fillWidth: true + } + + RowLayout { + id: rowLayout1 + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignRight + + Button { + id: goButton + text: qsTr("Proceed") + } + + Button { + id: clearButton + text: qsTr("Clear") + } + + Button { + id: cancelButton + text: qsTr("Cancel") + } + } + + Item { + Layout.fillHeight: true + Layout.columnSpan: 2 + } + } + } +} diff --git a/examples/location/mapviewer/forms/Locale.qml b/examples/location/mapviewer/forms/Locale.qml new file mode 100644 index 0000000..6e76c98 --- /dev/null +++ b/examples/location/mapviewer/forms/Locale.qml @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtPositioning 5.5 + +LocaleForm { + property string locale + signal selectLanguage(string language) + signal closeForm() + + goButton.onClicked: { + + if (!languageGroup.current) return + + if (otherRadioButton.checked) { + selectLanguage(language.text) + } else { + selectLanguage(languageGroup.current.text) + } + } + + clearButton.onClicked: { + language.text = "" + } + + cancelButton.onClicked: { + closeForm() + } + + Component.onCompleted: { + switch (locale) { + case "en": + enRadioButton.checked = true; + break + case "fr": + frRadioButton.checked = true; + break + default: + otherRadioButton.checked = true; + language.text = locale + break + } + } +} diff --git a/examples/location/mapviewer/forms/LocaleForm.ui.qml b/examples/location/mapviewer/forms/LocaleForm.ui.qml new file mode 100644 index 0000000..91a0847 --- /dev/null +++ b/examples/location/mapviewer/forms/LocaleForm.ui.qml @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + property alias clearButton: clearButton + property alias goButton: goButton + property alias cancelButton: cancelButton + property alias tabTitle: tabTitle + property alias languageGroup: languageGroup + property alias enRadioButton: enRadioButton + property alias frRadioButton: frRadioButton + property alias otherRadioButton: otherRadioButton + property alias language: language + + Rectangle { + id: tabRectangle + y: 20 + height: tabTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: "Locale" + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + id: item2 + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + GridLayout { + id: gridLayout3 + anchors.rightMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.topMargin: 0 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + ExclusiveGroup { id: languageGroup } + RadioButton { + id: enRadioButton + text: qsTr("en") + exclusiveGroup: languageGroup + Layout.columnSpan: 2 + } + + RadioButton { + id: frRadioButton + text: qsTr("fr") + exclusiveGroup: languageGroup + Layout.columnSpan: 2 + } + + RadioButton { + id: otherRadioButton + text: qsTr("Other") + exclusiveGroup: languageGroup + } + + TextField { + id: language + Layout.fillWidth: true + placeholderText: qsTr("") + } + + RowLayout { + id: rowLayout1 + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignRight + + Button { + id: goButton + text: qsTr("Proceed") + } + + Button { + id: clearButton + text: qsTr("Clear") + } + + Button { + id: cancelButton + text: qsTr("Cancel") + } + } + + Item { + Layout.fillHeight: true + Layout.columnSpan: 2 + } + + + } + } +} diff --git a/examples/location/mapviewer/forms/Message.qml b/examples/location/mapviewer/forms/Message.qml new file mode 100644 index 0000000..5d12c1f --- /dev/null +++ b/examples/location/mapviewer/forms/Message.qml @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 + +MessageForm { + property string title + property string message + property variant backPage + + signal closeForm(variant backPage) + + button.onClicked: { + closeForm(backPage) + } + + Component.onCompleted: { + messageText.text = message + messageTitle.text = title + } +} diff --git a/examples/location/mapviewer/forms/MessageForm.ui.qml b/examples/location/mapviewer/forms/MessageForm.ui.qml new file mode 100644 index 0000000..a300841 --- /dev/null +++ b/examples/location/mapviewer/forms/MessageForm.ui.qml @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + id: root + property alias messageText: messageText + property alias messageTitle: messageTitle + property alias button: button + + Rectangle { + id: tabRectangle + y: 20 + height: messageTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: messageTitle + color: "#ffffff" + text: qsTr("type") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + ColumnLayout { + id: columnLayout1 + spacing: 20 + anchors.fill: parent + + Label { + id: messageText + text: qsTr("message") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + textFormat: Text.RichText + } + + Button { + id: button + text: qsTr("OK") + Layout.alignment: Qt.AlignHCenter + } + + Item { + Layout.fillHeight: true + } + } + } +} + diff --git a/examples/location/mapviewer/forms/ReverseGeocode.qml b/examples/location/mapviewer/forms/ReverseGeocode.qml new file mode 100644 index 0000000..9e3cd65 --- /dev/null +++ b/examples/location/mapviewer/forms/ReverseGeocode.qml @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtPositioning 5.5 + +//Reverse Geocode Dialog +ReverseGeocodeForm { + property string title; + property variant coordinate + signal showPlace(variant coordinate) + signal closeForm() + + goButton.onClicked: { + var coordinate = QtPositioning.coordinate(parseFloat(latitude.text), + parseFloat(longitude.text)); + if (coordinate.isValid) { + showPlace(coordinate) + } + } + + clearButton.onClicked: { + latitude.text = "" + longitude.text = "" + } + + cancelButton.onClicked: { + closeForm() + } + + Component.onCompleted: { + latitude.text = "" + coordinate.latitude + longitude.text = "" + coordinate.longitude + if (title.length != 0) { + tabTitle.text = title; + } + } +} diff --git a/examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml b/examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml new file mode 100644 index 0000000..29eaec9 --- /dev/null +++ b/examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + property alias clearButton: clearButton + property alias goButton: goButton + property alias longitude: longitude + property alias latitude: latitude + property alias cancelButton: cancelButton + property alias tabTitle: tabTitle + Rectangle { + id: tabRectangle + y: 20 + height: tabTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: qsTr("Reverse Geocode") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + id: item2 + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + GridLayout { + id: gridLayout3 + anchors.rightMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.topMargin: 0 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + id: label2 + text: qsTr("Latitude") + } + + TextField { + id: latitude + Layout.fillWidth: true + } + + Label { + id: label3 + text: qsTr("Longitude") + } + + TextField { + id: longitude + Layout.fillWidth: true + placeholderText: qsTr("") + } + + RowLayout { + id: rowLayout1 + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignRight + + Button { + id: goButton + text: qsTr("Proceed") + } + + Button { + id: clearButton + text: qsTr("Clear") + } + + Button { + id: cancelButton + text: qsTr("Cancel") + } + } + Item { + Layout.fillHeight: true + Layout.columnSpan: 2 + } + } + } +} diff --git a/examples/location/mapviewer/forms/RouteAddress.qml b/examples/location/mapviewer/forms/RouteAddress.qml new file mode 100644 index 0000000..248396d --- /dev/null +++ b/examples/location/mapviewer/forms/RouteAddress.qml @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 +import QtPositioning 5.5 + +RouteAddressForm { + property alias plugin : tempGeocodeModel.plugin; + property variant fromAddress; + property variant toAddress; + signal showMessage(string topic, string message) + signal showRoute(variant startCoordinate,variant endCoordinate) + signal closeForm() + + goButton.onClicked: { + tempGeocodeModel.reset() + fromAddress.country = fromCountry.text + fromAddress.street = fromStreet.text + fromAddress.city = fromCity.text + toAddress.country = toCountry.text + toAddress.street = toStreet.text + toAddress.city = toCity.text + tempGeocodeModel.startCoordinate = QtPositioning.coordinate() + tempGeocodeModel.endCoordinate = QtPositioning.coordinate() + tempGeocodeModel.query = fromAddress + tempGeocodeModel.update(); + goButton.enabled = false; + } + + clearButton.onClicked: { + fromStreet.text = "" + fromCity.text = "" + fromCountry.text = "" + toStreet.text = "" + toCity.text = "" + toCountry.text = "" + } + + cancelButton.onClicked: { + closeForm() + } + + Component.onCompleted: { + fromStreet.text = fromAddress.street + fromCity.text = fromAddress.city + fromCountry.text = fromAddress.country + toStreet.text = toAddress.street + toCity.text = toAddress.city + toCountry.text = toAddress.country + } + + GeocodeModel { + id: tempGeocodeModel + + property int success: 0 + property variant startCoordinate + property variant endCoordinate + + onCountChanged: { + if (success == 1 && count == 1) { + query = toAddress + update(); + } + } + + onStatusChanged: { + if ((status == GeocodeModel.Ready) && (count == 1)) { + success++ + if (success == 1) { + startCoordinate.latitude = get(0).coordinate.latitude + startCoordinate.longitude = get(0).coordinate.longitude + } + if (success == 2) { + endCoordinate.latitude = get(0).coordinate.latitude + endCoordinate.longitude = get(0).coordinate.longitude + success = 0 + if (startCoordinate.isValid && endCoordinate.isValid) + showRoute(startCoordinate,endCoordinate) + else + goButton.enabled = true + } + } else if ((status == GeocodeModel.Ready) || (status == GeocodeModel.Error)) { + var st = (success == 0 ) ? "start" : "end" + success = 0 + if ((status == GeocodeModel.Ready) && (count == 0 )) { + showMessage(qsTr("Geocode Error"),qsTr("Unsuccessful geocode")); + goButton.enabled = true; + } + else if (status == GeocodeModel.Error) { + showMessage(qsTr("Geocode Error"), + qsTr("Unable to find location for the") + " " + + st + " " +qsTr("point")) + goButton.enabled = true; + } + else if ((status == GeocodeModel.Ready) && (count > 1 )) { + showMessage(qsTr("Ambiguous geocode"), + count + " " + qsTr("results found for the") + + " " + st + " " +qsTr("point, please specify location")) + goButton.enabled = true; + } + } + } + } +} diff --git a/examples/location/mapviewer/forms/RouteAddressForm.ui.qml b/examples/location/mapviewer/forms/RouteAddressForm.ui.qml new file mode 100644 index 0000000..3f98dc0 --- /dev/null +++ b/examples/location/mapviewer/forms/RouteAddressForm.ui.qml @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + property alias fromStreet: fromStreet + property alias fromCountry: fromCountry + property alias toStreet: toStreet + property alias toCity: toCity + property alias toCountry: toCountry + property alias fromCity: fromCity + property alias goButton: goButton + property alias clearButton: clearButton + property alias cancelButton: cancelButton + + Rectangle { + id: tabRectangle + y: 20 + height: tabTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: qsTr("Route Address") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + id: item2 + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + GridLayout { + id: gridLayout3 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + id: label1 + text: qsTr("From") + font.bold: true + anchors.horizontalCenter: parent.horizontalCenter + Layout.columnSpan : 2 + } + + Label { + id: label2 + text: qsTr("Street") + } + + TextField { + id: fromStreet + Layout.fillWidth: true + } + + Label { + id: label3 + text: qsTr("City") + } + + TextField { + id: fromCity + Layout.fillWidth: true + } + + Label { + id: label7 + text: qsTr("Country") + } + + TextField { + id: fromCountry + Layout.fillWidth: true + } + + Label { + id: label6 + text: qsTr("To") + font.bold: true + anchors.horizontalCenter: parent.horizontalCenter + Layout.columnSpan: 2 + } + + Label { + id: label4 + text: qsTr("Street") + } + + TextField { + id: toStreet + Layout.fillWidth: true + } + + Label { + id: label5 + text: qsTr("City") + } + + TextField { + id: toCity + Layout.fillWidth: true + } + + Label { + id: label8 + text: qsTr("Country") + } + + TextField { + id: toCountry + Layout.fillWidth: true + } + + RowLayout { + id: rowLayout1 + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignRight + + Button { + id: goButton + text: qsTr("Proceed") + } + + Button { + id: clearButton + text: qsTr("Clear") + } + + Button { + id: cancelButton + text: qsTr("Cancel") + } + } + + Item { + Layout.fillHeight: true + Layout.columnSpan: 2 + } + } + } +} diff --git a/examples/location/mapviewer/forms/RouteCoordinate.qml b/examples/location/mapviewer/forms/RouteCoordinate.qml new file mode 100644 index 0000000..f195ac7 --- /dev/null +++ b/examples/location/mapviewer/forms/RouteCoordinate.qml @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtPositioning 5.5 + +RouteCoordinateForm { + property variant toCoordinate + property variant fromCoordinate + signal showRoute(variant startCoordinate,variant endCoordinate) + signal closeForm() + + goButton.onClicked: { + var startCoordinate = QtPositioning.coordinate(parseFloat(fromLatitude.text), + parseFloat(fromLongitude.text)); + var endCoordinate = QtPositioning.coordinate(parseFloat(toLatitude.text), + parseFloat(toLongitude.text)); + if (startCoordinate.isValid && endCoordinate.isValid) { + goButton.enabled = false; + showRoute(startCoordinate,endCoordinate) + } + } + + clearButton.onClicked: { + fromLatitude.text = "" + fromLongitude.text = "" + toLatitude.text = "" + toLongitude.text = "" + } + + cancelButton.onClicked: { + closeForm() + } + + Component.onCompleted: { + fromLatitude.text = "" + fromCoordinate.latitude + fromLongitude.text = "" + fromCoordinate.longitude + toLatitude.text = "" + toCoordinate.latitude + toLongitude.text = "" + toCoordinate.longitude + } +} + diff --git a/examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml b/examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml new file mode 100644 index 0000000..bd492d3 --- /dev/null +++ b/examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + property alias fromLatitude: fromLatitude + property alias fromLongitude: fromLongitude + property alias toLatitude: toLatitude + property alias toLongitude: toLongitude + property alias clearButton: clearButton + property alias goButton: goButton + property alias cancelButton: cancelButton + + Rectangle { + id: tabRectangle + y: 20 + height: tabTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: qsTr("Route Coordinates") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + id: item2 + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + GridLayout { + id: gridLayout3 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + id: label1 + text: qsTr("From") + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + Layout.columnSpan : 2 + } + + Label { + id: label2 + text: qsTr("Latitude") + } + + TextField { + id: fromLatitude + Layout.fillWidth: true + } + + Label { + id: label3 + text: qsTr("Longitude") + } + + TextField { + id: fromLongitude + Layout.fillWidth: true + } + + Label { + id: label6 + text: qsTr("To") + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + Layout.columnSpan: 2 + } + + Label { + id: label4 + text: qsTr("Latitude") + } + + TextField { + id: toLatitude + Layout.fillWidth: true + } + + Label { + id: label5 + text: qsTr("Longitude") + } + + TextField { + id: toLongitude + Layout.fillWidth: true + } + + RowLayout { + id: rowLayout1 + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignRight + Button { + id: goButton + text: qsTr("Proceed") + } + + Button { + id: clearButton + text: qsTr("Clear") + } + + Button { + id: cancelButton + text: qsTr("Cancel") + } + } + Item { + Layout.fillHeight: true + Layout.columnSpan: 2 + } + } + } +} diff --git a/examples/location/mapviewer/forms/RouteList.qml b/examples/location/mapviewer/forms/RouteList.qml new file mode 100644 index 0000000..27fa572 --- /dev/null +++ b/examples/location/mapviewer/forms/RouteList.qml @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../helper.js" as Helper + +//! [routeinfomodel0] +ListView { +//! [routeinfomodel0] + property variant routeModel + property string totalTravelTime + property string totalDistance + signal closeForm() +//! [routeinfomodel1] + interactive: true + model: ListModel { id: routeInfoModel } + header: RouteListHeader {} + delegate: RouteListDelegate{ + routeIndex.text: index + 1 + routeInstruction.text: instruction + routeDistance.text: distance + } +//! [routeinfomodel1] + footer: Button { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Close") + onClicked: { + closeForm() + } + } + + Component.onCompleted: { + //! [routeinfomodel2] + routeInfoModel.clear() + if (routeModel.count > 0) { + for (var i = 0; i < routeModel.get(0).segments.length; i++) { + routeInfoModel.append({ + "instruction": routeModel.get(0).segments[i].maneuver.instructionText, + "distance": Helper.formatDistance(routeModel.get(0).segments[i].maneuver.distanceToNextInstruction) + }); + } + } + //! [routeinfomodel2] + totalTravelTime = routeModel.count == 0 ? "" : Helper.formatTime(routeModel.get(0).travelTime) + totalDistance = routeModel.count == 0 ? "" : Helper.formatDistance(routeModel.get(0).distance) + } +//! [routeinfomodel3] +} +//! [routeinfomodel3] diff --git a/examples/location/mapviewer/forms/RouteListDelegate.qml b/examples/location/mapviewer/forms/RouteListDelegate.qml new file mode 100644 index 0000000..2dc9981 --- /dev/null +++ b/examples/location/mapviewer/forms/RouteListDelegate.qml @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + id: root + property bool checked: false + property alias routeInstruction: instructionLabel + property alias routeDistance: distanceLabel + property alias routeIndex: indexLabel + + width: parent.width + height: indexLabel.height * 2 + + RowLayout { + spacing: 10 + anchors.left: parent.left + anchors.leftMargin: 30 + anchors.verticalCenter: parent.verticalCenter + Label { + id: indexLabel + } + Label { + id: instructionLabel + wrapMode: Text.Wrap + } + Label { + id: distanceLabel + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 15 + height: 1 + color: "#46a2da" + } +} + + + diff --git a/examples/location/mapviewer/forms/RouteListHeader.qml b/examples/location/mapviewer/forms/RouteListHeader.qml new file mode 100644 index 0000000..383f892 --- /dev/null +++ b/examples/location/mapviewer/forms/RouteListHeader.qml @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Item { + property alias travelTime: travelTimeLabel + property alias distance: distanceLabel + width: parent.width + height: tabTitle.height * 3.0 + + Rectangle { + id: tabRectangle + y: tabTitle.height + height: tabTitle.height * 2 - 1 + color: "#46a2da" + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: qsTr("Route Information") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + + Label { + id: travelTimeLabel + text: totalTravelTime + color: "#ffffff" + font.bold: true + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + } + + Label { + id: distanceLabel + text: totalDistance + color: "#ffffff" + font.bold: true + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + } + } +} diff --git a/examples/location/mapviewer/helper.js b/examples/location/mapviewer/helper.js new file mode 100644 index 0000000..19201d0 --- /dev/null +++ b/examples/location/mapviewer/helper.js @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +.pragma library + +function roundNumber(number, digits) +{ + var multiple = Math.pow(10, digits); + return Math.round(number * multiple) / multiple; +} + +function formatTime(sec) +{ + var value = sec + var seconds = value % 60 + value /= 60 + value = (value > 1) ? Math.round(value) : 0 + var minutes = value % 60 + value /= 60 + value = (value > 1) ? Math.round(value) : 0 + var hours = value + if (hours > 0) value = hours + "h:"+ minutes + "m" + else value = minutes + "min" + return value +} + +function formatDistance(meters) +{ + var dist = Math.round(meters) + if (dist > 1000 ){ + if (dist > 100000){ + dist = Math.round(dist / 1000) + } + else{ + dist = Math.round(dist / 100) + dist = dist / 10 + } + dist = dist + " km" + } + else{ + dist = dist + " m" + } + return dist +} diff --git a/examples/location/mapviewer/main.cpp b/examples/location/mapviewer/main.cpp new file mode 100644 index 0000000..1b52643 --- /dev/null +++ b/examples/location/mapviewer/main.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +static bool parseArgs(QStringList& args, QVariantMap& parameters) +{ + + while (!args.isEmpty()) { + + QString param = args.takeFirst(); + + if (param.startsWith("--help")) { + QTextStream out(stdout); + out << "Usage: " << endl; + out << "--plugin. - Sets parameter = value for plugin" << endl; + out.flush(); + return true; + } + + if (param.startsWith("--plugin.")) { + + param.remove(0, 9); + + if (args.isEmpty() || args.first().startsWith("--")) { + parameters[param] = true; + } else { + + QString value = args.takeFirst(); + + if (value == "true" || value == "on" || value == "enabled") { + parameters[param] = true; + } else if (value == "false" || value == "off" + || value == "disable") { + parameters[param] = false; + } else { + parameters[param] = value; + } + } + } + } + return false; +} + +int main(int argc, char *argv[]) +{ + QGuiApplication application(argc, argv); + + QVariantMap parameters; + QStringList args(QCoreApplication::arguments()); + + if (parseArgs(args, parameters)) + return 0; + if (!args.contains(QStringLiteral("osm.useragent"))) + parameters[QStringLiteral("osm.useragent")] = QStringLiteral("QtLocation Mapviewer example"); + + QQmlApplicationEngine engine; + engine.addImportPath(QStringLiteral(":/imports")); + engine.load(QUrl(QStringLiteral("qrc:///mapviewer.qml"))); + QObject::connect(&engine, SIGNAL(quit()), qApp, SLOT(quit())); + + QObject *item = engine.rootObjects().first(); + Q_ASSERT(item); + + QMetaObject::invokeMethod(item, "initializeProviders", + Q_ARG(QVariant, QVariant::fromValue(parameters))); + + return application.exec(); +} diff --git a/examples/location/mapviewer/map/CircleItem.qml b/examples/location/mapviewer/map/CircleItem.qml new file mode 100644 index 0000000..3b32ba5 --- /dev/null +++ b/examples/location/mapviewer/map/CircleItem.qml @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.5 +import QtLocation 5.6 + +//TODO: remove/refactor me when items are integrated + +MapCircle { + + color: "#46a2da" + border.color: "#190a33" + border.width: 2 + smooth: true + opacity: 0.25 + + function setGeometry(markers, index){ + center.latitude = markers[index].coordinate.latitude + center.longitude = markers[index].coordinate.longitude + radius= center.distanceTo(markers[index + 1].coordinate) + } +} diff --git a/examples/location/mapviewer/map/ImageItem.qml b/examples/location/mapviewer/map/ImageItem.qml new file mode 100644 index 0000000..3bf4dfe --- /dev/null +++ b/examples/location/mapviewer/map/ImageItem.qml @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.5; +import QtLocation 5.6 + +MapQuickItem { //to be used inside MapComponent only + id: imageItem + + MouseArea { + anchors.fill: parent + drag.target: parent + } + + function setGeometry(markers, index) { + coordinate.latitude = markers[index].coordinate.latitude + coordinate.longitude = markers[index].coordinate.longitude + } + + sourceItem: Image { + id: testImage + source: "../resources/icon.png" + opacity: 0.7 + } +} diff --git a/examples/location/mapviewer/map/MapComponent.qml b/examples/location/mapviewer/map/MapComponent.qml new file mode 100644 index 0000000..008a4a0 --- /dev/null +++ b/examples/location/mapviewer/map/MapComponent.qml @@ -0,0 +1,641 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtLocation 5.6 +import QtPositioning 5.5 +import "../helper.js" as Helper + +//! [top] +Map { + id: map +//! [top] + property variant markers + property variant mapItems + property int markerCounter: 0 // counter for total amount of markers. Resets to 0 when number of markers = 0 + property int currentMarker + property int lastX : -1 + property int lastY : -1 + property int pressX : -1 + property int pressY : -1 + property int jitterThreshold : 30 + property bool followme: false + property variant scaleLengths: [5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000] + property alias routeQuery: routeQuery + property alias routeModel: routeModel + property alias geocodeModel: geocodeModel + + signal showGeocodeInfo() + signal geocodeFinished() + signal routeError() + signal coordinatesCaptured(double latitude, double longitude) + signal showMainMenu(variant coordinate) + signal showMarkerMenu(variant coordinate) + signal showRouteMenu(variant coordinate) + signal showPointMenu(variant coordinate) + signal showRouteList() + + function geocodeMessage() + { + var street, district, city, county, state, countryCode, country, postalCode, latitude, longitude, text + latitude = Math.round(geocodeModel.get(0).coordinate.latitude * 10000) / 10000 + longitude =Math.round(geocodeModel.get(0).coordinate.longitude * 10000) / 10000 + street = geocodeModel.get(0).address.street + district = geocodeModel.get(0).address.district + city = geocodeModel.get(0).address.city + county = geocodeModel.get(0).address.county + state = geocodeModel.get(0).address.state + countryCode = geocodeModel.get(0).address.countryCode + country = geocodeModel.get(0).address.country + postalCode = geocodeModel.get(0).address.postalCode + + text = "Latitude: " + latitude + "
" + text +="Longitude: " + longitude + "
" + "
" + if (street) text +="Street: "+ street + "
" + if (district) text +="District: "+ district +"
" + if (city) text +="City: "+ city + "
" + if (county) text +="County: "+ county + "
" + if (state) text +="State: "+ state + "
" + if (countryCode) text +="Country code: "+ countryCode + "
" + if (country) text +="Country: "+ country + "
" + if (postalCode) text +="PostalCode: "+ postalCode + "
" + return text + } + + function calculateScale() + { + var coord1, coord2, dist, text, f + f = 0 + coord1 = map.toCoordinate(Qt.point(0,scale.y)) + coord2 = map.toCoordinate(Qt.point(0+scaleImage.sourceSize.width,scale.y)) + dist = Math.round(coord1.distanceTo(coord2)) + + if (dist === 0) { + // not visible + } else { + for (var i = 0; i < scaleLengths.length-1; i++) { + if (dist < (scaleLengths[i] + scaleLengths[i+1]) / 2 ) { + f = scaleLengths[i] / dist + dist = scaleLengths[i] + break; + } + } + if (f === 0) { + f = dist / scaleLengths[i] + dist = scaleLengths[i] + } + } + + text = Helper.formatDistance(dist) + scaleImage.width = (scaleImage.sourceSize.width * f) - 2 * scaleImageLeft.sourceSize.width + scaleText.text = text + } + + function deleteMarkers() + { + var count = map.markers.length + for (var i = 0; i= 0) + map.zoomLevel = value + } + } + + Item { + id: scale + z: map.z + 3 + visible: scaleText.text != "0 m" + anchors.bottom: parent.bottom; + anchors.right: parent.right + anchors.margins: 20 + height: scaleText.height * 2 + width: scaleImage.width + + Image { + id: scaleImageLeft + source: "../resources/scale_end.png" + anchors.bottom: parent.bottom + anchors.right: scaleImage.left + } + Image { + id: scaleImage + source: "../resources/scale.png" + anchors.bottom: parent.bottom + anchors.right: scaleImageRight.left + } + Image { + id: scaleImageRight + source: "../resources/scale_end.png" + anchors.bottom: parent.bottom + anchors.right: parent.right + } + Label { + id: scaleText + color: "#004EAE" + anchors.centerIn: parent + text: "0 m" + } + Component.onCompleted: { + map.calculateScale(); + } + } + + //! [routemodel0] + RouteModel { + id: routeModel + plugin : map.plugin + query: RouteQuery { + id: routeQuery + } + onStatusChanged: { + if (status == RouteModel.Ready) { + switch (count) { + case 0: + // technically not an error + map.routeError() + break + case 1: + map.showRouteList() + break + } + } else if (status == RouteModel.Error) { + map.routeError() + } + } + } + //! [routemodel0] + + //! [routedelegate0] + Component { + id: routeDelegate + + MapRoute { + id: route + route: routeData + line.color: "#46a2da" + line.width: 5 + smooth: true + opacity: 0.8 + //! [routedelegate0] + MouseArea { + id: routeMouseArea + anchors.fill: parent + hoverEnabled: false + property variant lastCoordinate + + onPressed : { + map.lastX = mouse.x + parent.x + map.lastY = mouse.y + parent.y + map.pressX = mouse.x + parent.x + map.pressY = mouse.y + parent.y + lastCoordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y)) + } + + onPositionChanged: { + if (mouse.button == Qt.LeftButton) { + map.lastX = mouse.x + parent.x + map.lastY = mouse.y + parent.y + } + } + + onPressAndHold:{ + if (Math.abs(map.pressX - parent.x- mouse.x ) < map.jitterThreshold + && Math.abs(map.pressY - parent.y - mouse.y ) < map.jitterThreshold) { + showRouteMenu(lastCoordinate); + } + } + + } + //! [routedelegate1] + } + } + //! [routedelegate1] + + //! [geocodemodel0] + GeocodeModel { + id: geocodeModel + plugin: map.plugin + onStatusChanged: { + if ((status == GeocodeModel.Ready) || (status == GeocodeModel.Error)) + map.geocodeFinished() + } + onLocationsChanged: + { + if (count == 1) { + map.center.latitude = get(0).coordinate.latitude + map.center.longitude = get(0).coordinate.longitude + } + } + } + //! [geocodemodel0] + + //! [pointdel0] + Component { + id: pointDelegate + + MapCircle { + id: point + radius: 1000 + color: "#46a2da" + border.color: "#190a33" + border.width: 2 + smooth: true + opacity: 0.25 + center: locationData.coordinate + //! [pointdel0] + MouseArea { + anchors.fill:parent + id: circleMouseArea + hoverEnabled: false + property variant lastCoordinate + + onPressed : { + map.lastX = mouse.x + parent.x + map.lastY = mouse.y + parent.y + map.pressX = mouse.x + parent.x + map.pressY = mouse.y + parent.y + lastCoordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y)) + } + + onPositionChanged: { + if (Math.abs(map.pressX - parent.x- mouse.x ) > map.jitterThreshold || + Math.abs(map.pressY - parent.y -mouse.y ) > map.jitterThreshold) { + if (pressed) parent.radius = parent.center.distanceTo( + map.toCoordinate(Qt.point(mouse.x, mouse.y))) + } + if (mouse.button == Qt.LeftButton) { + map.lastX = mouse.x + parent.x + map.lastY = mouse.y + parent.y + } + } + + onPressAndHold:{ + if (Math.abs(map.pressX - parent.x- mouse.x ) < map.jitterThreshold + && Math.abs(map.pressY - parent.y - mouse.y ) < map.jitterThreshold) { + showPointMenu(lastCoordinate); + } + } + } + //! [pointdel1] + } + } + //! [pointdel1] + + //! [routeview0] + MapItemView { + model: routeModel + delegate: routeDelegate + //! [routeview0] + autoFitViewport: true + //! [routeview1] + } + //! [routeview1] + + //! [geocodeview] + MapItemView { + model: geocodeModel + delegate: pointDelegate + } + //! [geocodeview] + + Timer { + id: scaleTimer + interval: 100 + running: false + repeat: false + onTriggered: { + map.calculateScale() + } + } + + MouseArea { + id: mouseArea + property variant lastCoordinate + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onPressed : { + map.lastX = mouse.x + map.lastY = mouse.y + map.pressX = mouse.x + map.pressY = mouse.y + lastCoordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y)) + } + + onPositionChanged: { + if (mouse.button == Qt.LeftButton) { + map.lastX = mouse.x + map.lastY = mouse.y + } + } + + onDoubleClicked: { + var mouseGeoPos = map.toCoordinate(Qt.point(mouse.x, mouse.y)); + var preZoomPoint = map.fromCoordinate(mouseGeoPos, false); + if (mouse.button === Qt.LeftButton) { + map.zoomLevel++; + } else if (mouse.button === Qt.RightButton) { + map.zoomLevel--; + } + var postZoomPoint = map.fromCoordinate(mouseGeoPos, false); + var dx = postZoomPoint.x - preZoomPoint.x; + var dy = postZoomPoint.y - preZoomPoint.y; + + var mapCenterPoint = Qt.point(map.width / 2.0 + dx, map.height / 2.0 + dy); + map.center = map.toCoordinate(mapCenterPoint); + + lastX = -1; + lastY = -1; + } + + onPressAndHold:{ + if (Math.abs(map.pressX - mouse.x ) < map.jitterThreshold + && Math.abs(map.pressY - mouse.y ) < map.jitterThreshold) { + showMainMenu(lastCoordinate); + } + } + } +//! [end] +} +//! [end] diff --git a/examples/location/mapviewer/map/Marker.qml b/examples/location/mapviewer/map/Marker.qml new file mode 100644 index 0000000..3c0ed17 --- /dev/null +++ b/examples/location/mapviewer/map/Marker.qml @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5; +import QtLocation 5.6 + +//! [mqi-top] +MapQuickItem { + id: marker +//! [mqi-top] + property alias lastMouseX: markerMouseArea.lastX + property alias lastMouseY: markerMouseArea.lastY + +//! [mqi-anchor] + anchorPoint.x: image.width/4 + anchorPoint.y: image.height + + sourceItem: Image { + id: image +//! [mqi-anchor] + source: "../resources/marker.png" + opacity: markerMouseArea.pressed ? 0.6 : 1.0 + MouseArea { + id: markerMouseArea + property int pressX : -1 + property int pressY : -1 + property int jitterThreshold : 10 + property int lastX: -1 + property int lastY: -1 + anchors.fill: parent + hoverEnabled : false + drag.target: marker + preventStealing: true + + onPressed : { + map.pressX = mouse.x + map.pressY = mouse.y + map.currentMarker = -1 + for (var i = 0; i< map.markers.length; i++){ + if (marker == map.markers[i]){ + map.currentMarker = i + break + } + } + } + + onPressAndHold:{ + if (Math.abs(map.pressX - mouse.x ) < map.jitterThreshold + && Math.abs(map.pressY - mouse.y ) < map.jitterThreshold) { + var p = map.fromCoordinate(marker.coordinate) + lastX = p.x + lastY = p.y + map.showMarkerMenu(marker.coordinate) + } + } + } + + Text{ + id: number + y: image.height/10 + width: image.width + color: "white" + font.bold: true + font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter + Component.onCompleted: { + text = map.markerCounter + } + } + +//! [mqi-closeimage] + } +//! [mqi-closeimage] + + Component.onCompleted: coordinate = map.toCoordinate(Qt.point(markerMouseArea.mouseX, + markerMouseArea.mouseY)); +//! [mqi-close] +} +//! [mqi-close] diff --git a/examples/location/mapviewer/map/MiniMap.qml b/examples/location/mapviewer/map/MiniMap.qml new file mode 100644 index 0000000..43af605 --- /dev/null +++ b/examples/location/mapviewer/map/MiniMap.qml @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 + +Rectangle{ + id: miniMapRect + width: 152 + height: 152 + anchors.right: (parent) ? parent.right : undefined + anchors.rightMargin: 10 + anchors.top: (parent) ? parent.top : undefined + anchors.topMargin: 10 + color: "#242424" + Map { + id: miniMap + anchors.top: parent.top + anchors.topMargin: 1 + anchors.left: parent.left + anchors.leftMargin: 1 + width: 150 + height: 150 + zoomLevel: (map.zoomLevel > minimumZoomLevel + 3) ? minimumZoomLevel + 3 : 2.5 + center: map.center + plugin: map.plugin + gesture.enabled: false + copyrightsVisible: false + + MapRectangle { + color: "#44ff0000" + border.width: 1 + border.color: "red" + topLeft { + latitude: miniMap.center.latitude + 5 + longitude: miniMap.center.longitude - 5 + } + bottomRight { + latitude: miniMap.center.latitude - 5 + longitude: miniMap.center.longitude + 5 + } + } + } +} diff --git a/examples/location/mapviewer/map/PolygonItem.qml b/examples/location/mapviewer/map/PolygonItem.qml new file mode 100644 index 0000000..cab4e6c --- /dev/null +++ b/examples/location/mapviewer/map/PolygonItem.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.5 +import QtLocation 5.6 + +//TODO: remove me when items are integrated + +MapPolygon { + + color: "#46a2da" + border.color: "#190a33" + border.width: 2 + smooth: true + opacity: 0.25 + + function setGeometry(markers, index){ + for (var i = index; i0) + plugin = Qt.createQmlObject ('import QtLocation 5.6; Plugin{ name:"' + provider + '"; parameters: appWindow.parameters}', appWindow) + else + plugin = Qt.createQmlObject ('import QtLocation 5.6; Plugin{ name:"' + provider + '"}', appWindow) + + if (minimap) { + minimap.destroy() + minimap = null + } + + var zoomLevel = null + var center = null + if (map) { + zoomLevel = map.zoomLevel + center = map.center + map.destroy() + } + + map = mapComponent.createObject(page); + map.plugin = plugin; + + if (zoomLevel != null) { + map.zoomLevel = zoomLevel + map.center = center + } else { + map.zoomLevel = (map.maximumZoomLevel - map.minimumZoomLevel)/2 + } + + map.forceActiveFocus() + } + + function getPlugins() + { + var plugin = Qt.createQmlObject ('import QtLocation 5.6; Plugin {}', appWindow) + var myArray = new Array() + for (var i = 0; i" + qsTr("Distance:") + "
" + distance) + break + case "drawImage": + map.addGeoItem("ImageItem") + break + case "drawRectangle": + map.addGeoItem("RectangleItem") + break + case "drawCircle": + map.addGeoItem("CircleItem") + break; + case "drawPolyline": + map.addGeoItem("PolylineItem") + break; + case "drawPolygonMenu": + map.addGeoItem("PolygonItem") + break + default: + console.log("Unsupported operation") + } + } + } + + ItemPopupMenu { + id: itemPopupMenu + + function show(type,coordinate) + { + stackView.pop(page) + itemPopupMenu.type = type + itemPopupMenu.update() + itemPopupMenu.popup() + } + + onItemClicked: { + stackView.pop(page) + switch (item) { + case "showRouteInfo": + stackView.showRouteListPage() + break; + case "deleteRoute": + map.routeModel.reset(); + break; + case "showPointInfo": + map.showGeocodeInfo() + break; + case "deletePoint": + map.geocodeModel.reset() + break; + default: + console.log("Unsupported operation") + } + } + } + + StackView { + id: stackView + anchors.fill: parent + focus: true + initialItem: Item { + id: page + } + + function showMessage(title,message,backPage) + { + push({ item: Qt.resolvedUrl("forms/Message.qml") , + properties: { + "title" : title, + "message" : message, + "backPage" : backPage + }}) + currentItem.closeForm.connect(closeMessage) + } + + function closeMessage(backPage) + { + pop(backPage) + } + + function closeForm() + { + pop(page) + } + + function showRouteListPage() + { + push({ item: Qt.resolvedUrl("forms/RouteList.qml") , + properties: { + "routeModel" : map.routeModel + }}) + currentItem.closeForm.connect(closeForm) + } + } + + Component { + id: mapComponent + + MapComponent{ + width: page.width + height: page.height + onFollowmeChanged: mainMenu.isFollowMe = map.followme + onSupportedMapTypesChanged: mainMenu.mapTypeMenu.createMenu(map) + onCoordinatesCaptured: { + var text = "" + qsTr("Latitude:") + " " + Helper.roundNumber(latitude,4) + "
" + qsTr("Longitude:") + " " + Helper.roundNumber(longitude,4) + stackView.showMessage(qsTr("Coordinates"),text); + } + onGeocodeFinished:{ + if (map.geocodeModel.status == GeocodeModel.Ready) { + if (map.geocodeModel.count == 0) { + stackView.showMessage(qsTr("Geocode Error"),qsTr("Unsuccessful geocode")) + } else if (map.geocodeModel.count > 1) { + stackView.showMessage(qsTr("Ambiguous geocode"), map.geocodeModel.count + " " + + qsTr("results found for the given address, please specify location")) + } else { + stackView.showMessage(qsTr("Location"), geocodeMessage(),page) + } + } else if (map.geocodeModel.status == GeocodeModel.Error) { + stackView.showMessage(qsTr("Geocode Error"),qsTr("Unsuccessful geocode")) + } + } + onRouteError: stackView.showMessage(qsTr("Route Error"),qsTr("Unable to find a route for the given points"),page) + + onShowGeocodeInfo: stackView.showMessage(qsTr("Location"),geocodeMessage(),page) + + onErrorChanged: { + if (map.error != Map.NoError) { + var title = qsTr("ProviderError") + var message = map.errorString + "

" + qsTr("Try to select other provider") + "" + if (map.error == Map.MissingRequiredParameterError) + message += "
" + qsTr("or see") + " \'mapviewer --help\' " + + qsTr("how to pass plugin parameters.") + stackView.showMessage(title,message); + } + } + onShowMainMenu: mapPopupMenu.show(coordinate) + onShowMarkerMenu: markerPopupMenu.show(coordinate) + onShowRouteMenu: itemPopupMenu.show("Route",coordinate) + onShowPointMenu: itemPopupMenu.show("Point",coordinate) + onShowRouteList: stackView.showRouteListPage() + } + } +} diff --git a/examples/location/mapviewer/mapviewer.qrc b/examples/location/mapviewer/mapviewer.qrc new file mode 100644 index 0000000..a84c67d --- /dev/null +++ b/examples/location/mapviewer/mapviewer.qrc @@ -0,0 +1,37 @@ + + + mapviewer.qml + map/MapComponent.qml + map/Marker.qml + map/PolylineItem.qml + map/RectangleItem.qml + map/CircleItem.qml + map/PolygonItem.qml + map/ImageItem.qml + map/MiniMap.qml + forms/Message.qml + forms/MessageForm.ui.qml + forms/Geocode.qml + forms/GeocodeForm.ui.qml + forms/ReverseGeocode.qml + forms/ReverseGeocodeForm.ui.qml + forms/RouteCoordinate.qml + forms/RouteCoordinateForm.ui.qml + forms/RouteAddress.qml + forms/RouteAddressForm.ui.qml + forms/Locale.qml + forms/LocaleForm.ui.qml + forms/RouteList.qml + forms/RouteListDelegate.qml + forms/RouteListHeader.qml + menus/MainMenu.qml + menus/MapPopupMenu.qml + menus/MarkerPopupMenu.qml + menus/ItemPopupMenu.qml + helper.js + resources/scale_end.png + resources/scale.png + resources/marker.png + resources/icon.png + + diff --git a/examples/location/mapviewer/menus/ItemPopupMenu.qml b/examples/location/mapviewer/menus/ItemPopupMenu.qml new file mode 100644 index 0000000..8d7e2f7 --- /dev/null +++ b/examples/location/mapviewer/menus/ItemPopupMenu.qml @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Menu { + property variant type + signal itemClicked(string item) + + function update() { + clear() + addItem(qsTr("Info")).triggered.connect(function(){itemClicked("show" + type + "Info")}) + addItem(qsTr("Delete")).triggered.connect(function(){itemClicked("delete" + type )}) + } +} diff --git a/examples/location/mapviewer/menus/MainMenu.qml b/examples/location/mapviewer/menus/MainMenu.qml new file mode 100644 index 0000000..7054c40 --- /dev/null +++ b/examples/location/mapviewer/menus/MainMenu.qml @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtLocation 5.6 + +MenuBar { + property variant providerMenu: providerMenu + property variant mapTypeMenu: mapTypeMenu + property variant toolsMenu: toolsMenu + property alias isFollowMe: toolsMenu.isFollowMe + property alias isMiniMap: toolsMenu.isMiniMap + + signal selectProvider(string providerName) + signal selectMapType(variant mapType) + signal selectTool(string tool); + signal toggleMapState(string state) + + Menu { + id: providerMenu + title: qsTr("Provider") + + function createMenu(plugins) + { + clear() + for (var i = 0; i < plugins.length; i++) { + createProviderMenuItem(plugins[i]); + } + } + + function createProviderMenuItem(provider) + { + var item = addItem(provider); + item.checkable = true; + item.triggered.connect(function(){selectProvider(provider)}) + } + } + + Menu { + id: mapTypeMenu + title: qsTr("MapType") + + function createMenu(map) + { + clear() + for (var i = 0; i 0) { + addItem(qsTr("Delete all markers")).triggered.connect(function(){itemClicked("deleteMarkers")}) + } + + if (mapItemsCount > 0) { + addItem(qsTr("Delete all items")).triggered.connect(function(){itemClicked("deleteItems")}) + } + } +} diff --git a/examples/location/mapviewer/menus/MarkerPopupMenu.qml b/examples/location/mapviewer/menus/MarkerPopupMenu.qml new file mode 100644 index 0000000..61855ff --- /dev/null +++ b/examples/location/mapviewer/menus/MarkerPopupMenu.qml @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Menu { + property int currentMarker + property int markersCount + signal itemClicked(string item) + + function update() { + clear() + addItem(qsTr("Delete")).triggered.connect(function(){itemClicked("deleteMarker")}) + addItem(qsTr("Coordinates")).triggered.connect(function(){itemClicked("getMarkerCoordinate")}) + addItem(qsTr("Move to")).triggered.connect(function(){itemClicked("moveMarkerTo")}) + if (currentMarker == markersCount-2){ + addItem(qsTr("Route to next point")).triggered.connect(function(){itemClicked("routeToNextPoint")}); + addItem(qsTr("Distance to next point")).triggered.connect(function(){itemClicked("distanceToNextPoint")}); + } + if (currentMarker < markersCount-2){ + addItem(qsTr("Route to next points")).triggered.connect(function(){itemClicked("routeToNextPoints")}); + addItem(qsTr("Distance to next point")).triggered.connect(function(){itemClicked("distanceToNextPoint")}); + } + + var menu = addMenu(qsTr("Draw...")) + menu.addItem(qsTr("Image")).triggered.connect(function(){itemClicked("drawImage")}) + + if (currentMarker <= markersCount-2){ + menu.addItem(qsTr("Rectangle")).triggered.connect(function(){itemClicked("drawRectangle")}) + menu.addItem(qsTr("Circle")).triggered.connect(function(){itemClicked("drawCircle")}) + menu.addItem(qsTr("Polyline")).triggered.connect(function(){itemClicked("drawPolyline")}) + } + + if (currentMarker < markersCount-2){ + menu.addItem(qsTr("Polygon")).triggered.connect(function(){itemClicked("drawPolygonMenu")}) + } + } +} diff --git a/examples/location/mapviewer/resources/icon.png b/examples/location/mapviewer/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..973a50091ada099b37b07d1456e54acb6e773650 GIT binary patch literal 1859 zcmah~cU05K8vRM=QkS;4qVA*lRNAuV>2py@qzVQE6_BFn%32UnkfkLAf)we9^aTZ} z(t|<>NE$5$f`k$XC6o{X0Rn^?ATQ`2yZ^m&?tJro_uiRr=A1caV(p=pveHMS0RWJ- zwlZ@N&h`gG5+sahiby^Hh*0fqubT@?Kmgn*zu`qTMhx6|^2z#H`+pJStNv(<0OD_m zR?|R&38LLH7=HmlG!Ma9Ac$rmZIDo$MJUc3fxiqRfWJe?BM1k8V=p5Jm*IG@P=ym9 z2cB=qfejD&ZieH8Bm{sc*f#VJkh7&U1A^{xsJ;6hSo&&?dR>CD&Uh%k!?0F+iauSuSU1q#dO%m zbX<$=aCp^uJ-+j1LYGrQmvds5OA_f;63I2?i+d{h&TI0WH1gfF?t2+Me`NMN$f7*V zqCCo`c<1yA@g%p`C-3XC{J!V;eSUBI{0pdm7WIb|55P(W;3b1$rGtpF!SM2-hz~=N z6||R?w5Up2bk%T7^>A$U@T;1UxZ08UI(j0Co`j+&*V9w#M^iqIrZ$YdL62vm$FrKo zvoI4mn2FrxiN9JHd7l~it&{IsCkwEXg}ABrxTzxiR1smigfLx7oGxvfE^nLp(8;Xo zWL9@EYe=)TUsxzItDemINSQ-Z*yvt%Q!g9SH~+bR0XwjO8(hE-afmcd8*QniMO-7zQf(% zuWs_!Hu=1*-SzF=&7Hk1{@yl!Z)bO(zqh};x4*Y9*cS){Zpj~vg|FJh-vR0f90G}n zOGrr{mQ_&vLH&rvvEx5$pE58$Yhr3*LWM-JA7fUE|_;E0D#)9&A`{435zu5ZDsI} z65Nj%v9_C>B{>-mbXH3lr*A96Q&u`$Q^QpT;qVq>iYg#bEymRDXF+9TFk{&rmhicGFLk?G`;aZ3NbsO62aSy)P0RKR0u=`D05Ji1d z^%hv6b1=LNbW559tDI<75)a+AM~3V@&%})}v;{4D3x2+jHJ9AAe?d~TmK*$8)4pp` zKITTq!;>P_);MqW4@S>()Z;~cPix8hx<=cRyO6XUQBR(NJ3BVjWCdGn}368Ut-izwpS~`X#V*M zXUm6RnveSCAnr4D!}{@@WAi09JZLAZb`<9&!OSCNnkswO0Ge-~&Ys?~dzED?xK*py z;Z3Q^JPN~C7mp{zITw2duM8JB1y2l>!B&jq){D+C;AY#J>n9+N|6zAGc+Q*F1w(7u zLA^+R(M0ov&bi2Axw_#lA;tU1RDH6 z&HJPwjL3~8QY$@CZ3aG!{qv+PmA(OVq^aXZDZE%d+du6>&85@#HI8;#GR5Q;Mw-BH`XaJL9yJC_V-*t% gdhjLiuUs-bd_{o9{h&Z=1{XL$eoD)0qRHcyZ`_I literal 0 HcmV?d00001 diff --git a/examples/location/mapviewer/resources/marker.png b/examples/location/mapviewer/resources/marker.png new file mode 100644 index 0000000000000000000000000000000000000000..2116dfdf51bfb8ac9556af035d598cf2c68c44e3 GIT binary patch literal 752 zcmVP001Be1^@s6=bY090008FNklP9F+g_dGL z7b4wOL~YbX6}NhC`Y^Yem;_BVH^=jjGv%gj-gDq^?<8~n@6OCQGh-NK7!h@__p?7@ zAHg%1%k{H&7`2$=I6)SB$ey9%mf^nW7pw@t0liA$uW3=@<{h$6c2acLUNScZ#1n1& zj{lW0thX!xPr(xr5KoZZd4{Yl+b9~?SDiKsYw4jl=ahsz49ascH$lOp{)gvA{a+7Rbz6(!?wYhMaEN_*&FvlTY+Qe#GeLd5Y`1f1;(W)TxhmI&f zGMmlT7dqEPnL5c-$rEwKC^iKt`y;L(O{LEFOX&6M`3tgeo|g$ok3Ur@d#&7y#A^h6 z?>^6`UFq7fPoGtQPnFzO*2?UlncYoA0W+aJ?(31K(tFo2L@iE#B&)%{`ZRITEx!AZ zU)hx!$Bv5w?bcmR&+YjDzHG#=UYFZb?pf8h$W$s-$07?*nr~~*-vC?M)D-<;udDUC zx(`a(n9X1Re>5i2b#_ic_8U43M<~(&X=?Gdk$OC$)?v`lX*}=K5dL}Zz8b-2L$~o) id-3sdrSb5U8~YFSpzHYmbwF4E0000PQ<|Z&(Z(QQpe}ECj@=zX;NfjIC%2Ts>e~;XMma*JYD@<);T3K0RYVMA4vcJ literal 0 HcmV?d00001 diff --git a/examples/location/mapviewer/resources/scale_end.png b/examples/location/mapviewer/resources/scale_end.png new file mode 100644 index 0000000000000000000000000000000000000000..94510b1258e33726e65d4d5f539b8dc1dea5c905 GIT binary patch literal 93 zcmeAS@N?(olHy`uVBq!ia0vp^OhC-W0V2~}Z=?b#6;Bt(5RU7~DID*U6A}^<1tu^B pAL2^zV(}^D?rhdzjZ-+uz+kqK@vYpot*Suv44$rjF6*2UngECF7V7{2 literal 0 HcmV?d00001 diff --git a/examples/location/minimal_map/doc/images/minimal_map.png b/examples/location/minimal_map/doc/images/minimal_map.png new file mode 100644 index 0000000000000000000000000000000000000000..31ad0f8b307555fe0749c77425eb57c59a3c776c GIT binary patch literal 156053 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%0DOPO=b*~? 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 Eg6voShyVZp literal 0 HcmV?d00001 diff --git a/examples/location/minimal_map/doc/src/minimal_map.qdoc b/examples/location/minimal_map/doc/src/minimal_map.qdoc new file mode 100644 index 0000000..06b310a --- /dev/null +++ b/examples/location/minimal_map/doc/src/minimal_map.qdoc @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\example minimal_map +\title Minimal Map (QML) +\ingroup qtlocation-examples +\brief The minimum code to display a map using Qt Quick. + +\image minimal_map.png + +\e {Minimal Map} demonstrates how to use the \l{Map} item to render a map. +It shows the minimum amount of code needed to display the map, and can be used +as a basis for further experimentation. + +\include examples-run.qdocinc + +\section1 C++ Code + +In \c main.cpp we use only the QGuiApplication and QQmlApplicationEngine +classes. + +\quotefromfile minimal_map/main.cpp +\skipto #include +\printto main + +In the main function, we first instantiate a QGuiApplication object. +Then we create a QQmlApplicationEngine and tell it to load \c main.qml +from the \l{The Qt Resource System}{Qt Resource System}. + +Finally, QGuiApplication::exec() launches the main event loop. + +\printuntil } + +\section1 QML Code + +In \c main.qml, we import the \l {Qt Location QML Types}{QtLocation} QML module +and its depending \l {Qt Positioning QML Types}{QtPositioning} QML module. +Next, we create the top level window, set a sensible default size, and make +it visible. The window will be filled by a \l [QML]{Map} item showing the map. + +\quotefromfile minimal_map/main.qml +\skipto import +\printuntil } +\printline } +\skipto Map +\printuntil } +\printline } + +The \l [QML]{Plugin} item is necessary to define the map provider we are +going to use. The example can work with any of the available geo services +plugins. However, some plugins may require additional plugin parameters +in order to function correctly and we can use \l [QML]{PluginParameter} +to specify them. In this example, we use the \c osm plugin, which is a +\l {Qt Location Open Street Map Plugin} and does not require any parameters. + +In the \l [QML]{Map} item, we refer to the \c plugin we use and we set the \c +center and the \c zoomLevel of the map. + +\section1 Requirements + +The example requires a working internet connection to download +\c OpenStreetMap map tiles. An optional system proxy should be picked +up automatically. +*/ diff --git a/examples/location/minimal_map/main.cpp b/examples/location/minimal_map/main.cpp new file mode 100644 index 0000000..e0b58e3 --- /dev/null +++ b/examples/location/minimal_map/main.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + return app.exec(); +} + diff --git a/examples/location/minimal_map/main.qml b/examples/location/minimal_map/main.qml new file mode 100644 index 0000000..26f2498 --- /dev/null +++ b/examples/location/minimal_map/main.qml @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Window 2.0 +import QtLocation 5.6 +import QtPositioning 5.6 + +Window { + width: 512 + height: 512 + visible: true + + Plugin { + id: osmPlugin + name: "osm" + // specify plugin parameters if necessary + // PluginParameter { + // name: + // value: + // } + } + + Map { + anchors.fill: parent + plugin: osmPlugin + center: QtPositioning.coordinate(59.91, 10.75) // Oslo + zoomLevel: 10 + } +} diff --git a/examples/location/minimal_map/minimal_map.pro b/examples/location/minimal_map/minimal_map.pro new file mode 100644 index 0000000..5c16525 --- /dev/null +++ b/examples/location/minimal_map/minimal_map.pro @@ -0,0 +1,10 @@ +TEMPLATE = app + +QT += location + +SOURCES += main.cpp + +RESOURCES += qml.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/location/minimal_map +INSTALLS += target diff --git a/examples/location/minimal_map/qml.qrc b/examples/location/minimal_map/qml.qrc new file mode 100644 index 0000000..0ff3892 --- /dev/null +++ b/examples/location/minimal_map/qml.qrc @@ -0,0 +1,6 @@ + + + main.qml + + + diff --git a/examples/location/places/doc/images/places.png b/examples/location/places/doc/images/places.png new file mode 100644 index 0000000000000000000000000000000000000000..0b1ac8b70f0e3875075304084e739f4c43294913 GIT binary patch literal 151611 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 ig9BW66wn@kLK*;~Eo 0) + return place.location.address.text; + + return place.location.address.street; + } + + function categoryNames(categories) { + var result = ""; + + for (var i = 0; i < categories.length; ++i) { + if (result == "") { + result = categories[i].name; + } else { + result = result + ", " + categories[i].name; + } + } + + return result; + } + + function additonalInformation(place) { + var keys = place.extendedAttributes.keys(); + var result; + + for (var i = 0; i < keys.length; ++i) { + var label = place.extendedAttributes[keys[i]].label; + var text = place.extendedAttributes[keys[i]].text; + if (label) { + result += label + ": " + if (text) + result += text + result += "
" + } + } + + if (!result) + result = qsTr("No information") + + return result; + } + + editorialsButton.onClicked: showEditorials(place) + imagesButton.onClicked: showImages(place) + reviewsButton.onClicked: showReviews(place) + findSimilarButton.onClicked: searchForSimilar(place) + + Component.onCompleted: { + placeName.text = place ? (place.favorite ? place.favorite.name : place.name) : "" + placeIcon.source = place ? (place.favorite ? place.favorite.icon.url(Qt.size(40,40)) + : place.icon.url() == "" ? + "../resources/marker.png" + : place.icon.url(Qt.size(40,40))) : "" + ratingView.rating = (place && place.ratings) ? place.ratings.average : 0 + distance.text = Helper.formatDistance(distanceToPlace) + address.text = placeAddress(place) + categories.text = place ? categoryNames(place.categories) : "" + phone.text = place ? place.primaryPhone : "" + fax.text = place ? place.primaryFax : "" + email.text = place ? place.primaryEmail : "" + website.text = place ? '' + place.primaryWebsite + '' : "" + addInformation.text = place ? additonalInformation(place) : "" + if (place) { + editorialsButton.enabled = Qt.binding(function(){ return place && place.editorialModel.totalCount > 0 }) + reviewsButton.enabled = Qt.binding(function(){ return place && place.reviewModel.totalCount > 0 }) + imagesButton.enabled = Qt.binding(function(){ return place && place.imageModel.totalCount > 0 }) + findSimilarButton.enabled = true + } + } +} + diff --git a/examples/location/places/forms/PlaceDetailsForm.ui.qml b/examples/location/places/forms/PlaceDetailsForm.ui.qml new file mode 100644 index 0000000..f13b9b5 --- /dev/null +++ b/examples/location/places/forms/PlaceDetailsForm.ui.qml @@ -0,0 +1,285 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 +import "../views" + +Item { + id: root + property alias placeName: placeName + property alias placeIcon: placeIcon + property alias distance: distance + property alias address: address + property alias categories: categories + property alias phone: phone + property alias fax: fax + property alias email: email + property alias website: website + property alias addInformation: addInformation + property alias editorialsButton: editorialsButton + property alias reviewsButton: reviewsButton + property alias imagesButton: imagesButton + property alias findSimilarButton: findSimilarButton + property alias ratingView: ratingView + width: parent.width + height: parent.height + + ScrollView { + id:scrollView + flickableItem.interactive: true + anchors.fill: parent + anchors.margins: 15 + + GridLayout { + width: scrollView.width - 15 + rows: 7 + columns: 2 + + RowLayout { + Layout.columnSpan: 2 + Layout.fillWidth: true + + Image { + id: placeIcon + source: "../resources/marker.png" + anchors.margins: 30 + } + + Label { + id: placeName + text: qsTr("PlaceName") + font.bold: true + } + + Item { + Layout.fillWidth: true + } + } + + RatingView { + id: ratingView + size: placeName.height * 2 + Layout.columnSpan: 2 + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + Layout.columnSpan: 2 + height: 1 + color: "#46a2da" + visible: addressBox.visible + } + + GroupBox { + id: addressBox + Layout.fillWidth: true + Layout.columnSpan: 2 + flat: true + title: qsTr("Address") + + GridLayout { + id: gridLayout3 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + text: qsTr("Distance:") + } + + Label { + id: distance + Layout.fillWidth: true + text: qsTr("1000 km") + } + + Label { + id: address + Layout.columnSpan: 2 + text: qsTr("Street Number
xxxxx City
Country") + } + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + Layout.columnSpan: 2 + height: 1 + color: "#46a2da" + visible: categoriesBox.visible + } + + GroupBox { + id: categoriesBox + Layout.fillWidth: true + Layout.columnSpan: 2 + flat: true + title: qsTr("Categories") + + Label { + id: categories + anchors.fill: parent + text: qsTr("category1, category2 ,category3") + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: "#46a2da" + visible: contactDetailsBox.visible + } + + GroupBox { + id: contactDetailsBox + Layout.fillWidth: true + Layout.columnSpan: 2 + flat: true + title: qsTr("Contact details") + GridLayout { + id: gridLayout4 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + text: qsTr("Phone: ") + } + + Label { + id: phone + Layout.fillWidth: true + text: qsTr("000-000-000") + } + + Label { + text: qsTr("Fax: ") + } + + Label { + id: fax + Layout.fillWidth: true + text: qsTr("000-000-000") + } + + Label { + text: qsTr("Email: ") + } + + Label { + id: email + Layout.fillWidth: true + text: qsTr("name@company.com") + } + + Label { + text: qsTr("Website: ") + } + + Label { + id: website + Layout.fillWidth: true + text: qsTr("http:://company.com") + } + } + } + + Rectangle { + Layout.columnSpan: 2 + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: "#46a2da" + visible: informationBox.visible + } + + GroupBox { + id: informationBox + Layout.fillWidth: true + Layout.columnSpan: 2 + flat: true + title: qsTr("Additional information") + ColumnLayout { + Label { + id: addInformation + text: qsTr("AdditionalInformation1
AdditionalInformation2
AdditionalInformation3") + } + } + } + + RowLayout { + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignHCenter + + Button { + id: editorialsButton + text: qsTr("Editorials") + enabled: false + } + + Button { + id: reviewsButton + text: qsTr("Reviews") + enabled: false + } + + Button { + id: imagesButton + text: qsTr("Images") + enabled: false + } + + Button { + id: findSimilarButton + text: qsTr("Find similar") + enabled: false + } + } + } + } +} + diff --git a/examples/location/places/forms/SearchBoundingBox.qml b/examples/location/places/forms/SearchBoundingBox.qml new file mode 100644 index 0000000..10c2863 --- /dev/null +++ b/examples/location/places/forms/SearchBoundingBox.qml @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtPositioning 5.5 + +SearchBoundingBoxForm { + property variant searchRegion + signal changeSearchBoundingBox(variant coordinate, real widthDeg, real heightDeg) + signal closeForm() + + goButton.onClicked: { + var coordinate = QtPositioning.coordinate(parseFloat(latitude.text), + parseFloat(longitude.text)); + if (coordinate.isValid) + changeSearchBoundingBox(coordinate,parseFloat(widthDeg.text),parseFloat(heightDeg.text)) + } + + clearButton.onClicked: { + latitude.text = "" + longitude.text = "" + widthDeg.text = "" + heightDeg.text = "" + } + + cancelButton.onClicked: closeForm() + + Component.onCompleted: { + latitude.text = "" + searchRegion.center.latitude + longitude.text = "" + searchRegion.center.longitude + widthDeg.text = searchRegion.width ? "" + searchRegion.width : "0.0" + heightDeg.text = searchRegion.height ? "" + searchRegion.height: "0.0" + } +} diff --git a/examples/location/places/forms/SearchBoundingBoxForm.ui.qml b/examples/location/places/forms/SearchBoundingBoxForm.ui.qml new file mode 100644 index 0000000..4bae3d6 --- /dev/null +++ b/examples/location/places/forms/SearchBoundingBoxForm.ui.qml @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + property alias clearButton: clearButton + property alias goButton: goButton + property alias longitude: longitude + property alias latitude: latitude + property alias widthDeg: widthDeg + property alias heightDeg: heightDeg + property alias cancelButton: cancelButton + property alias tabTitle: tabTitle + Rectangle { + id: tabRectangle + y: 20 + height: tabTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: qsTr("Search Bounding Box") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + id: item2 + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + GridLayout { + id: gridLayout3 + anchors.rightMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.topMargin: 0 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + id: label2 + text: qsTr("Latitude") + } + + TextField { + id: latitude + Layout.fillWidth: true + } + + Label { + id: label3 + text: qsTr("Longitude") + } + + TextField { + id: longitude + Layout.fillWidth: true + placeholderText: qsTr("") + } + + Label { + id: label4 + text: qsTr("Width (deg)") + } + + TextField { + id: widthDeg + Layout.fillWidth: true + } + + Label { + id: label5 + text: qsTr("Height (deg)") + } + + TextField { + id: heightDeg + Layout.fillWidth: true + placeholderText: qsTr("") + } + + RowLayout { + id: rowLayout1 + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignRight + + Button { + id: goButton + text: qsTr("Set") + } + + Button { + id: clearButton + text: qsTr("Clear") + } + + Button { + id: cancelButton + text: qsTr("Cancel") + } + } + Item { + Layout.fillHeight: true + Layout.columnSpan: 2 + } + } + } +} diff --git a/examples/location/places/forms/SearchBoundingCircle.qml b/examples/location/places/forms/SearchBoundingCircle.qml new file mode 100644 index 0000000..ce6212a --- /dev/null +++ b/examples/location/places/forms/SearchBoundingCircle.qml @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtPositioning 5.5 + +SearchBoundingCircleForm { + property variant searchRegion + signal changeSearchBoundingCircle(variant coordinate, real radius) + signal closeForm() + + goButton.onClicked: { + var coordinate = QtPositioning.coordinate(parseFloat(latitude.text), + parseFloat(longitude.text)); + if (coordinate.isValid) + changeSearchBoundingCircle(coordinate,parseFloat(radius.text)) + } + + clearButton.onClicked: { + latitude.text = "" + longitude.text = "" + radius.text = "" + } + + cancelButton.onClicked: closeForm() + + Component.onCompleted: { + latitude.text = "" + searchRegion.center.latitude + longitude.text = "" + searchRegion.center.longitude + radius.text = searchRegion.radius ? "" + searchRegion.radius : "0.0" + } +} diff --git a/examples/location/places/forms/SearchBoundingCircleForm.ui.qml b/examples/location/places/forms/SearchBoundingCircleForm.ui.qml new file mode 100644 index 0000000..9bfd996 --- /dev/null +++ b/examples/location/places/forms/SearchBoundingCircleForm.ui.qml @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + property alias clearButton: clearButton + property alias goButton: goButton + property alias longitude: longitude + property alias latitude: latitude + property alias radius: radius + property alias cancelButton: cancelButton + property alias tabTitle: tabTitle + Rectangle { + id: tabRectangle + y: 20 + height: tabTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: qsTr("Search Bounding Circle") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + id: item2 + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + GridLayout { + id: gridLayout3 + anchors.rightMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.topMargin: 0 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + id: label2 + text: qsTr("Latitude") + } + + TextField { + id: latitude + Layout.fillWidth: true + } + + Label { + id: label3 + text: qsTr("Longitude") + } + + TextField { + id: longitude + Layout.fillWidth: true + placeholderText: qsTr("") + } + + Label { + id: label4 + text: qsTr("Radius (m)") + } + + TextField { + id: radius + Layout.fillWidth: true + } + + RowLayout { + id: rowLayout1 + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignRight + + Button { + id: goButton + text: qsTr("Set") + } + + Button { + id: clearButton + text: qsTr("Clear") + } + + Button { + id: cancelButton + text: qsTr("Cancel") + } + } + Item { + Layout.fillHeight: true + Layout.columnSpan: 2 + } + } + } +} diff --git a/examples/location/places/forms/SearchCenter.qml b/examples/location/places/forms/SearchCenter.qml new file mode 100644 index 0000000..b8fbda6 --- /dev/null +++ b/examples/location/places/forms/SearchCenter.qml @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtPositioning 5.5 + +SearchCenterForm { + property string title; + property variant coordinate + signal changeSearchCenter(variant coordinate) + signal closeForm() + + goButton.onClicked: { + var coordinate = QtPositioning.coordinate(parseFloat(latitude.text), + parseFloat(longitude.text)); + if (coordinate.isValid) + changeSearchCenter(coordinate) + } + + clearButton.onClicked: { + latitude.text = "" + longitude.text = "" + } + + cancelButton.onClicked: closeForm() + + Component.onCompleted: { + latitude.text = "" + coordinate.latitude + longitude.text = "" + coordinate.longitude + if (title.length != 0) + tabTitle.text = title; + } +} diff --git a/examples/location/places/forms/SearchCenterForm.ui.qml b/examples/location/places/forms/SearchCenterForm.ui.qml new file mode 100644 index 0000000..f303b07 --- /dev/null +++ b/examples/location/places/forms/SearchCenterForm.ui.qml @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + property alias clearButton: clearButton + property alias goButton: goButton + property alias longitude: longitude + property alias latitude: latitude + property alias cancelButton: cancelButton + property alias tabTitle: tabTitle + Rectangle { + id: tabRectangle + y: 20 + height: tabTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: qsTr("Search Center") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + id: item2 + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + GridLayout { + id: gridLayout3 + anchors.rightMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.topMargin: 0 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + id: label2 + text: qsTr("Latitude") + } + + TextField { + id: latitude + Layout.fillWidth: true + } + + Label { + id: label3 + text: qsTr("Longitude") + } + + TextField { + id: longitude + Layout.fillWidth: true + placeholderText: qsTr("") + } + + RowLayout { + id: rowLayout1 + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignRight + + Button { + id: goButton + text: qsTr("Set") + } + + Button { + id: clearButton + text: qsTr("Clear") + } + + Button { + id: cancelButton + text: qsTr("Cancel") + } + } + Item { + Layout.fillHeight: true + Layout.columnSpan: 2 + } + } + } +} diff --git a/examples/location/places/forms/SearchOptions.qml b/examples/location/places/forms/SearchOptions.qml new file mode 100644 index 0000000..4411539 --- /dev/null +++ b/examples/location/places/forms/SearchOptions.qml @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 +import QtPositioning 5.5 + +SearchOptionsForm { + id: root + property Plugin plugin + property PlaceSearchModel model + + signal changeSearchSettings(bool orderByDistance, + bool orderByName, + string locales) + signal closeForm() + + setButton.onClicked: changeSearchSettings(distanceOrderButton.checked, + nameOrderButton.checked, + locales.text) + + clearButton.onClicked: { + locales.text = "" + distanceOrderButton.checked = false + nameOrderButton.checked = false + } + + cancelButton.onClicked: { + closeForm() + } + + Component.onCompleted: { + locales.visible = root.plugin != null && root.plugin.supportsPlaces(Plugin.LocalizedPlacesFeature); + favoritesButton.visible = false; +// favoritesButton.enabled = placeSearchModel.favoritesPlugin !== null) +// isFavoritesEnabled = true; + locales.text = root.plugin.locales.join(Qt.locale().groupSeparator); + distanceOrderButton.checked = model.relevanceHint == PlaceSearchModel.DistanceHint + nameOrderButton.checked = model.relevanceHint == PlaceSearchModel.LexicalPlaceNameHint + } +} diff --git a/examples/location/places/forms/SearchOptionsForm.ui.qml b/examples/location/places/forms/SearchOptionsForm.ui.qml new file mode 100644 index 0000000..945a389 --- /dev/null +++ b/examples/location/places/forms/SearchOptionsForm.ui.qml @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + property alias clearButton: clearButton + property alias setButton: setButton + property alias cancelButton: cancelButton + property alias tabTitle: tabTitle + property alias orderGroup: orderGroup + property alias distanceOrderButton: distanceOrderButton + property alias nameOrderButton: nameOrderButton + property alias favoritesButton: favoritesButton + property alias locales: locales + + Rectangle { + id: tabRectangle + y: 20 + height: tabTitle.height * 2 + color: "#46a2da" + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: tabTitle + color: "#ffffff" + text: qsTr("Search Options") + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Item { + id: item2 + anchors.rightMargin: 20 + anchors.leftMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: tabRectangle.bottom + + GridLayout { + id: gridLayout3 + anchors.rightMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.topMargin: 0 + rowSpacing: 10 + rows: 1 + columns: 2 + anchors.fill: parent + + Label { + id: label + text: qsTr("Locale(s)") + visible: locales.visible + } + + TextField { + id: locales + Layout.fillWidth: true + placeholderText: qsTr("") + } + + RadioButton { + id: favoritesButton + text: qsTr("Enable favorites") + Layout.columnSpan: 2 + } + + ExclusiveGroup { id: orderGroup } + RadioButton { + id: distanceOrderButton + text: qsTr("Order by distance") + exclusiveGroup: orderGroup + Layout.columnSpan: 2 + } + + RadioButton { + id: nameOrderButton + text: qsTr("Order by name") + exclusiveGroup: orderGroup + Layout.columnSpan: 2 + } + + RowLayout { + id: rowLayout1 + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignRight + + Button { + id: setButton + text: qsTr("Set") + } + + Button { + id: clearButton + text: qsTr("Clear") + } + + Button { + id: cancelButton + text: qsTr("Cancel") + } + } + + Item { + Layout.fillHeight: true + Layout.columnSpan: 2 + } + + + } + } +} diff --git a/examples/location/places/helper.js b/examples/location/places/helper.js new file mode 100644 index 0000000..5b09022 --- /dev/null +++ b/examples/location/places/helper.js @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +.pragma library + +function formatDistance(distance) +{ + if (distance < 1000) + return distance.toFixed(0) + " m"; + + var km = distance/1000; + if (km < 10) + return km.toFixed(1) + " km"; + + return km.toFixed(0) + " km"; +} diff --git a/examples/location/places/items/MainMenu.qml b/examples/location/places/items/MainMenu.qml new file mode 100644 index 0000000..9785c18 --- /dev/null +++ b/examples/location/places/items/MainMenu.qml @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtLocation 5.6 + +MenuBar { + property variant providerMenu: providerMenu + property variant settingsMenu: settingsMenu + + signal selectProvider(string providerName) + signal selectSetting(string setting); + + + + Menu { + id: providerMenu + title: qsTr("Provider") + + function createMenu(plugins) + { + clear() + for (var i = 0; i < plugins.length; i++) { + createProviderMenuItem(plugins[i]); + } + } + + function createProviderMenuItem(provider) + { + var item = addItem(provider); + item.checkable = true; + item.triggered.connect(function(){selectProvider(provider)}) + } + } + + Menu { + id: settingsMenu + title: qsTr("Settings") + + function createMenu(map) + { + clear() + var item = addItem(qsTr("Search Center")); + item.triggered.connect(function(){selectSetting("searchCenter")}) + item = addItem(qsTr("Search Bounding Box")); + item.triggered.connect(function(){selectSetting("searchBoundingBox")}) + item = addItem(qsTr("Search Bounding Circle")); + item.triggered.connect(function(){selectSetting("searchBoundingCircle")}) + item = addItem(qsTr("Search Options")); + item.triggered.connect(function(){selectSetting("SearchOptions")}) + } + } +} diff --git a/examples/location/places/items/MapComponent.qml b/examples/location/places/items/MapComponent.qml new file mode 100644 index 0000000..0be555d --- /dev/null +++ b/examples/location/places/items/MapComponent.qml @@ -0,0 +1,214 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtPositioning 5.5 +import QtLocation 5.6 +import "../helper.js" as Helper + +Map { + id: map + property bool followme: false + property variant scaleLengths: [5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000] + + function calculateScale() + { + var coord1, coord2, dist, text, f + f = 0 + coord1 = map.toCoordinate(Qt.point(0,scale.y)) + coord2 = map.toCoordinate(Qt.point(0+scaleImage.sourceSize.width,scale.y)) + dist = Math.round(coord1.distanceTo(coord2)) + + if (dist === 0) { + // not visible + } else { + for (var i = 0; i < scaleLengths.length-1; i++) { + if (dist < (scaleLengths[i] + scaleLengths[i+1]) / 2 ) { + f = scaleLengths[i] / dist + dist = scaleLengths[i] + break; + } + } + if (f === 0) { + f = dist / scaleLengths[i] + dist = scaleLengths[i] + } + } + + text = Helper.formatDistance(dist) + scaleImage.width = (scaleImage.sourceSize.width * f) - 2 * scaleImageLeft.sourceSize.width + scaleText.text = text + } + + center { + // The Qt Company in Oslo + latitude: 59.9485 + longitude: 10.7686 + } + + gesture.flickDeceleration: 3000 + gesture.enabled: true + onCopyrightLinkActivated: Qt.openUrlExternally(link) + + onCenterChanged:{ + scaleTimer.restart() + if (map.followme) + if (map.center != positionSource.position.coordinate) map.followme = false + } + + onZoomLevelChanged:{ + scaleTimer.restart() + if (map.followme) map.center = positionSource.position.coordinate + } + + onWidthChanged:{ + scaleTimer.restart() + } + + onHeightChanged:{ + scaleTimer.restart() + } + + Keys.onPressed: { + if (event.key === Qt.Key_Plus) { + map.zoomLevel++ + } else if (event.key === Qt.Key_Minus) { + map.zoomLevel-- + } + } + + Timer { + id: scaleTimer + interval: 100 + running: false + repeat: false + onTriggered: { + map.calculateScale() + } + } + + Item { + id: scale + visible: scaleText.text != "0 m" + z: map.z + 3 + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: 20 + height: scaleText.height * 2 + width: scaleImage.width + + Image { + id: scaleImageLeft + source: "../../resources/scale_end.png" + anchors.bottom: parent.bottom + anchors.right: scaleImage.left + } + Image { + id: scaleImage + source: "../../resources/scale.png" + anchors.bottom: parent.bottom + anchors.right: scaleImageRight.left + } + Image { + id: scaleImageRight + source: "../../resources/scale_end.png" + anchors.bottom: parent.bottom + anchors.right: parent.right + } + Label { + id: scaleText + color: "#004EAE" + anchors.centerIn: parent + text: "0 m" + } + Component.onCompleted: { + map.calculateScale(); + } + } + + MapQuickItem { + id: poiTheQtComapny + sourceItem: Rectangle { width: 14; height: 14; color: "#e41e25"; border.width: 2; border.color: "white"; smooth: true; radius: 7 } + coordinate { + latitude: 59.9485 + longitude: 10.7686 + } + opacity:1.0 + anchorPoint: Qt.point(sourceItem.width/2, sourceItem.height/2) + } + + MapQuickItem { + sourceItem: Text{ + text: "The Qt Company" + color:"#242424" + font.bold: true + styleColor: "#ECECEC" + style: Text.Outline + } + coordinate: poiTheQtComapny.coordinate + anchorPoint: Qt.point(-poiTheQtComapny.sourceItem.width * 0.5,poiTheQtComapny.sourceItem.height * 1.5) + } + + PositionSource{ + id: positionSource + active: followme + + onPositionChanged: { + map.center = positionSource.position.coordinate + } + } + + Slider { + id: zoomSlider; + z: map.z + 3 + minimumValue: map.minimumZoomLevel; + maximumValue: map.maximumZoomLevel; + anchors.margins: 10 + anchors.bottom: scale.top + anchors.top: parent.top + anchors.right: parent.right + orientation : Qt.Vertical + value: map.zoomLevel + onValueChanged: { + map.zoomLevel = value + } + } +} diff --git a/examples/location/places/items/SearchBar.qml b/examples/location/places/items/SearchBar.qml new file mode 100644 index 0000000..e5a3b33 --- /dev/null +++ b/examples/location/places/items/SearchBar.qml @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +ToolBar { + + property bool busyIndicatorRunning : false + property bool searchBarVisbile: true + + signal doSearch(string searchText) + signal searchTextChanged(string searchText) + signal showCategories() + signal goBack() + signal showMap() + + onSearchBarVisbileChanged: { + searchBar.opacity = searchBarVisbile ? 1 : 0 + backBar.opacity = searchBarVisbile ? 0 : 1 + } + + function showSearch(text) { + if (text != null) { + searchText.ignoreTextChange = true + searchText.text = text + searchText.ignoreTextChange = false + } + } + + RowLayout { + id: searchBar + width: parent.width + height: parent.height + Behavior on opacity { NumberAnimation{} } + visible: opacity ? true : false + TextField { + id: searchText + Behavior on opacity { NumberAnimation{} } + visible: opacity ? true : false + property bool ignoreTextChange: false + placeholderText: qsTr("Type place...") + Layout.fillWidth: true + onTextChanged: { + if (!ignoreTextChange) + searchTextChanged(text) + } + onAccepted: doSearch(searchText.text) + } + ToolButton { + id: searchButton + iconSource: "../../resources/search.png" + onClicked: doSearch(searchText.text) + } + ToolButton { + id: categoryButton + iconSource: "../../resources/categories.png" + onClicked: showCategories() + } + } + + RowLayout { + id: backBar + width: parent.width + height: parent.height + opacity: 0 + Behavior on opacity { NumberAnimation{} } + visible: opacity ? true : false + ToolButton { + id: backButton + iconSource: "../../resources/left.png" + onClicked: goBack() + } + ToolButton { + id: mapButton + iconSource: "../../resources/search.png" + onClicked: showMap() + } + Item { + Layout.fillWidth: true + } + } +} + diff --git a/examples/location/places/main.cpp b/examples/location/places/main.cpp new file mode 100644 index 0000000..8c3b64e --- /dev/null +++ b/examples/location/places/main.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +static bool parseArgs(QStringList& args, QVariantMap& parameters) +{ + + while (!args.isEmpty()) { + + QString param = args.takeFirst(); + + if (param.startsWith("--help")) { + QTextStream out(stdout); + out << "Usage: " << endl; + out << "--plugin. - Sets parameter = value for plugin" << endl; + out.flush(); + return true; + } + + if (param.startsWith("--plugin.")) { + + param.remove(0, 9); + + if (args.isEmpty() || args.first().startsWith("--")) { + parameters[param] = true; + } else { + + QString value = args.takeFirst(); + + if (value == "true" || value == "on" || value == "enabled") { + parameters[param] = true; + } else if (value == "false" || value == "off" + || value == "disable") { + parameters[param] = false; + } else { + parameters[param] = value; + } + } + } + } + return false; +} + +int main(int argc, char *argv[]) +{ + QGuiApplication application(argc, argv); + + QVariantMap parameters; + QStringList args(QCoreApplication::arguments()); + + if (parseArgs(args, parameters)) + return 0; + + QQmlApplicationEngine engine; + engine.addImportPath(QStringLiteral(":/imports")); + engine.load(QUrl(QStringLiteral("qrc:///places.qml"))); + QObject::connect(&engine, SIGNAL(quit()), qApp, SLOT(quit())); + + QObject *item = engine.rootObjects().first(); + Q_ASSERT(item); + + QMetaObject::invokeMethod(item, "initializeProviders", + Q_ARG(QVariant, QVariant::fromValue(parameters))); + + return application.exec(); +} diff --git a/examples/location/places/places.pro b/examples/location/places/places.pro new file mode 100644 index 0000000..baeef40 --- /dev/null +++ b/examples/location/places/places.pro @@ -0,0 +1,44 @@ +TARGET = qml_location_places +TEMPLATE = app + +QT += qml quick network positioning location +SOURCES += main.cpp + +RESOURCES += \ + places.qrc + +OTHER_FILES += \ + places.qml \ + helper.js \ + items/MainMenu.qml \ + items/SearchBar.qml \ + items/MapComponent.qml \ + forms/Message.qml \ + forms/MessageForm.ui.qml \ + forms/SearchCenter.qml \ + forms/SearchCenterForm.ui.qml \ + forms/SearchBoundingBox.qml \ + forms/SearchBoundingBoxForm.ui.qml \ + forms/SearchBoundingCircle.qml \ + forms/SearchBoundingCircleForm.ui.qml \ + forms/PlaceDetails.qml \ + forms/PlaceDetailsForm.ui.qml \ + forms/SearchOptions.qml \ + forms/SearchOptionsForm.ui.qml \ + views/SuggestionView.qml \ + views/RatingView.qml \ + views/CategoryView.qml \ + views/CategoryDelegate.qml \ + views/SearchResultDelegate.qml \ + views/SearchResultView.qml \ + views/EditorialView.qml \ + views/EditorialDelegate.qml \ + views/EditorialPage.qml \ + views/ReviewView.qml \ + views/ReviewDelegate.qml \ + views/ReviewPage.qml \ + views/ImageView.qml + +target.path = $$[QT_INSTALL_EXAMPLES]/location/places +INSTALLS += target + diff --git a/examples/location/places/places.qml b/examples/location/places/places.qml new file mode 100644 index 0000000..0141fbb --- /dev/null +++ b/examples/location/places/places.qml @@ -0,0 +1,497 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 +import QtPositioning 5.5 +import QtLocation 5.6 +import "items" + +ApplicationWindow { + id: appWindow + property Map map + property variant parameters + property variant searchLocation: map ? map.center : QtPositioning.coordinate() + property variant searchRegion: QtPositioning.circle(searchLocation) + property variant searchRegionItem + + property Plugin favoritesPlugin + + function getPlugins() { + var plugin = Qt.createQmlObject('import QtLocation 5.3; Plugin {}', appWindow); + var myArray = new Array; + for (var i = 0; i < plugin.availableServiceProviders.length; i++) { + var tempPlugin = Qt.createQmlObject ('import QtLocation 5.3; Plugin {name: "' + plugin.availableServiceProviders[i]+ '"}', appWindow) + + if (tempPlugin.supportsPlaces() && tempPlugin.supportsMapping() ) + myArray.push(tempPlugin.name) + } + myArray.sort() + return myArray; + } + + function initializeProviders(pluginParameters) + { + var parameters = new Array() + for (var prop in pluginParameters) { + var parameter = Qt.createQmlObject('import QtLocation 5.3; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}',appWindow) + parameters.push(parameter) + } + appWindow.parameters = parameters + var plugins = getPlugins() + mainMenu.providerMenu.createMenu(plugins) + for (var i = 0; i0) + plugin = Qt.createQmlObject ('import QtLocation 5.3; Plugin{ name:"' + provider + '"; parameters: appWindow.parameters}', appWindow) + else + plugin = Qt.createQmlObject ('import QtLocation 5.3; Plugin{ name:"' + provider + '"}', appWindow) + + if (map) + map.destroy(); + map = mapComponent.createObject(page); + map.plugin = plugin; + map.zoomLevel = (map.maximumZoomLevel - map.minimumZoomLevel)/2 + categoryModel.plugin = plugin; + categoryModel.update(); + placeSearchModel.plugin = plugin; + suggestionModel.plugin = plugin; + } + + title: qsTr("Places") + width: 360 + height: 640 + visible: true + menuBar: mainMenu + toolBar: searchBar + + MainMenu { + id: mainMenu + onSelectProvider: { + stackView.pop(page) + for (var i = 0; i < providerMenu.items.length; i++) { + providerMenu.items[i].checked = providerMenu.items[i].text === providerName + } + + createMap(providerName) + if (map.error === Map.NoError) { + settingsMenu.createMenu(map); + } else { + settingsMenu.clear(); + } + } + onSelectSetting: { + stackView.pop({tem:page,immediate: true}) + switch (setting) { + case "searchCenter": + stackView.push({ item: Qt.resolvedUrl("forms/SearchCenter.qml") , + properties: { "coordinate": map.center}}) + stackView.currentItem.changeSearchCenter.connect(stackView.changeSearchCenter) + stackView.currentItem.closeForm.connect(stackView.closeForm) + break + case "searchBoundingBox": + stackView.push({ item: Qt.resolvedUrl("forms/SearchBoundingBox.qml") , + properties: { "searchRegion": searchRegion}}) + stackView.currentItem.changeSearchBoundingBox.connect(stackView.changeSearchBoundingBox) + stackView.currentItem.closeForm.connect(stackView.closeForm) + break + case "searchBoundingCircle": + stackView.push({ item: Qt.resolvedUrl("forms/SearchBoundingCircle.qml") , + properties: { "searchRegion": searchRegion}}) + stackView.currentItem.changeSearchBoundingCircle.connect(stackView.changeSearchBoundingCircle) + stackView.currentItem.closeForm.connect(stackView.closeForm) + break + case "SearchOptions": + stackView.push({ item: Qt.resolvedUrl("forms/SearchOptions.qml") , + properties: { "plugin": map.plugin, + "model": placeSearchModel}}) + stackView.currentItem.changeSearchSettings.connect(stackView.changeSearchSettings) + stackView.currentItem.closeForm.connect(stackView.closeForm) + break + default: + console.log("Unsupported setting !") + } + } + } + + //! [PlaceSearchSuggestionModel search text changed 1] + SearchBar { + id: searchBar + //! [PlaceSearchSuggestionModel search text changed 1] + width: appWindow.width + searchBarVisbile: stackView.depth > 1 && + stackView.currentItem && + stackView.currentItem.objectName != "suggestionView" ? false : true + onShowCategories: { + if (map && map.plugin) { + stackView.pop({tem:page,immediate: true}) + stackView.enterCategory() + } + } + onGoBack: stackView.pop() + //! [PlaceSearchSuggestionModel search text changed 2] + onSearchTextChanged: { + if (searchText.length >= 3 && suggestionModel != null) { + suggestionModel.searchTerm = searchText; + suggestionModel.update(); + } + } + //! [PlaceSearchSuggestionModel search text changed 2] + onDoSearch: { + if (searchText.length > 0) + placeSearchModel.searchForText(searchText); + } + onShowMap: stackView.pop(page) + //! [PlaceSearchSuggestionModel search text changed 3] + } + //! [PlaceSearchSuggestionModel search text changed 3] + + StackView { + id: stackView + + function showMessage(title,message,backPage) + { + push({ item: Qt.resolvedUrl("forms/Message.qml") , + properties: { + "title" : title, + "message" : message, + "backPage" : backPage + }}) + currentItem.closeForm.connect(closeMessage) + } + + function closeMessage(backPage) + { + pop(backPage) + } + + function closeForm() + { + pop(page) + } + + function enterCategory(index) + { + push({ item: Qt.resolvedUrl("views/CategoryView.qml") , + properties: { "categoryModel": categoryModel, + "rootIndex" : index + }}) + currentItem.showSubcategories.connect(stackView.enterCategory) + currentItem.searchCategory.connect(placeSearchModel.searchForCategory) + } + + function showSuggestions() + { + if (currentItem.objectName != "suggestionView") { + stackView.pop(page) + push({ item: Qt.resolvedUrl("views/SuggestionView.qml") , + properties: { "suggestionModel": suggestionModel } + }) + currentItem.objectName = "suggestionView" + currentItem.suggestionSelected.connect(searchBar.showSearch) + currentItem.suggestionSelected.connect(placeSearchModel.searchForText) + } + } + + function showPlaces() + { + if (currentItem.objectName != "searchResultView") { + stackView.pop({tem:page,immediate: true}) + push({ item: Qt.resolvedUrl("views/SearchResultView.qml") , + properties: { "placeSearchModel": placeSearchModel } + }) + currentItem.showPlaceDetails.connect(showPlaceDatails) + currentItem.showMap.connect(searchBar.showMap) + currentItem.objectName = "searchResultView" + } + } + + function showPlaceDatails(place, distance) + { + push({ item: Qt.resolvedUrl("forms/PlaceDetails.qml") , + properties: { "place": place, + "distanceToPlace": distance } + }) + currentItem.searchForSimilar.connect(searchForSimilar) + currentItem.showReviews.connect(showReviews) + currentItem.showEditorials.connect(showEditorials) + currentItem.showImages.connect(showImages) + } + + function showEditorials(place) + { + push({ item: Qt.resolvedUrl("views/EditorialView.qml") , + properties: { "place": place } + }) + currentItem.showEditorial.connect(showEditorial) + } + + function showReviews(place) + { + push({ item: Qt.resolvedUrl("views/ReviewView.qml") , + properties: { "place": place } + }) + currentItem.showReview.connect(showReview) + } + + function showImages(place) + { + push({ item: Qt.resolvedUrl("views/ImageView.qml") , + properties: { "place": place } + }) + } + + function showEditorial(editorial) + { + push({ item: Qt.resolvedUrl("views/EditorialPage.qml") , + properties: { "editorial": editorial } + }) + } + + function showReview(review) + { + push({ item: Qt.resolvedUrl("views/ReviewPage.qml") , + properties: { "review": review } + }) + } + + function changeSearchCenter(coordinate) + { + stackView.pop(page) + map.center = coordinate; + if (searchRegionItem) { + map.removeMapItem(searchRegionItem); + searchRegionItem.destroy(); + } + } + + function changeSearchBoundingBox(coordinate,widthDeg,heightDeg) + { + stackView.pop(page) + map.center = coordinate + searchRegion = QtPositioning.rectangle(map.center, widthDeg, heightDeg) + if (searchRegionItem) { + map.removeMapItem(searchRegionItem); + searchRegionItem.destroy(); + } + searchRegionItem = Qt.createQmlObject('import QtLocation 5.3; MapRectangle { color: "#46a2da"; border.color: "#190a33"; border.width: 2; opacity: 0.25 }', page, "MapRectangle"); + searchRegionItem.topLeft = searchRegion.topLeft; + searchRegionItem.bottomRight = searchRegion.bottomRight; + map.addMapItem(searchRegionItem); + } + + function changeSearchBoundingCircle(coordinate,radius) + { + stackView.pop(page) + map.center = coordinate; + searchRegion = QtPositioning.circle(coordinate, radius) + + if (searchRegionItem) { + map.removeMapItem(searchRegionItem); + searchRegionItem.destroy(); + } + searchRegionItem = Qt.createQmlObject('import QtLocation 5.3; MapCircle { color: "#46a2da"; border.color: "#190a33"; border.width: 2; opacity: 0.25 }', page, "MapRectangle"); + searchRegionItem.center = searchRegion.center; + searchRegionItem.radius = searchRegion.radius; + map.addMapItem(searchRegionItem); + } + + function changeSearchSettings(orderByDistance, orderByName, locales) + { + stackView.pop(page) + /*if (isFavoritesEnabled) { + if (favoritesPlugin == null) + favoritesPlugin = Qt.createQmlObject('import QtLocation 5.3; Plugin { name: "places_jsondb" }', page); + favoritesPlugin.parameters = pluginParametersFromMap(pluginParameters); + placeSearchModel.favoritesPlugin = favoritesPlugin; + } else { + placeSearchModel.favoritesPlugin = null; + }*/ + placeSearchModel.favoritesPlugin = null; + + placeSearchModel.relevanceHint = orderByDistance ? PlaceSearchModel.DistanceHint : + orderByName ? PlaceSearchModel.LexicalPlaceNameHint : + PlaceSearchModel.UnspecifiedHint; + map.plugin.locales = locales.split(Qt.locale().groupSeparator); + } + + //! [PlaceRecommendationModel search] + function searchForSimilar(place) { + stackView.pop(page) + searchBar.showSearch(place.name) + placeSearchModel.searchForRecommendations(place.placeId); + } + //! [PlaceRecommendationModel search] + + anchors.fill: parent + focus: true + initialItem: Item { + id: page + + //! [PlaceSearchModel model] + PlaceSearchModel { + id: placeSearchModel + searchArea: searchRegion + + function searchForCategory(category) { + searchTerm = ""; + categories = category; + recommendationId = ""; + searchArea = searchRegion + limit = -1; + update(); + } + + function searchForText(text) { + searchTerm = text; + categories = null; + recommendationId = ""; + searchArea = searchRegion + limit = -1; + update(); + } + + function searchForRecommendations(placeId) { + searchTerm = ""; + categories = null; + recommendationId = placeId; + searchArea = null; + limit = -1; + update(); + } + + onStatusChanged: { + switch (status) { + case PlaceSearchModel.Ready: + if (count > 0) + stackView.showPlaces() + else + stackView.showMessage(qsTr("Search Place Error"),qsTr("Place not found !")) + break; + case PlaceSearchModel.Error: + stackView.showMessage(qsTr("Search Place Error"),errorString()) + break; + } + } + } + //! [PlaceSearchModel model] + + //! [PlaceSearchSuggestionModel model] + PlaceSearchSuggestionModel { + id: suggestionModel + searchArea: searchRegion + + onStatusChanged: { + if (status == PlaceSearchSuggestionModel.Ready) + stackView.showSuggestions() + } + } + //! [PlaceSearchSuggestionModel model] + + //! [CategoryModel model] + CategoryModel { + id: categoryModel + hierarchical: true + } + //! [CategoryModel model] + + Component { + id: mapComponent + + MapComponent { + width: page.width + height: page.height + + onErrorChanged: { + if (map.error != Map.NoError) { + var title = qsTr("ProviderError"); + var message = map.errorString + "

" + qsTr("Try to select other provider") + ""; + if (map.error == Map.MissingRequiredParameterError) + message += "
" + qsTr("or see") + " \'mapviewer --help\' " + + qsTr("how to pass plugin parameters."); + stackView.showMessage(title,message); + } + } + + MapItemView { + model: placeSearchModel + delegate: MapQuickItem { + coordinate: model.type === PlaceSearchModel.PlaceResult ? place.location.coordinate : QtPositioning.coordinate() + + visible: model.type === PlaceSearchModel.PlaceResult + + anchorPoint.x: image.width * 0.28 + anchorPoint.y: image.height + + sourceItem: Image { + id: image + source: "resources/marker.png" + MouseArea { + anchors.fill: parent + onClicked: stackView.showPlaceDatails(model.place,model.distance) + } + } + } + } + } + } + } + } + + Rectangle { + color: "white" + opacity: busyIndicator.running ? 0.8 : 0 + anchors.fill: parent + Behavior on opacity { NumberAnimation{} } + } + BusyIndicator { + id: busyIndicator + anchors.centerIn: parent + running: placeSearchModel.status == PlaceSearchModel.Loading || + categoryModel.status === CategoryModel.Loading + } +} diff --git a/examples/location/places/places.qrc b/examples/location/places/places.qrc new file mode 100644 index 0000000..42026a6 --- /dev/null +++ b/examples/location/places/places.qrc @@ -0,0 +1,42 @@ + + + places.qml + helper.js + items/MainMenu.qml + items/MapComponent.qml + items/SearchBar.qml + forms/Message.qml + forms/MessageForm.ui.qml + forms/SearchCenter.qml + forms/SearchCenterForm.ui.qml + forms/SearchBoundingBox.qml + forms/SearchBoundingBoxForm.ui.qml + forms/SearchBoundingCircle.qml + forms/SearchBoundingCircleForm.ui.qml + forms/SearchOptions.qml + forms/SearchOptionsForm.ui.qml + forms/PlaceDetails.qml + forms/PlaceDetailsForm.ui.qml + views/SuggestionView.qml + views/CategoryDelegate.qml + views/CategoryView.qml + views/EditorialDelegate.qml + views/EditorialPage.qml + views/EditorialView.qml + views/ImageView.qml + views/RatingView.qml + views/ReviewDelegate.qml + views/ReviewPage.qml + views/ReviewView.qml + views/SearchResultDelegate.qml + views/SearchResultView.qml + resources/categories.png + resources/left.png + resources/marker.png + resources/right.png + resources/scale.png + resources/scale_end.png + resources/search.png + resources/star.png + + diff --git a/examples/location/places/resources/categories.png b/examples/location/places/resources/categories.png new file mode 100644 index 0000000000000000000000000000000000000000..b2d73ea0e0c017e24d80de0e8129465db9e44e2e GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2JUv|;Lp07OCrJ1lxN%?yBfp75 z=$(W=^87LWU61~*=o0>*mA4=~gGJ)~ac1TK(f8^H&o0qaJ1S|y!yBa3y5)tMrsBj& cF6>ea3V@;|>0q(VGh978nDCnq>C9%DPk#q*=F zu<2v{vKY_M*#Ra+je56GQC1n%&V{JH7+WXYh3Ob6Mw<&;$TifHC#} literal 0 HcmV?d00001 diff --git a/examples/location/places/resources/marker.png b/examples/location/places/resources/marker.png new file mode 100644 index 0000000000000000000000000000000000000000..2116dfdf51bfb8ac9556af035d598cf2c68c44e3 GIT binary patch literal 752 zcmVP001Be1^@s6=bY090008FNklP9F+g_dGL z7b4wOL~YbX6}NhC`Y^Yem;_BVH^=jjGv%gj-gDq^?<8~n@6OCQGh-NK7!h@__p?7@ zAHg%1%k{H&7`2$=I6)SB$ey9%mf^nW7pw@t0liA$uW3=@<{h$6c2acLUNScZ#1n1& zj{lW0thX!xPr(xr5KoZZd4{Yl+b9~?SDiKsYw4jl=ahsz49ascH$lOp{)gvA{a+7Rbz6(!?wYhMaEN_*&FvlTY+Qe#GeLd5Y`1f1;(W)TxhmI&f zGMmlT7dqEPnL5c-$rEwKC^iKt`y;L(O{LEFOX&6M`3tgeo|g$ok3Ur@d#&7y#A^h6 z?>^6`UFq7fPoGtQPnFzO*2?UlncYoA0W+aJ?(31K(tFo2L@iE#B&)%{`ZRITEx!AZ zU)hx!$Bv5w?bcmR&+YjDzHG#=UYFZb?pf8h$W$s-$07?*nr~~*-vC?M)D-<;udDUC zx(`a(n9X1Re>5i2b#_ic_8U43M<~(&X=?Gdk$OC$)?v`lX*}=K5dL}Zz8b-2L$~o) id-3sdrSb5U8~YFSpzHYmbwF4E0000V@;|>0q@p}s978nDCnp?WIL391jps+> z#>S7;kN+Rz{2}_5UF)a6!^}da5W(CjlH3yJ360MKr$|~K*tuf?R literal 0 HcmV?d00001 diff --git a/examples/location/places/resources/scale.png b/examples/location/places/resources/scale.png new file mode 100644 index 0000000000000000000000000000000000000000..c4f08122ada97f0848409a7b4de914274d04d55e GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^ra;Wb!3HFgIj$80DNRoo#}JF&PQ<|Z&(Z(QQpe}ECj@=zX;NfjIC%2Ts>e~;XMma*JYD@<);T3K0RYVMA4vcJ literal 0 HcmV?d00001 diff --git a/examples/location/places/resources/scale_end.png b/examples/location/places/resources/scale_end.png new file mode 100644 index 0000000000000000000000000000000000000000..94510b1258e33726e65d4d5f539b8dc1dea5c905 GIT binary patch literal 93 zcmeAS@N?(olHy`uVBq!ia0vp^OhC-W0V2~}Z=?b#6;Bt(5RU7~DID*U6A}^<1tu^B pAL2^zV(}^D?rhdzjZ-+uz+kqK@vYpot*Suv44$rjF6*2UngECF7V7{2 literal 0 HcmV?d00001 diff --git a/examples/location/places/resources/search.png b/examples/location/places/resources/search.png new file mode 100644 index 0000000000000000000000000000000000000000..ce8c27aa6e387f5ac9cec3fe14c33dd99caca5d1 GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gy!2%?eOIddVsZ*XVjv*T7=T3^|J#4_^%Bv{A zwD6K*07t6;M{59EBaj56#s{@Jt8X2dy6t9sVc)txJCxD|EG|s5-fY8qne|V~v6~sL zfm{@G=xuWP8-|V zmG|wJW;?gYF71;}bqfqzJ;VA+V4&8%>nGmE{Zk4En3CIa_hnDBN7jN#9LGLb1f?%N z<(47GIc=`eOee+7k8ZPmt=u{-``1%&#EP)zqJjq^g28AcLO?%AlpqE~#2|^N zs~}!@=UUeLVnr84MWeBiUpdLIP)o*%drn_f*9Qd*BsMD|N)%B|0v}*PK zmCUQ5zE20x4-5f4t5Nvb;0AaCYC*4RQotneFYp)_>;i47LHHULRtfL(&n7Ua8WhkS zJk*5uH7i(A4GNeJvc~Q!&VsgSSHNCFXk8Jn!H6^q-wQkzc3-&yxxx2o4t}$MWB9%q z3$9a|Orru?gQJ4Lj$sjRz}Pe@;3M$V5SC2-f8o7NX;i=hVYj7BK->bI(=++UBE8;8hPVAsuriFK>>ro ze-`ssID%X3CJSom3iy_HR~O6|a2Z`uk6Lt}0E=%AE($v@D4D*#zz({#-D@E9TZ1~>dlHD*eoQ!bpzgW zOHfX!E>ozi&RS%lKsrw9*vtff7dWl?F~keMO#bf+X1^2kwxz@2oJ#TmA5y6T`vs2L zbR1s-+JzlnY%%hP?=4E`w;gaYoa(X zkxPu>GrpTZT(F%nXuSR%=oH7vu{ZeHQfzoJ20G#%m=NGR> zAgN1FzYFOvU&JBC6fjp{i0T=^aaIxv=nI|+>CbDX@In2%e1I|~rPapn26*6Hq5AW* zuB;Q{N=y@<-5rZH+iL$Hlc!zWtY3WKc=8DkIrAa=3}#jMQ=AHzB;@2msf`wS2h8AxYmM-^J$OKq z3E6ehX^8ELEw{{F%VIoXG)4!eej&TjQ~0u^8**Hys>0DpRe!c_4c!i(*WBH~zyFZC zPGK6cDr>BXLjfZ>dfTcuRn*W0g<&OLv6}-P6zD-J>LUN%5gxEwsK(4iQAA%^uKT`p zk%4b`IF>?+e!d9fW~DCXoW(E63@>m*uPO6$JA$QJ@g-#*dj6uQ70?*4>vRI#M3Fq= z?h{?-`BS)9scM9cQ7hmVq5P%vHZ`vA{c#s+trm;)P53hEhY#ReSWP@~-o{^sh_x1H zYoLj5J$S`m#iPMU_c$ItfLmchL(SWxT}uznOe13v{{1UuuXoV~ok6Cp 0 ? model.title : qsTr("Untitled editorial") + font.bold: true + + wrapMode: Text.WordWrap + elide: Text.ElideRight + maximumLineCount: 2 + } + + MouseArea { + anchors.fill: parent + onClicked: showEditorial() + } +} diff --git a/examples/location/places/views/EditorialPage.qml b/examples/location/places/views/EditorialPage.qml new file mode 100644 index 0000000..caf0b7a --- /dev/null +++ b/examples/location/places/views/EditorialPage.qml @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + id: root + property variant editorial + width: parent.width + height: parent.height + + ScrollView { + id: scrollView + flickableItem.interactive: true + anchors.fill: parent + anchors.margins: 15 + + ColumnLayout { + width: scrollView.width - 30 + spacing: 10 + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: "#46a2da" + } + + Label { + text: editorial.title + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + wrapMode: Text.WordWrap + textFormat: Text.RichText + } + + Label { + text: editorial.text + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + wrapMode: Text.WordWrap + textFormat: Text.RichText + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: "#46a2da" + } + + Image { + Layout.alignment: Qt.AlignHCenter + source: editorial.supplier.icon.url(Qt.size(width, height), Icon.List) + } + + Label { + text: editorial.supplier.name + Layout.alignment: Qt.AlignHCenter + wrapMode: Text.WordWrap + textFormat: Text.RichText + } + + Button { + id: button + text: qsTr("Open url") + Layout.alignment: Qt.AlignHCenter + onClicked: { + Qt.openUrlExternally(editorial.supplier.url) + } + } + } + } +} diff --git a/examples/location/places/views/EditorialView.qml b/examples/location/places/views/EditorialView.qml new file mode 100644 index 0000000..05d4523 --- /dev/null +++ b/examples/location/places/views/EditorialView.qml @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 + +//! [PlaceEditorialModel view] +ListView { + id:view + property Place place + signal showEditorial(variant editorial) + width: parent.width + height: parent.height + model: place.editorialModel + delegate: EditorialDelegate { + onShowEditorial: view.showEditorial(model) + } +} +//! [PlaceEditorialModel view] + diff --git a/examples/location/places/views/ImageView.qml b/examples/location/places/views/ImageView.qml new file mode 100644 index 0000000..a4495da --- /dev/null +++ b/examples/location/places/views/ImageView.qml @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 +import QtQuick.Controls 1.4 + +Item { + id: root + property Place place + width: parent.width + height: parent.height + + GridView { + id: gridView + + anchors.fill: parent + + model: place.imageModel + + cellWidth: width / 3 + cellHeight: cellWidth + + delegate: Rectangle { + width: gridView.cellWidth + height: gridView.cellHeight + + color: "#30FFFFFF" + + Image { + anchors.fill: parent + anchors.margins: 5 + + source: url + + fillMode: Image.PreserveAspectFit + } + + MouseArea { + anchors.fill: parent + onClicked: { + listView.positionViewAtIndex(index, ListView.Contain); + root.state = "list"; + } + } + } + } + + ListView { + id: listView + + anchors.top: parent.top + anchors.bottom: position.top + width: parent.width + spacing: 10 + + model: place.imageModel + orientation: ListView.Horizontal + snapMode: ListView.SnapOneItem + + visible: false + + delegate: Item { + width: listView.width + height: listView.height + + Image { + anchors.fill: parent + source: url + fillMode: Image.PreserveAspectFit + + MouseArea { + anchors.fill: parent + onClicked: root.state = "" + } + } + + Button { + id: button + text: qsTr("Open url") + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + Qt.openUrlExternally(supplier.url) + } + } + } + } + + Label { + id: position + + width: parent.width + anchors.bottom: parent.bottom + visible: listView.visible + + text: (listView.currentIndex + 1) + '/' + listView.model.totalCount + horizontalAlignment: Text.AlignRight + } + + states: [ + State { + name: "list" + PropertyChanges { + target: gridView + visible: false + } + PropertyChanges { + target: listView + visible: true + } + } + ] +} diff --git a/examples/location/places/views/RatingView.qml b/examples/location/places/views/RatingView.qml new file mode 100644 index 0000000..7751f64 --- /dev/null +++ b/examples/location/places/views/RatingView.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 + +Row { + property real rating: 0 + property int size: 0 + + Repeater { + model: Math.ceil(rating) + Image { + source: "../../resources/star.png" + width: size + height: size + } + } +} diff --git a/examples/location/places/views/ReviewDelegate.qml b/examples/location/places/views/ReviewDelegate.qml new file mode 100644 index 0000000..7f2ba64 --- /dev/null +++ b/examples/location/places/views/ReviewDelegate.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 +import QtQuick.Controls 1.4 + +Item { + id: root + signal showReview() + + width: parent.width + height: icon.height + 8 + + Image { + id: icon + + width: 64 + height: 64 + + anchors.verticalCenter: root.verticalCenter + anchors.left: root.left + anchors.leftMargin: 4 + + source: model.supplier.icon.url(Qt.size(64, 64), Icon.List) + fillMode: Image.PreserveAspectFit + } + + Label { + anchors.top: icon.top + anchors.topMargin: 4 + anchors.left: icon.right + anchors.leftMargin: 4 + anchors.right: root.right + anchors.rightMargin: 4 + + text: model.title + font.bold: true + + wrapMode: Text.WordWrap + elide: Text.ElideRight + maximumLineCount: 2 + } + + RatingView { + anchors.bottom: icon.bottom + anchors.bottomMargin: 4 + anchors.left: icon.right + anchors.leftMargin: 4 + anchors.right: root.right + anchors.rightMargin: 4 + + rating: model.rating + size: 16 + } + + MouseArea { + anchors.fill: parent + onClicked: showReview() + } +} diff --git a/examples/location/places/views/ReviewPage.qml b/examples/location/places/views/ReviewPage.qml new file mode 100644 index 0000000..d3ccf73 --- /dev/null +++ b/examples/location/places/views/ReviewPage.qml @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +Item { + id: root + property variant review + width: parent.width + height: parent.height + + ScrollView { + id: scrollView + flickableItem.interactive: true + anchors.fill: parent + anchors.margins: 15 + + ColumnLayout { + width: scrollView.width - 30 + spacing: 10 + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: "#46a2da" + } + + Label { + text: review.title + width: parent.width + wrapMode: Text.WordWrap + } + + Label { + text: Qt.formatDateTime(review.dateTime) + width: parent.width + Layout.alignment: Qt.AlignHCenter + } + + RatingView { + size: 16 + rating: review.rating + } + + Label { + text: review.text + width: parent.width + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter + textFormat: Text.RichText + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: "#46a2da" + } + + Image { + Layout.alignment: Qt.AlignHCenter + source: review.supplier.icon.url(Qt.size(width, height), Icon.List) + } + + Label { + text: editorial.supplier.name + Layout.alignment: Qt.AlignHCenter + wrapMode: Text.WordWrap + textFormat: Text.RichText + } + + Button { + id: button + text: qsTr("Open url") + Layout.alignment: Qt.AlignHCenter + onClicked: { + Qt.openUrlExternally(review.supplier.url) + } + } + } + } +} diff --git a/examples/location/places/views/ReviewView.qml b/examples/location/places/views/ReviewView.qml new file mode 100644 index 0000000..eb8727b --- /dev/null +++ b/examples/location/places/views/ReviewView.qml @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 + +//! [ReviewModel delegate] +ListView { + id:view + property Place place + signal showReview(variant review) + width: parent.width + height: parent.height + model: place.reviewModel + delegate: ReviewDelegate { + onShowReview: view.showReview(model) + } +} +//! [ReviewModel delegate] + diff --git a/examples/location/places/views/SearchResultDelegate.qml b/examples/location/places/views/SearchResultDelegate.qml new file mode 100644 index 0000000..a4ef9af --- /dev/null +++ b/examples/location/places/views/SearchResultDelegate.qml @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 +import "../helper.js" as Helper + +Item { + id: root + + signal showPlaceDetails(variant place,variant distance) + signal searchFor(string query) + + width: parent.width + height: childrenRect.height + + //! [PlaceSearchModel place delegate] + Component { + id: placeComponent + Item { + id: placeRoot + width: root.width + height: Math.max(icon.height, 3 * placeName.height) + + Rectangle { + anchors.fill: parent + color: "#44ffffff" + visible: mouse.pressed + } + + Rectangle { + anchors.fill: parent + color: "#dbffde" + visible: model.sponsored !== undefined ? model.sponsored : false + + Label { + text: qsTr("Sponsored result") + horizontalAlignment: Text.AlignRight + anchors.right: parent.right + anchors.bottom: parent.bottom + font.pixelSize: 8 + visible: model.sponsored !== undefined ? model.sponsored : false + } + } + + GridLayout { + rows: 2 + columns: 2 + anchors.fill: parent + anchors.leftMargin: 30 + flow: GridLayout.TopToBottom + + Image { + // anchors.verticalCenter: parent.verticalCenter + id:icon + source: place.favorite ? "../../resources/star.png" : place.icon.url() + Layout.rowSpan: 2 + } + + Label { + id: placeName + text: place.favorite ? place.favorite.name : place.name + Layout.fillWidth: true + } + + Label { + id: distanceText + font.italic: true + text: Helper.formatDistance(distance) + Layout.fillWidth: true + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 15 + height: 1 + color: "#46a2da" + } + + MouseArea { + id: mouse + anchors.fill: parent + onClicked: { + if (model.type === undefined || type === PlaceSearchModel.PlaceResult) { + if (!place.detailsFetched) + place.getDetails(); + root.showPlaceDetails(model.place, model.distance); + } + } + } + } + } + //! [PlaceSearchModel place delegate] + + Component { + id: proposedSearchComponent + + Item { + id: proposedSearchRoot + + width: root.width + height: Math.max(icon.height, 2 * proposedSearchTitle.height) + + Rectangle { + anchors.fill: parent + color: "#11ffffff" + visible: mouse.pressed + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 30 + + Image { + source: icon.url() + } + + Label { + id: proposedSearchTitle + anchors.verticalCenter: parent.verticalCenter + text: "Search for " + title + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 15 + height: 1 + color: "#46a2da" + } + + MouseArea { + anchors.fill: parent + onClicked: root.ListView.view.model.updateWith(index); + } + } + } + + Loader { + anchors.left: parent.left + anchors.right: parent.right + + sourceComponent: { + switch (model.type) { + case PlaceSearchModel.PlaceResult: + return placeComponent; + case PlaceSearchModel.ProposedSearchResult: + return proposedSearchComponent; + default: + //do nothing, don't assign component if result type not recognized + } + } + } +} diff --git a/examples/location/places/views/SearchResultView.qml b/examples/location/places/views/SearchResultView.qml new file mode 100644 index 0000000..1c2ee17 --- /dev/null +++ b/examples/location/places/views/SearchResultView.qml @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtLocation 5.6 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +//! [PlaceSearchModel place list] +ListView { + id: searchView + width: parent.width + height: parent.height + + property variant placeSearchModel + signal showPlaceDetails(variant place, variant distance) + signal showMap() + + model: placeSearchModel + delegate: SearchResultDelegate { + onShowPlaceDetails: searchView.showPlaceDetails(place, distance) + onSearchFor: placeSearchModel.searchForText(query); + } + + footer: + + RowLayout { + width: parent.width + + Button { + text: qsTr("Previous") + enabled: placeSearchModel.previousPagesAvailable + onClicked: placeSearchModel.previousPage() + Layout.alignment: Qt.AlignHCenter + } + + Button { + text: qsTr("Clear") + onClicked: { + placeSearchModel.reset() + showMap() + } + Layout.alignment: Qt.AlignHCenter + } + + Button { + text: qsTr("Next") + enabled: placeSearchModel.nextPagesAvailable + onClicked: placeSearchModel.nextPage() + Layout.alignment: Qt.AlignHCenter + } + } +} +//! [PlaceSearchModel place list] diff --git a/examples/location/places/views/SuggestionView.qml b/examples/location/places/views/SuggestionView.qml new file mode 100644 index 0000000..4ae9449 --- /dev/null +++ b/examples/location/places/views/SuggestionView.qml @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +//! [PlaceSearchSuggestionModel view 1] +ListView { + id: suggestionView + property variant suggestionModel + signal suggestionSelected(string text) +//! [PlaceSearchSuggestionModel view 1] + snapMode: ListView.SnapToItem +//! [PlaceSearchSuggestionModel view 2] + model: suggestionModel + delegate: Item { + width: parent.width + height: label.height * 1.5 + Label { + id: label + text: suggestion + } + MouseArea { + anchors.fill: parent + onClicked: suggestionSelected(suggestion) + } + } +} +//! [PlaceSearchSuggestionModel view 2] + diff --git a/examples/location/places_list/Marker.qml b/examples/location/places_list/Marker.qml new file mode 100644 index 0000000..7b73cde --- /dev/null +++ b/examples/location/places_list/Marker.qml @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + width: image.width + Image { + id: image + anchors.centerIn: parent + source: "marker.png" + Text{ + y: parent.height/10 + width: parent.width + color: "white" + font.bold: true + font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter + text: index + } + } +} diff --git a/examples/location/places_list/doc/images/places_list.png b/examples/location/places_list/doc/images/places_list.png new file mode 100644 index 0000000000000000000000000000000000000000..bf09a0314174aba8e59a2c609e563d2f0e0312a0 GIT binary patch literal 18195 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 M07*qoM6N<$g30|;@&Et; literal 0 HcmV?d00001 diff --git a/examples/location/places_list/doc/src/places_list.qdoc b/examples/location/places_list/doc/src/places_list.qdoc new file mode 100644 index 0000000..ee9925a --- /dev/null +++ b/examples/location/places_list/doc/src/places_list.qdoc @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example places_list + \title Places List (QML) + \ingroup qtlocation-examples + + \brief The Places List example demonstrates how to search for and display a list of places using a \l ListView. + \image places_list.png + + \include examples-run.qdocinc + + The \c {Places List} example demonstrates how to search for a list of places + in a certain area and displays the result using a \l ListView. In this particular case, a search + for places associated with the term \c pizza is performed. + + \section1 Performing a Place Search + + To write a QML application that will show places in a list, we start by + making the following import declarations. + + \snippet places_list/places_list.qml Imports + + Instantiate a \l Plugin instance. The \l Plugin is effectively the backend + from where places are sourced from. Depending on the type of the plugin, + some mandatory parameters may be need to be filled in. The most likely type + of PluginParameter are some form of service access token which are documented + in the service plugin. As an example see the \l + {Mandatory Parameters} {HERE Plugin} documentation. In this snippet the \c osm + plugin is used which does not require any further parameter: + + \snippet places_list/places_list.qml Initialize Plugin + + Next we instantiate a \l PlaceSearchModel which we can use to specify + search parameters and perform a places search operation. For illustrative + purposes, \l {PlaceSearchModel::update} {update()} is invoked once + construction of the model is complete. Typically \l + {PlaceSearchModel::update} {update()} would be invoked in response to a + user action such as a button click. + + \snippet places_list/places_list.qml PlaceSearchModel + + Finally we instantiate a \l ListView to show the search results found by + the model. An inline delegate has been used and we have assumed that + every search result is of \l {Search Result Types} {type} \c + PlaceSearchesult. Consequently it is assumed that we always have access to + the \e place \l {PlaceSearchModel Roles} {role}, other search result types + may not have a \e place \l {PlaceSearchModel Roles} {role}. + + \snippet places_list/places_list.qml Places ListView +*/ diff --git a/examples/location/places_list/main.cpp b/examples/location/places_list/main.cpp new file mode 100644 index 0000000..2db67f7 --- /dev/null +++ b/examples/location/places_list/main.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +int main(int argc, char **argv) +{ + QGuiApplication app(argc,argv); + QQuickView view; + view.setSource(QUrl(QStringLiteral("qrc:///places_list.qml"))); + view.show(); + return app.exec(); +} diff --git a/examples/location/places_list/marker.png b/examples/location/places_list/marker.png new file mode 100644 index 0000000000000000000000000000000000000000..2116dfdf51bfb8ac9556af035d598cf2c68c44e3 GIT binary patch literal 752 zcmVP001Be1^@s6=bY090008FNklP9F+g_dGL z7b4wOL~YbX6}NhC`Y^Yem;_BVH^=jjGv%gj-gDq^?<8~n@6OCQGh-NK7!h@__p?7@ zAHg%1%k{H&7`2$=I6)SB$ey9%mf^nW7pw@t0liA$uW3=@<{h$6c2acLUNScZ#1n1& zj{lW0thX!xPr(xr5KoZZd4{Yl+b9~?SDiKsYw4jl=ahsz49ascH$lOp{)gvA{a+7Rbz6(!?wYhMaEN_*&FvlTY+Qe#GeLd5Y`1f1;(W)TxhmI&f zGMmlT7dqEPnL5c-$rEwKC^iKt`y;L(O{LEFOX&6M`3tgeo|g$ok3Ur@d#&7y#A^h6 z?>^6`UFq7fPoGtQPnFzO*2?UlncYoA0W+aJ?(31K(tFo2L@iE#B&)%{`ZRITEx!AZ zU)hx!$Bv5w?bcmR&+YjDzHG#=UYFZb?pf8h$W$s-$07?*nr~~*-vC?M)D-<;udDUC zx(`a(n9X1Re>5i2b#_ic_8U43M<~(&X=?Gdk$OC$)?v`lX*}=K5dL}Zz8b-2L$~o) id-3sdrSb5U8~YFSpzHYmbwF4E0000 + + marker.png + places_list.qml + Marker.qml + + diff --git a/examples/location/places_map/doc/images/places_map.png b/examples/location/places_map/doc/images/places_map.png new file mode 100644 index 0000000000000000000000000000000000000000..4982df233d227d35680a3886cefa7e1a2416ef95 GIT binary patch literal 167995 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≤F)kLCqS(lL_LN_)Fjl%ZeL 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 +#include + +int main(int argc, char **argv) +{ + QGuiApplication app(argc,argv); + QQuickView view; + view.setSource(QUrl(QStringLiteral("qrc:///places_map.qml"))); + view.show(); + return app.exec(); +} diff --git a/examples/location/places_map/marker.png b/examples/location/places_map/marker.png new file mode 100644 index 0000000000000000000000000000000000000000..2116dfdf51bfb8ac9556af035d598cf2c68c44e3 GIT binary patch literal 752 zcmVP001Be1^@s6=bY090008FNklP9F+g_dGL z7b4wOL~YbX6}NhC`Y^Yem;_BVH^=jjGv%gj-gDq^?<8~n@6OCQGh-NK7!h@__p?7@ zAHg%1%k{H&7`2$=I6)SB$ey9%mf^nW7pw@t0liA$uW3=@<{h$6c2acLUNScZ#1n1& zj{lW0thX!xPr(xr5KoZZd4{Yl+b9~?SDiKsYw4jl=ahsz49ascH$lOp{)gvA{a+7Rbz6(!?wYhMaEN_*&FvlTY+Qe#GeLd5Y`1f1;(W)TxhmI&f zGMmlT7dqEPnL5c-$rEwKC^iKt`y;L(O{LEFOX&6M`3tgeo|g$ok3Ur@d#&7y#A^h6 z?>^6`UFq7fPoGtQPnFzO*2?UlncYoA0W+aJ?(31K(tFo2L@iE#B&)%{`ZRITEx!AZ zU)hx!$Bv5w?bcmR&+YjDzHG#=UYFZb?pf8h$W$s-$07?*nr~~*-vC?M)D-<;udDUC zx(`a(n9X1Re>5i2b#_ic_8U43M<~(&X=?Gdk$OC$)?v`lX*}=K5dL}Zz8b-2L$~o) id-3sdrSb5U8~YFSpzHYmbwF4E0000 500) { + // 500m from last performed pizza search + lastSearchPosition = currentPosition + searchModel.searchArea = QtPositioning.circle(currentPosition) + searchModel.update() + } + } + } + //! [Current Location] + + //! [PlaceSearchModel] + property variant locationOslo: QtPositioning.coordinate( 59.93, 10.76) + + PlaceSearchModel { + id: searchModel + + plugin: myPlugin + + searchTerm: "Pizza" + searchArea: QtPositioning.circle(locationOslo) + + Component.onCompleted: update() + } + //! [PlaceSearchModel] + + //! [Places MapItemView] + Map { + id: map + anchors.fill: parent + plugin: myPlugin; + center: locationOslo + zoomLevel: 13 + + MapItemView { + model: searchModel + delegate: MapQuickItem { + coordinate: place.location.coordinate + + anchorPoint.x: image.width * 0.5 + anchorPoint.y: image.height + + sourceItem: Column { + Image { id: image; source: "marker.png" } + Text { text: title; font.bold: true } + } + } + } + } + //! [Places MapItemView] + + Connections { + target: searchModel + onStatusChanged: { + if (searchModel.status == PlaceSearchModel.Error) + console.log(searchModel.errorString()); + } + } +} diff --git a/examples/location/places_map/places_map.qrc b/examples/location/places_map/places_map.qrc new file mode 100644 index 0000000..2239529 --- /dev/null +++ b/examples/location/places_map/places_map.qrc @@ -0,0 +1,6 @@ + + + marker.png + places_map.qml + + diff --git a/examples/location/planespotter/Plane.qml b/examples/location/planespotter/Plane.qml new file mode 100644 index 0000000..eec5a23 --- /dev/null +++ b/examples/location/planespotter/Plane.qml @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.4 +import QtLocation 5.6 + +//! [PlaneMapQuick1] +// Plane.qml +MapQuickItem { + id: plane + property string pilotName; + property int bearing: 0; + + anchorPoint.x: image.width/2 + anchorPoint.y: image.height/2 + + sourceItem: Grid { + //... +//! [PlaneMapQuick1] + columns: 1 + Grid { + horizontalItemAlignment: Grid.AlignHCenter + Image { + id: image + rotation: bearing + source: "airplane.png" + } + Rectangle { + id: bubble + color: "lightblue" + border.width: 1 + width: text.width * 1.3 + height: text.height * 1.3 + radius: 5 + Text { + id: text + anchors.centerIn: parent + text: pilotName + } + } + } + + Rectangle { + id: message + color: "lightblue" + border.width: 1 + width: banner.width * 1.3 + height: banner.height * 1.3 + radius: 5 + opacity: 0 + Text { + id: banner + anchors.centerIn: parent + } + SequentialAnimation { + id: playMessage + running: false + NumberAnimation { target: message; + property: "opacity"; + to: 1.0; + duration: 200 + easing.type: Easing.InOutQuad + } + PauseAnimation { duration: 1000 } + NumberAnimation { target: message; + property: "opacity"; + to: 0.0; + duration: 200} + } + } + } + function showMessage(message) { + banner.text = message + playMessage.start() +//! [PlaneMapQuick2] + } +} +//! [PlaneMapQuick2] diff --git a/examples/location/planespotter/airplane.png b/examples/location/planespotter/airplane.png new file mode 100644 index 0000000000000000000000000000000000000000..080460dd623908ac19f6d6add6663ddc926fcaf1 GIT binary patch literal 831 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 literal 0 HcmV?d00001 diff --git a/examples/location/planespotter/doc/src/planespotter.qdoc b/examples/location/planespotter/doc/src/planespotter.qdoc new file mode 100644 index 0000000..ed3a2b2 --- /dev/null +++ b/examples/location/planespotter/doc/src/planespotter.qdoc @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example planespotter + \title Plane Spotter (QML) + \ingroup qtlocation-examples + + \brief The \c {Plane Spotter} example demonstrates the tight integration of + location and positioning data types into QML + + \image planespotter.png + + The \c {Plane Spotter} example demonstrates how to integrate location and positioning + related C++ data types into QML and vice versa. This is useful when it is desirable to + run CPU intensive position calculations in native environments + but the results are supposed to be displayed using QML. + + The example shows a map of Europe and airplanes on two routes across Europe. + The first airplane commutes between Oslo and Berlin and the second airplane + commutes between London and Berlin. The position tracking of each airplane + is implemented in C++. The Oslo-Berlin plane is piloted in QML and the London-Berlin + plane is commanded by a C++ pilot. + + \include examples-run.qdocinc + + \section1 Overview + + + This example makes use of the \l Q_GADGET feature as part of its position controller + implementation. It permits \l {Cpp_value_integration_positioning}{direct integration} + of non-QObject based C++ value types into QML. + + The main purpose of the \c PlaneController class is to track the current + coordinates of the plane at a given time. It exposes the position + via its position property. + + \snippet planespotter/main.cpp PlaneController1 + \snippet planespotter/main.cpp PlaneController2 + + The example's \c main() function is responsible for the binding of the + \c PlaneController class instances into the QML context: + + \snippet planespotter/main.cpp PlaneControllerMain + + Similar to QObject derived classes, \l QGeoCoordinate can be integrated without + an additional QML wrapper. + + \section1 Steering the Planes + + As mentioned above, the primary purpose of \c PlaneController class is to track the current + positions of the two planes (Oslo-Berlin and London-Berlin) and advertise them as a property + to the QML layer. Its secondary purpose is to set and progress a plane along a given + flight path. In a sense it can act as a pilot. This is very much like + \l CoordinateAnimation which can animate the transition from one geo coordinate to another. + This example demonstrates how the \c {PlaneController}'s position property is modified + by C++ code using the PlaneController's own piloting abilities and by QML code using + \l CoordinateAnimation as pilot. The Oslo-Berlin plane is animated using QML code + and the London-Berlin plane is animated using C++ code. + + No matter which pilot is used, the results to the pilot's + actions are visible in C++ and QML and thus the example demonstrates unhindered and direct + exchange of position data through the C++/QML boundary. + + The visual representation of each \c Plane is done using + the \l MapQuickItem type which permits the embedding of arbitrary QtQuick items + into a map: + + \snippet planespotter/Plane.qml PlaneMapQuick1 + \snippet planespotter/Plane.qml PlaneMapQuick2 + + \section2 The C++ Pilot + + The C++ plane is steered by C++. The \c from and \c to property of the controller + class set the origin and destination which the pilot uses to calculate the + bearing for the plane: + + \snippet planespotter/main.cpp C++Pilot1 + + The pilot employs a \l QBasicTimer and \l {QTimerEvent}{QTimerEvents} to + constantly update the position. During each timer iteration + \c PlaneController::updatePosition() is called and a new position calculated. + + \snippet planespotter/main.cpp C++Pilot3 + + Once the new position is calculated, \c setPosition() is called and + the subsequent change notification of the property pushes the new position + to the QML layer. + + The C++ plane is started by clicking on the plane: + + \snippet planespotter/planespotter.qml CppPlane1 + \snippet planespotter/planespotter.qml CppPlane2 + + \l {azimuthTo}() calculates the bearing in degrees from one coordinate to another. + Note that the above code utilizes a QML animation to tie the rotation + and the position change into a single animation flow: + + \snippet planespotter/planespotter.qml CppPlane3 + + First, \l NumberAnimation rotates the plane into the correct direction + and once that is done the \c startFlight() function takes care of + starting the plane's position change. + + \snippet planespotter/main.cpp C++Pilot2 + + \section2 The QML Pilot + + The \l CoordinateAnimation type is used to control the flight from Oslo + to Berlin and vice versa. It replaces the above \l ScriptAction. + + \snippet planespotter/planespotter.qml QmlPlane1 + + The \l MouseArea of the QML plane implements the logic for the course setting + and starts the animation when required. + + \snippet planespotter/planespotter.qml QmlPlane2 + +*/ diff --git a/examples/location/planespotter/main.cpp b/examples/location/planespotter/main.cpp new file mode 100644 index 0000000..c71a699 --- /dev/null +++ b/examples/location/planespotter/main.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ANIMATION_DURATION 4000 + +//! [PlaneController1] +class PlaneController: public QObject +{ + Q_OBJECT + Q_PROPERTY(QGeoCoordinate position READ position WRITE setPosition NOTIFY positionChanged) +//! [PlaneController1] + //! [C++Pilot1] + Q_PROPERTY(QGeoCoordinate from READ from WRITE setFrom NOTIFY fromChanged) + Q_PROPERTY(QGeoCoordinate to READ to WRITE setTo NOTIFY toChanged) + //! [C++Pilot1] + +public: + PlaneController() + { + easingCurve.setType(QEasingCurve::InOutQuad); + easingCurve.setPeriod(ANIMATION_DURATION); + } + + void setFrom(const QGeoCoordinate& from) + { + fromCoordinate = from; + } + + QGeoCoordinate from() const + { + return fromCoordinate; + } + + void setTo(const QGeoCoordinate& to) + { + toCoordinate = to; + } + + QGeoCoordinate to() const + { + return toCoordinate; + } + + void setPosition(const QGeoCoordinate &c) { + if (currentPosition == c) + return; + + currentPosition = c; + emit positionChanged(); + } + + QGeoCoordinate position() const + { + return currentPosition; + } + + Q_INVOKABLE bool isFlying() const { + return timer.isActive(); + } + +//! [C++Pilot2] +public slots: + void startFlight() + { + if (timer.isActive()) + return; + + startTime = QTime::currentTime(); + finishTime = startTime.addMSecs(ANIMATION_DURATION); + + timer.start(15, this); + emit departed(); + } +//! [C++Pilot2] + + void swapDestinations() { + if (currentPosition == toCoordinate) { + // swap destinations + toCoordinate = fromCoordinate; + fromCoordinate = currentPosition; + } + } + +signals: + void positionChanged(); + void arrived(); + void departed(); + void toChanged(); + void fromChanged(); + +protected: + void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE + { + if (!event) + return; + + if (event->timerId() == timer.timerId()) + updatePosition(); + else + QObject::timerEvent(event); + } + +private: + //! [C++Pilot3] + void updatePosition() + { + // simple progress animation + qreal progress; + QTime current = QTime::currentTime(); + if (current >= finishTime) { + progress = 1.0; + timer.stop(); + } else { + progress = ((qreal)startTime.msecsTo(current) / ANIMATION_DURATION); + } + + setPosition(QGeoProjection::coordinateInterpolation( + fromCoordinate, toCoordinate, easingCurve.valueForProgress(progress))); + + if (!timer.isActive()) + emit arrived(); + } + //! [C++Pilot3] + +private: + QGeoCoordinate currentPosition; + QGeoCoordinate fromCoordinate, toCoordinate; + QBasicTimer timer; + QTime startTime, finishTime; + QEasingCurve easingCurve; +//! [PlaneController2] + // ... +}; +//! [PlaneController2] + +//! [PlaneControllerMain] +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + PlaneController oslo2berlin; + PlaneController berlin2london; + + QQmlApplicationEngine engine; + engine.rootContext()->setContextProperty("oslo2Berlin", &oslo2berlin); + engine.rootContext()->setContextProperty("berlin2London", &berlin2london); + engine.load(QUrl(QStringLiteral("qrc:/planespotter.qml"))); + + return app.exec(); +} +//! [PlaneControllerMain] + +#include "main.moc" diff --git a/examples/location/planespotter/planespotter.pro b/examples/location/planespotter/planespotter.pro new file mode 100644 index 0000000..d3be902 --- /dev/null +++ b/examples/location/planespotter/planespotter.pro @@ -0,0 +1,10 @@ +TEMPLATE = app +TARGET = planespotter +QT += qml quick positioning positioning-private location + +SOURCES += main.cpp + +RESOURCES += qml.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/location/planespotter +INSTALLS += target diff --git a/examples/location/planespotter/planespotter.qml b/examples/location/planespotter/planespotter.qml new file mode 100644 index 0000000..624a015 --- /dev/null +++ b/examples/location/planespotter/planespotter.qml @@ -0,0 +1,218 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.4 +import QtQuick.Window 2.2 +import QtPositioning 5.5 +import QtLocation 5.6 + +Window { + width: 700 + height: 500 + visible: true + + property variant topLeftEurope: QtPositioning.coordinate(60.5, 0.0) + property variant bottomRightEurope: QtPositioning.coordinate(51.0, 14.0) + property variant viewOfEurope: + QtPositioning.rectangle(topLeftEurope, bottomRightEurope) + + property variant berlin: QtPositioning.coordinate(52.5175, 13.384) + property variant oslo: QtPositioning.coordinate(59.9154, 10.7425) + property variant london: QtPositioning.coordinate(51.5, 0.1275) + + Map { + id: mapOfEurope + anchors.centerIn: parent; + anchors.fill: parent + plugin: Plugin { + name: "osm" + } + + Plane { + id: qmlPlane + pilotName: "QML" + coordinate: oslo2Berlin.position + + SequentialAnimation { + id: qmlPlaneAnimation + property real rotationDirection : 0; + NumberAnimation { + target: qmlPlane; property: "bearing"; duration: 1000 + easing.type: Easing.InOutQuad + to: qmlPlaneAnimation.rotationDirection + } + //! [QmlPlane1] + CoordinateAnimation { + id: coordinateAnimation; duration: 5000 + target: oslo2Berlin; property: "position" + easing.type: Easing.InOutQuad + } + //! [QmlPlane1] + + onStopped: { + if (coordinateAnimation.to === berlin) + qmlPlane.showMessage(qsTr("Hello Berlin!")) + else if (coordinateAnimation.to === oslo) + qmlPlane.showMessage(qsTr("Hello Oslo!")) + } + onStarted: { + if (coordinateAnimation.from === oslo) + qmlPlane.showMessage(qsTr("See you Oslo!")) + else if (coordinateAnimation.from === berlin) + qmlPlane.showMessage(qsTr("See you Berlin!")) + } + } + + //! [QmlPlane2] + MouseArea { + anchors.fill: parent + onClicked: { + if (qmlPlaneAnimation.running) { + console.log("Plane still in the air."); + return; + } + + if (oslo2Berlin.position === berlin) { + coordinateAnimation.from = berlin; + coordinateAnimation.to = oslo; + } else if (oslo2Berlin.position === oslo) { + coordinateAnimation.from = oslo; + coordinateAnimation.to = berlin; + } + + qmlPlaneAnimation.rotationDirection = oslo2Berlin.position.azimuthTo(coordinateAnimation.to) + qmlPlaneAnimation.start() + } + } + //! [QmlPlane2] + Component.onCompleted: { + oslo2Berlin.position = oslo; + } + } + + //! [CppPlane1] + Plane { + id: cppPlane + pilotName: "C++" + coordinate: berlin2London.position + + MouseArea { + anchors.fill: parent + onClicked: { + if (cppPlaneAnimation.running || berlin2London.isFlying()) { + console.log("Plane still in the air."); + return; + } + + berlin2London.swapDestinations(); + cppPlaneAnimation.rotationDirection = berlin2London.position.azimuthTo(berlin2London.to) + cppPlaneAnimation.start(); + cppPlane.departed(); + } + } + //! [CppPlane1] + //! [CppPlane3] + SequentialAnimation { + id: cppPlaneAnimation + property real rotationDirection : 0; + NumberAnimation { + target: cppPlane; property: "bearing"; duration: 1000 + easing.type: Easing.InOutQuad + to: cppPlaneAnimation.rotationDirection + } + ScriptAction { script: berlin2London.startFlight() } + } + //! [CppPlane3] + + Component.onCompleted: { + berlin2London.position = berlin; + berlin2London.to = london; + berlin2London.from = berlin; + berlin2London.arrived.connect(arrived) + } + + function arrived(){ + if (berlin2London.to === berlin) + cppPlane.showMessage(qsTr("Hello Berlin!")) + else if (berlin2London.to === london) + cppPlane.showMessage(qsTr("Hello London!")) + } + + function departed(){ + if (berlin2London.from === berlin) + cppPlane.showMessage(qsTr("See you Berlin!")) + else if (berlin2London.from === london) + cppPlane.showMessage(qsTr("See you London!")) + } + //! [CppPlane2] + } + //! [CppPlane2] + + visibleRegion: viewOfEurope + } + + Rectangle { + id: infoBox + anchors.centerIn: parent + color: "white" + border.width: 1 + width: text.width * 1.3 + height: text.height * 1.3 + radius: 5 + Text { + id: text + anchors.centerIn: parent + text: qsTr("Hit the plane to start the flight!") + } + + Timer { + interval: 5000; running: true; repeat: false; + onTriggered: fadeOut.start() + } + + NumberAnimation { + id: fadeOut; target: infoBox; + property: "opacity"; + to: 0.0; + duration: 200 + easing.type: Easing.InOutQuad + } + } +} diff --git a/examples/location/planespotter/qml.qrc b/examples/location/planespotter/qml.qrc new file mode 100644 index 0000000..6903ec0 --- /dev/null +++ b/examples/location/planespotter/qml.qrc @@ -0,0 +1,7 @@ + + + planespotter.qml + Plane.qml + airplane.png + + diff --git a/examples/positioning/geoflickr/doc/images/qml-flickr-1.jpg b/examples/positioning/geoflickr/doc/images/qml-flickr-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..42514ff06dc3549246d39e01719c202624419e58 GIT binary patch literal 66794 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 z zz&`d1zU0Z5xWobF+&E*O5@tQuD%aTjxf{)UDGFONo}?`!S1a*SiIUU9THp!%l?@AZ z&)A0W=0pQVS~|JeBv&$+sfQ$_2=uBc%Ov;qtE~bN(zDG*9_qE!&KzPkdgGpVl*720 zQl?k3L)m`CNG7=@R`ry6>l}8A2x+jL_KEM<>uX@c1ZtNRmvA96h~@`oqbAH%MWj5( z3Lmi)Ur1z{C1Z@@b46f+hCtkb2 znr()yY3uc7gIRgT(UGwRNnyDV8J%~Mn6i98We`Fnl9Z&xwnCysb|z$;;m13mwbYIr zHHXb%D>N}L!saBzX%%sHWhVi*WOM>6Ce@`TtS_2n@V>P&JgtDuHqf^1W{T^&b_Qyj zG#u=;!m+exLTuj>DN9HU^q_>=XLxfANG-K921oc&v`%$vjIhU;!1xuYr!l!kK(w4@ zj4KgLrdCd(*Oy3@afG1$sQsT3I%*EqwbX9S{;S-eb)TAgHYcWwK(*B^km~! zB}(3*6Z`B43=q^GwFf37Ez1%r>KN+%(}NRo?OJMQy93Z#8tDx&p?eU`&MBSELgBW! zbMGzXSPQxf9ps&~G5Yq|;E&liq48`cQI-b24|-k<5YQ6d!}idGZfmvFPICt?OVuU` z?8+|+?(FBq0Njwvp6$7Rg?$n-elm9k%}E1s6#pX405${& zwK3L|ND`oc-~?$^@D*eV!--2rd#gPZdnq&W4Y+vmqTkZDDaHUrqmY^YAlXg!zyIvx zHFTSX;Rq8JP3&JbQY)>rJUB`}%Lu)H6qL(XoJhQLE1uLBnyTyGlWON0V31T_-HuXBH)m}sqWjw^&B|9eBCEv-)RR_~aTCWe%`k;3@7_`}~J-i(r)Nju@u=4O*kHwR#(Fk2#?@fp0u!1-o+l<6J z)2@)rQ->2Z%?J>j=&eBS41?YsfV3VPLy%*m3-VM4(I+4$3)${!#(m7&_8qITF`)&? zK#eK}#tl+jfa>W-yh`DdQAN_w)Ugo9rEffMu=rpkk3qV|C4go*AZVow2;vKD%aM2| zmuzCCWXEw#$8lZ9Tm1!k=QvYzPf;z^aicY%$8qdv`BD#k6*F0O8?9UtH(oa4c(~42 zvF5J3WsazsCXX%`&9jp2QQDvkE6DtSD-K<9uJNdYPs$LzF0Sf0KE;vf-fGzwc%H;d z_Zq$24pI|L?IAk*c%A5=?<}%#jJY6*f;gJPcqHBx4oiGk&kMh358(wRVv%?kQxe457heE( z|5%752sTYHlBxKGMt4pzGw@l`aBMclPhVk3?<6UWcLO2`F{>$-(uplE@<2LE2oxo; zSs6}H?j^4AOqRKtV94S23U6Dj4sM}|pjJzo;M~C*X$MJZiuNteC3<&)DP!y51u6MR zyvtFpLaA}d6i9?MAIV1y#cz57TwY)-(X{?GZsoW5!nukw9-p+)mw4`t%qlL=`$fs) zo@|Q;n%Ct6$x^mJXh9v|d(JekkxgTKRsUq0UBLZ;qk`zwYMJ!T#b@&hH+{g|M`L)4 zwmC+BFpY&4l-P^PyM*VRyfJw!=FQI_p?0Vrgx_4Hw9VOC&t%lep_4*}N# zDE)(0vfY(j#KE~Jdt0$C@sI>598g5wF|(4%XZ@Qyo~^LvGdAnOg3-mI*gbFbYdp&D zbY4*|*0QzizGQ93y2Jw#^phR?S!oyu;y8|nRC-P{*<>$42o`e@#7l*Mhdfsvq1V2x zzs;~iWNTU5%#y=={j-9!bo=QnX*)4H|9*`msJ4@Nc(r`}mTAO&vwzJCF5yfvBT96Xmw@TW5YVx(MfStATXLk6dtK5pL zUEOZ7TAHTS?Mkbqsl3q+E$s~1)p=etC#$9C)D(GMlbu=GJ;d8pWXFTolzME+HDOp4}Kv(_0tuQK&_&8r_V}cE+>bx=^as za;CH05bw~_>7-10YssCV^VKZ8(i0!$db8Y}&KGmIywVdNC3`KozPd5quO4NyoayFx zhaP}EL2oz=4- z1+rR`JFQ3Qx*U4X*yRYnXX`>lL_|bHL_|bHMD#EE0k&up$4UoudjJ3c07*qoM6N<$ Eg5PjTRR910 literal 0 HcmV?d00001 diff --git a/examples/positioning/geoflickr/doc/src/geoflickr.qdoc b/examples/positioning/geoflickr/doc/src/geoflickr.qdoc new file mode 100644 index 0000000..6d043d4 --- /dev/null +++ b/examples/positioning/geoflickr/doc/src/geoflickr.qdoc @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example geoflickr + \title GeoFlickr (QML) + \ingroup qtpositioning-examples + + \brief The GeoFlickr example shows how to use the user's current position to + fetch local content from a web service. + + This is a small example, illustrating one of the very core parts of the + \l{Qt Positioning} API: the ability to retrieve and use the user's current + geographic position. + + Key QML types shown in this example: + \list + \li \l{QtPositioning::PositionSource}{PositionSource} + \li \l{XmlListModel}{XmlListModel} + \endlist + + \image qml-flickr-1.jpg + + \include examples-run.qdocinc + + \section1 Retrieving the Current Position + + Retrieving the user's current position is achieved using the PositionSource + type. In this example, we instantiate the PositionSource as part of the + GeoTab component (the floating "window" describing current position and + status). + + \snippet geoflickr/flickrmobile/GeoTab.qml possrc + + When the "Locate and update" button is pressed, we first interrogate the + PositionSource to check if it has an available backend for positioning + data. If it does not, we fall back to using a pre-recorded NMEA log + for demonstration. We then instruct the PositionSource to update. + + \snippet geoflickr/flickrmobile/GeoTab.qml locatebutton-top + \snippet geoflickr/flickrmobile/GeoTab.qml locatebutton-clicked + + To share the new position data with the rest of the application, we use + properties that we have created on the GeoTab component: + + \snippet geoflickr/flickrmobile/GeoTab.qml props + + \section1 Using the Current Position + + The longitude and latitude values retrieved here are eventually set on + in properties on the RestModel component. The RestModel is an XmlListModel, + which retrieves XML data from a URL and creates a data model by performing + XPath queries on it. + + In this case, it retrieves data from the Flickr REST API online, based on + our current position + + \snippet geoflickr/flickrcommon/RestModel.qml restmodel + + This model data is then shown in a variety of Qt Quick views to produce + the example application. + +*/ diff --git a/examples/positioning/geoflickr/flickr-90.qml b/examples/positioning/geoflickr/flickr-90.qml new file mode 100644 index 0000000..92e9344 --- /dev/null +++ b/examples/positioning/geoflickr/flickr-90.qml @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + width: 480; height: 320 + + Loader { + y: 320; rotation: -90 + transformOrigin: Item.TopLeft + source: "flickr.qml" + } +} diff --git a/examples/positioning/geoflickr/flickr.qml b/examples/positioning/geoflickr/flickr.qml new file mode 100644 index 0000000..5ae065c --- /dev/null +++ b/examples/positioning/geoflickr/flickr.qml @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 +import "flickrcommon" as Common +import "flickrmobile" as Mobile + +Item { + id: screen; width: 320; height: 480 + property bool inListView : false + + Rectangle { + id: background + anchors.fill: parent; color: "#343434"; + + Image { source: "flickrmobile/images/stripes.png"; fillMode: Image.Tile; anchors.fill: parent; opacity: 0.3 } + + Common.RestModel { + id: restModel + coordinate: geoTab.coordinate + } + + Item { + id: views + x: 2; width: parent.width - 4 + anchors.top: titleBar.bottom; anchors.bottom: toolBar.top + + Text { + text: qsTr("Network error") + font.pixelSize: 48 + fontSizeMode: Text.HorizontalFit + anchors.centerIn: parent + width: parent.width * 0.9 + visible: restModel.status === XmlListModel.Error + + } + + Mobile.GridDelegate { id: gridDelegate } + GridView { + x: (width/4-79)/2; y: x + id: photoGridView; model: restModel; delegate: gridDelegate; cacheBuffer: 100 + cellWidth: (parent.width-2)/4; cellHeight: cellWidth; width: parent.width; height: parent.height - 1; z: 6 + } + Mobile.ListDelegate { id: listDelegate } + ListView { + id: photoListView; model: restModel; delegate: listDelegate; z: 6 + width: parent.width; height: parent.height; x: -(parent.width * 1.5); cacheBuffer: 100; + } + states: State { + name: "ListView"; when: screen.inListView == true + PropertyChanges { target: photoListView; x: 0 } + PropertyChanges { target: photoGridView; x: -(parent.width * 1.5) } + } + + transitions: Transition { + NumberAnimation { properties: "x"; duration: 500; easing.type: Easing.InOutQuad } + } + } + Mobile.ImageDetails { id: imageDetails; width: parent.width; anchors.left: views.right; height: parent.height; z:1 } + Mobile.TitleBar { id: titleBar; z: 5; width: parent.width; height: 40; opacity: 0.9 } + Mobile.GeoTab { + id: geoTab; + x: 15; y:50; + } + Mobile.ToolBar { + id: toolBar; z: 5 + height: 40; anchors.bottom: parent.bottom; width: parent.width; opacity: 0.9 + button1Label: "Update"; button2Label: "View mode" + onButton1Clicked: restModel.reload() + onButton2Clicked: if (screen.inListView == true) screen.inListView = false; else screen.inListView = true + } + Connections { + target: imageDetails + onClosed: { + if (background.state == "DetailedView") { + background.state = ''; + imageDetails.photoUrl = ""; + } + } + } + + states: State { + name: "DetailedView" + PropertyChanges { target: views; x: -parent.width } + PropertyChanges { target: geoTab; x: -parent.width } + PropertyChanges { target: toolBar; button1Label: "More..." } + PropertyChanges { + target: toolBar + onButton1Clicked: if (imageDetails.state=='') imageDetails.state='Back'; else imageDetails.state='' + } + PropertyChanges { target: toolBar; button2Label: "Back" } + PropertyChanges { target: toolBar; onButton2Clicked: imageDetails.closed() } + } + + transitions: Transition { + NumberAnimation { properties: "x"; duration: 500; easing.type: Easing.InOutQuad } + } + } +} diff --git a/examples/positioning/geoflickr/flickr.qrc b/examples/positioning/geoflickr/flickr.qrc new file mode 100644 index 0000000..1019f7d --- /dev/null +++ b/examples/positioning/geoflickr/flickr.qrc @@ -0,0 +1,30 @@ + + + flickr.qml + flickr-90.qml + flickrcommon/Progress.qml + flickrcommon/RestModel.qml + flickrcommon/ScrollBar.qml + flickrcommon/Slider.qml + flickrmobile/Button.qml + flickrmobile/GeoTab.qml + flickrmobile/GridDelegate.qml + flickrmobile/ImageDetails.qml + flickrmobile/ListDelegate.qml + flickrmobile/nmealog.txt + flickrmobile/TitleBar.qml + flickrmobile/ToolBar.qml + flickrmobile/images/gloss.png + flickrmobile/images/lineedit.png + flickrmobile/images/lineedit.sci + flickrmobile/images/moon.png + flickrmobile/images/quit.png + flickrmobile/images/star.png + flickrmobile/images/stripes.png + flickrmobile/images/sun.png + flickrmobile/images/titlebar.png + flickrmobile/images/titlebar.sci + flickrmobile/images/toolbutton.png + flickrmobile/images/toolbutton.sci + + diff --git a/examples/positioning/geoflickr/flickrcommon/Progress.qml b/examples/positioning/geoflickr/flickrcommon/Progress.qml new file mode 100644 index 0000000..294957b --- /dev/null +++ b/examples/positioning/geoflickr/flickrcommon/Progress.qml @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + property variant progress: 0 + + Rectangle { + anchors.fill: parent; smooth: true + border.color: "white"; border.width: 0; radius: height/2 - 2 + gradient: Gradient { + GradientStop { position: 0; color: "#66343434" } + GradientStop { position: 1.0; color: "#66000000" } + } + } + + Rectangle { + y: 2; height: parent.height-4; + x: 2; width: Math.max(parent.width * progress - 4, 0); + opacity: width < 1 ? 0 : 1; smooth: true + gradient: Gradient { + GradientStop { position: 0; color: "lightsteelblue" } + GradientStop { position: 1.0; color: "steelblue" } + } + radius: height/2 - 2 + } + + Text { + text: Math.round(progress * 100) + "%" + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + color: "white"; font.bold: true + } +} diff --git a/examples/positioning/geoflickr/flickrcommon/RestModel.qml b/examples/positioning/geoflickr/flickrcommon/RestModel.qml new file mode 100644 index 0000000..762cfec --- /dev/null +++ b/examples/positioning/geoflickr/flickrcommon/RestModel.qml @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +//! [restmodel] +XmlListModel { + property variant coordinate + + source: "https://api.flickr.com/services/rest/?" + + "min_taken_date=2000-01-01+0:00:00&" + + "extras=date_taken&" + + "method=flickr.photos.search&" + + "per_page=30&" + + "sort=date-taken-desc&" + + "api_key=e36784df8a03fea04c22ed93318b291c&" + + "lat=" + coordinate.latitude + "&lon=" + coordinate.longitude; + query: "/rsp/photos/photo" + + XmlRole { name: "title"; query: "@title/string()" } + XmlRole { name: "datetaken"; query: "@datetaken/string()" } + XmlRole { name: "farm"; query: "@farm/string()" } + XmlRole { name: "server"; query: "@server/string()" } + XmlRole { name: "id"; query: "@id/string()" } + XmlRole { name: "secret"; query: "@secret/string()" } +} +//! [restmodel] diff --git a/examples/positioning/geoflickr/flickrcommon/ScrollBar.qml b/examples/positioning/geoflickr/flickrcommon/ScrollBar.qml new file mode 100644 index 0000000..c18e097 --- /dev/null +++ b/examples/positioning/geoflickr/flickrcommon/ScrollBar.qml @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: container + + property variant flickableArea + + Rectangle { + radius: 5 + color: "black" + opacity: 0.3 + border.color: "white" + border.width: 2 + x: 0 + y: flickableArea.visibleArea.yPosition * container.height + width: parent.width + height: flickableArea.visibleArea.heightRatio * container.height + } + states: [ + State { + name: "show" + when: flickableArea.movingVertically + PropertyChanges { + target: container + opacity: 1 + } + } + ] + transitions: [ + Transition { + from: "*" + to: "*" + NumberAnimation { + target: container + properties: "opacity" + duration: 400 + } + } + ] +} diff --git a/examples/positioning/geoflickr/flickrcommon/Slider.qml b/examples/positioning/geoflickr/flickrcommon/Slider.qml new file mode 100644 index 0000000..0a6fa70 --- /dev/null +++ b/examples/positioning/geoflickr/flickrcommon/Slider.qml @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: slider; width: 400; height: 16 + + // value is read/write. + property real value + onValueChanged: { handle.x = 2 + (value - minimum) * slider.xMax / (maximum - minimum); } + property real maximum: 1 + property real minimum: 1 + property int xMax: slider.width - handle.width - 4 + + Rectangle { + anchors.fill: parent + border.color: "white"; border.width: 0; radius: 8 + gradient: Gradient { + GradientStop { position: 0.0; color: "#66343434" } + GradientStop { position: 1.0; color: "#66000000" } + } + } + + Rectangle { + id: handle; smooth: true + x: slider.width / 2 - handle.width / 2; y: 2; width: 30; height: slider.height-4; radius: 6 + gradient: Gradient { + GradientStop { position: 0.0; color: "lightgray" } + GradientStop { position: 1.0; color: "gray" } + } + + MouseArea { + anchors.fill: parent; drag.target: parent + drag.axis: Drag.XAxis; drag.minimumX: 2; drag.maximumX: slider.xMax+2 + onPositionChanged: { value = (maximum - minimum) * (handle.x-2) / slider.xMax + minimum; } + } + } +} diff --git a/examples/positioning/geoflickr/flickrmobile/Button.qml b/examples/positioning/geoflickr/flickrmobile/Button.qml new file mode 100644 index 0000000..27e5140 --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/Button.qml @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: container + + signal clicked + + property string text + + BorderImage { + id: buttonImage + source: "images/toolbutton.sci" + width: container.width; height: container.height + } + BorderImage { + id: pressed + opacity: 0 + source: "images/toolbutton.sci" + width: container.width; height: container.height + } + MouseArea { + id: mouseRegion + anchors.fill: buttonImage + onClicked: { container.clicked(); } + } + Text { + color: "white" + anchors.centerIn: buttonImage; font.bold: true + text: container.text; style: Text.Raised; styleColor: "black" + } + states: [ + State { + name: "Pressed" + when: mouseRegion.pressed == true + PropertyChanges { target: pressed; opacity: 1 } + } + ] +} diff --git a/examples/positioning/geoflickr/flickrmobile/GeoTab.qml b/examples/positioning/geoflickr/flickrmobile/GeoTab.qml new file mode 100644 index 0000000..4505319 --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/GeoTab.qml @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtPositioning 5.2 + +Rectangle { + id: container + property int maxX: parent.width; property int maxY: parent.height +//! [props] + property variant coordinate +//! [props] + + Binding { + target: container + property: "coordinate" + value: positionSource.position.coordinate + } + + width: 300; height: 130 + color: "blue" + opacity: 0.7 + border.color: "black" + border.width: 1 + radius: 5 + gradient: Gradient { + GradientStop {position: 0.0; color: "grey"} + GradientStop {position: 1.0; color: "black"} + } + MouseArea { + anchors.fill: parent + drag.target: parent + drag.axis: Drag.XandYAxis + drag.minimumX: -(parent.width * (2/3)); drag.maximumX: parent.maxX - (parent.width/3) + drag.minimumY: -(parent.height/2); drag.maximumY: parent.maxY - (parent.height/2) + } +//! [locatebutton-top] + Button { + id: locateButton + text: "Locate & update" +//! [locatebutton-top] + anchors {left: parent.left; leftMargin: 5} + y: 3; height: 32; width: parent.width - 10 +//! [locatebutton-clicked] + onClicked: { + if (positionSource.supportedPositioningMethods === + PositionSource.NoPositioningMethods) { + positionSource.nmeaSource = "nmealog.txt"; + sourceText.text = "(filesource): " + printableMethod(positionSource.supportedPositioningMethods); + } + positionSource.update(); + } + } +//! [locatebutton-clicked] +//! [possrc] + PositionSource { + id: positionSource + onPositionChanged: { planet.source = "images/sun.png"; } + + onSourceErrorChanged: { + if (sourceError == PositionSource.NoError) + return + + console.log("Source error: " + sourceError) + activityText.fadeOut = true + stop() + } + + onUpdateTimeout: { + activityText.fadeOut = true + } + } +//! [possrc] + function printableMethod(method) { + if (method === PositionSource.SatellitePositioningMethods) + return "Satellite"; + else if (method === PositionSource.NoPositioningMethods) + return "Not available" + else if (method === PositionSource.NonSatellitePositioningMethods) + return "Non-satellite" + else if (method === PositionSource.AllPositioningMethods) + return "Multiple" + return "source error"; + } + + Grid { + id: locationGrid + columns: 2 + anchors {left: parent.left; leftMargin: 5; top: locateButton.bottom; topMargin: 5} + spacing: 5 + Text {color: "white"; font.bold: true + text: "Lat:"; style: Text.Raised; styleColor: "black" + } + Text {id: latitudeValue; color: "white"; font.bold: true + text: positionSource.position.coordinate.latitude; style: Text.Raised; styleColor: "black"; + } + Text {color: "white"; font.bold: true + text: "Lon:"; style: Text.Raised; styleColor: "black" + } + Text {id: longitudeValue; color: "white"; font.bold: true + text: positionSource.position.coordinate.longitude; style: Text.Raised; styleColor: "black" + } + } + Image { + id: planet + anchors {top: locationGrid.bottom; left: parent.left; leftMargin: locationGrid.anchors.leftMargin} + source: "images/moon.png" + width: 30; height: 30 + } + Text {id: sourceText; color: "white"; font.bold: true; + anchors {left: planet.right; leftMargin: 5; verticalCenter: planet.verticalCenter} + text: "Source: " + printableMethod(positionSource.supportedPositioningMethods); style: Text.Raised; styleColor: "black"; + } + + Text { + id: activityText; color: "white"; font.bold: true; + anchors { top: planet.bottom; horizontalCenter: parent.horizontalCenter } + property bool fadeOut: false + + text: { + if (fadeOut) + return qsTr("Timeout occurred!"); + else if (positionSource.active) + return qsTr("Retrieving update...") + else + return "" + } + + Timer { + id: fadeoutTimer; repeat: false; interval: 3000; running: activityText.fadeOut + onTriggered: { activityText.fadeOut = false; } + } + } +} diff --git a/examples/positioning/geoflickr/flickrmobile/GridDelegate.qml b/examples/positioning/geoflickr/flickrmobile/GridDelegate.qml new file mode 100644 index 0000000..362317f --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/GridDelegate.qml @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + import QtQuick 2.0 + import QtQuick.Window 2.0 + + Component { + id: photoDelegate + Item { + id: wrapper; width: 79; height: 79 + + function photoClicked() { + imageDetails.photoTitle = title; + imageDetails.photoDate = datetaken; + imageDetails.photoUrl = "http://farm" + farm + ".static.flickr.com/" + server + "/" + id + "_" + secret + ".jpg"; + console.log(imageDetails.photoUrl); + scaleMe.state = "Details"; + } + + Item { + anchors.centerIn: parent + scale: 0.0 + Behavior on scale { NumberAnimation { easing.type: Easing.InOutQuad} } + id: scaleMe + + Rectangle { height: 79; width: 79; id: blackRect; anchors.centerIn: parent; color: "black"; smooth: true } + Rectangle { + id: whiteRect; width: 76; height: 76; anchors.centerIn: parent; color: "#dddddd"; smooth: true + Image { id: thumb; + // source: imagePath; + source: imageDetails.photoUrl = "http://farm" + farm + ".static.flickr.com/" + server + "/" + id + "_" + secret + "_t.jpg" + width: parent.width; height: parent.height + x: 1; y: 1; smooth: true} + Image { source: "images/gloss.png" } + } + + Connections { + target: toolBar + onButton2Clicked: if (scaleMe.state == 'Details' ) scaleMe.state = 'Show' + } + + states: [ + State { + name: "Show"; when: thumb.status == Image.Ready + PropertyChanges { target: scaleMe; scale: Math.round(Screen.pixelDensity / 4) } + }, + State { + name: "Details" + PropertyChanges { target: scaleMe; scale: Math.round(Screen.pixelDensity / 4)} + ParentChange { target: wrapper; parent: imageDetails.frontContainer } + PropertyChanges { target: wrapper; x: 20; y: 60; z: 1000 } + PropertyChanges { target: background; state: "DetailedView" } + } + ] + transitions: [ + Transition { + from: "Show"; to: "Details" + ParentAnimation { + NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad } + } + }, + Transition { + from: "Details"; to: "Show" + SequentialAnimation { + ParentAnimation { + NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad } + } + PropertyAction { targets: wrapper; properties: "z" } + } + } + ] + } + MouseArea { anchors.fill: wrapper; onClicked: { photoClicked() } } + } + } diff --git a/examples/positioning/geoflickr/flickrmobile/ImageDetails.qml b/examples/positioning/geoflickr/flickrmobile/ImageDetails.qml new file mode 100644 index 0000000..c11c0aa --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/ImageDetails.qml @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import "../flickrcommon" as Common + +Flipable { + id: container + + property variant frontContainer: containerFront + property string photoTitle: "" + property string photoDate + property string photoUrl + property variant prevScale: 1.0 + + signal closed + + transform: Rotation { + id: itemRotation + origin.x: container.width / 2; + axis.y: 1; axis.z: 0 + } + + front: Item { + id: containerFront; anchors.fill: container + + Rectangle { + anchors.fill: parent + color: "black"; opacity: 0.4 + } + + Column { + spacing: 10 + anchors { + left: parent.left; leftMargin: 20 + right: parent.right; rightMargin: 20 + top: parent.top; topMargin: 180 + } + Text { font.bold: true; color: "white"; elide: Text.ElideRight; text: container.photoTitle } + Text { color: "white"; elide: Text.ElideRight; text: "Published: " + container.photoDate } + } + } + + back: Item { + anchors.fill: container + + Rectangle { anchors.fill: parent; color: "black"; opacity: 0.4 } + + Common.Progress { + anchors.centerIn: parent; width: 200; height: 18 + progress: bigImage.progress; visible: bigImage.status != Image.Ready + } + + Flickable { + id: flickable; anchors.fill: parent; clip: true + contentWidth: imageContainer.width; contentHeight: imageContainer.height + + Item { + id: imageContainer + width: Math.max(bigImage.width * bigImage.scale, flickable.width); + height: Math.max(bigImage.height * bigImage.scale, flickable.height); + + Image { + id: bigImage; // source: container.photoUrl + source: container.photoUrl + scale: slider.value + anchors.centerIn: parent; smooth: !flickable.movingVertically + onStatusChanged : { + // Default scale shows the entire image. + if (status == Image.Ready && width != 0) { + slider.minimum = Math.min(flickable.width / width, flickable.height / height); + prevScale = Math.min(slider.minimum, 1); + slider.value = prevScale; + } + } + } + } + } + + Text { + text: "Image Unavailable" + visible: bigImage.status == Image.Error + anchors.centerIn: parent; color: "white"; font.bold: true + } + + Common.Slider { + id: slider; visible: { bigImage.status == Image.Ready && maximum > minimum } + anchors { + bottom: parent.bottom; bottomMargin: 65 + left: parent.left; leftMargin: 25 + right: parent.right; rightMargin: 25 + } + onValueChanged: { + if (bigImage.width * value > flickable.width) { + var xoff = (flickable.width/2 + flickable.contentX) * value / prevScale; + flickable.contentX = xoff - flickable.width/2; + } + if (bigImage.height * value > flickable.height) { + var yoff = (flickable.height/2 + flickable.contentY) * value / prevScale; + flickable.contentY = yoff - flickable.height/2; + } + prevScale = value; + } + } + } + + states: State { + name: "Back" + PropertyChanges { target: itemRotation; angle: 180 } + } + + transitions: Transition { + SequentialAnimation { + PropertyAction { target: bigImage; property: "smooth"; value: false } + NumberAnimation { easing.type: Easing.InOutQuad; properties: "angle"; duration: 500 } + PropertyAction { target: bigImage; property: "smooth"; value: !flickable.movingVertically } + } + } +} diff --git a/examples/positioning/geoflickr/flickrmobile/ListDelegate.qml b/examples/positioning/geoflickr/flickrmobile/ListDelegate.qml new file mode 100644 index 0000000..c10c859 --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/ListDelegate.qml @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + Component { + Item { + id: wrapper; width: wrapper.ListView.view.width; height: 86 + Item { + id: moveMe + Rectangle { color: "black"; opacity: index % 2 ? 0.2 : 0.4; height: 84; width: wrapper.width; y: 1 } + Rectangle { + x: 6; y: 4; width: 76; height: 76; color: "white"; smooth: true + + Image { + //source: imagePath; + source: "http://farm" + farm + ".static.flickr.com/" + server + "/" + id + "_" + secret + "_t.jpg" + width: parent.width; height: parent.height + x: 0; y: 0 } + + Image { source: "images/gloss.png" } + } + Column { + x: 92; width: wrapper.ListView.view.width - 95; y: 15; spacing: 2 + Text { text: title; color: "white"; width: parent.width; font.bold: true; elide: Text.ElideRight; style: Text.Raised; styleColor: "black" } + Text { text: datetaken; width: parent.width; elide: Text.ElideRight; color: "#cccccc"; style: Text.Raised; styleColor: "black" } + } + } + } +} diff --git a/examples/positioning/geoflickr/flickrmobile/TitleBar.qml b/examples/positioning/geoflickr/flickrmobile/TitleBar.qml new file mode 100644 index 0000000..7fd7262 --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/TitleBar.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: titleBar + BorderImage { source: "images/titlebar.sci"; width: parent.width; height: parent.height + 14; y: -7 } + + Item { + id: container + width: (parent.width * 2) - 55 ; height: parent.height + + Image { + id: quitButton + anchors.left: parent.left//; anchors.leftMargin: 0 + anchors.verticalCenter: parent.verticalCenter + source: "images/quit.png" + MouseArea { + anchors.fill: parent + onClicked: Qt.quit() + } + } + + Text { + id: categoryText + anchors { + left: quitButton.right; leftMargin: 10; rightMargin: 10 + verticalCenter: parent.verticalCenter + } + elide: Text.ElideLeft + text: "GeoFlickr (QML)" + font.bold: true; color: "White"; style: Text.Raised; styleColor: "Black" + } + } + + transitions: Transition { + NumberAnimation { properties: "x"; easing.type: Easing.InOutQuad } + } +} diff --git a/examples/positioning/geoflickr/flickrmobile/ToolBar.qml b/examples/positioning/geoflickr/flickrmobile/ToolBar.qml new file mode 100644 index 0000000..1c921cb --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/ToolBar.qml @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: toolbar + + property alias button1Label: button1.text + property alias button2Label: button2.text + signal button1Clicked + signal button2Clicked + + BorderImage { source: "images/titlebar.sci"; width: parent.width; height: parent.height + 14; y: -7 } + + Button { + id: button1 + anchors.left: parent.left; anchors.leftMargin: 5; y: 3; width: 140; height: 32 + onClicked: toolbar.button1Clicked() + } + + Button { + id: button2 + anchors.right: parent.right; anchors.rightMargin: 5; y: 3; width: 140; height: 32 + onClicked: toolbar.button2Clicked() + } +} diff --git a/examples/positioning/geoflickr/flickrmobile/images/gloss.png b/examples/positioning/geoflickr/flickrmobile/images/gloss.png new file mode 100644 index 0000000000000000000000000000000000000000..dff2bd34718bce770ed746136f8976332111c23e GIT binary patch literal 889 zcmV-<1BU#GP)eTd}%-tK1b1D4>ySGqo)GjrzLvFHj= z6ours|8KvB5bhFPA3bXSq1ii_tVuw48*3Puc;)XPLEvq@0M!gpbZ1Ll1^l47D7vdi z`@@I+JGf&8-y<7EA2J)e*Kb)8I7LzPu`*^hYk{LE`s7KS8b=@dIEp?8r?nWqwK9sn zvw-muUwc|}_Y@;|?*mciRyY+zi+%{KdB%EZQq!Uz4K>fTb9m*V=%?x|v0k%@aA_1p zKj-OF4Vi~amlplvwVgWKu@Y^RJ$PT8-rb$((ynscUqru!wW1zG=X7b&?-q^SwcDCX zOeTt=KP*L~4qA)W5dG;8mpbY8r*WQ=!^cYXXDcBDq71VmgpZVqh#$%{MJPO zoUKQgEa5i4%F2|6Jt^(AGe>l~A>&}BIQOxZF_i^aEo?ap`Ar(lJM8EZyCMZ+SB29pGh zSg@hzWe6M#$7mL#(BchidKseYIN$B98PBp5u9&iBm>7s)6M`~`R`WoGh%sv>M^Owx zc`ihUiK;@M6*JlaXsvR~ZX;=hHZd`MB)~DVaLr??uW-eXB~zlHa~2a>wgA~7W-9Km+0YRW8uA|UW4I5BTM4~?*i`v?*i`vud&yE=VmBv+|22M P00000NkvXXu0mjfaetp8 literal 0 HcmV?d00001 diff --git a/examples/positioning/geoflickr/flickrmobile/images/lineedit.png b/examples/positioning/geoflickr/flickrmobile/images/lineedit.png new file mode 100644 index 0000000000000000000000000000000000000000..a6afb5121a6f8f02491ce9fa220d9f8b6c0f31c0 GIT binary patch literal 1307 zcmV+$1?2jPP)^_M)OMqYwHdLJ>s}eN&%{;FDDU1^oxa7nPzf zf|n{76#7tuptM>qwY6%kY0}0t>Ez5g`}=&DOiXMwNlBU%`@!bG%sywZNPmU@yB5~XvhKv!26k>*eX&bbw5aXw&G#TbLCA|i}k z8f7#)N6$bH1z+HgV`usG{a=Q?E!s!7KC!vxy=C(31Wd&|ecvMJ3*;m7aD5HC*jIC@bW7P5Ig-Q0^y^q_fw-J0mj6kel zw5houE82_#qG*u0YpWrOL||gWgF7E!_uyWV$r+*^k1tf(Nzyug%&@zEC#Vp+-0V1` zzZa3St15$kvK(nl2r0Q-Ol?bzPe1yE_Wn-16qKwXbQZd~X=oG9`UMY4vl+b0EnCN` zx?~q=^<8j}D4GBshPtXa^+Ya*&qxsw>eJKYeXeO|nvjp$2uVtm%P$GQ)!16A-IodyiE^a&f9@g^NzD zgk`h4nq^mljWq?lp1@rd!^XPn3-G$v@IS#B>(;l1*FD3=7z6=p7QBqiqj*h#&EKk9 zS5?Jwoz)G%Hvs=H0AE)=yzUq_#^Q+=c-?wn*BH}0|MpiYT>j7{ndOr+G4IzOEFes*|GuptD@Dn(UGS#&L;j-B|1sH2&U z*y-tMthY=|O)&E12&puhdPbUwrL2)l&@_WansDUHBbW#pjRtn=iwR<%r@Xbo>xbXq z$gvR+AQ*y%t9FW(r2eak2q6SQ2y>BZ>oaQ{Si`}O4l?}XFokNK^g@Dj-o4kex%6Cl zbA{3AKREpPVMralr3&30T?m4~tjYp3#x%Xv+RLJ=pMU$6*WP`Vw?2HE(vDvGH&!`) z=oDr{dBY=Z1JVB4{)cKwd%}h9#u+~`PH(A~qAgOS0~=d(nRpiqz_Tp7ET(F)tO{e7 z&NJ@D=)I{#d25;Rqi31?qW*%Z4h)(!4G(v0E53Nwle=ndLj^P#M$e4Wn3_Q*QyOG= z?^iU63Yz`vea1jnUk`=u9Qi_#^Iu=!_@Se(6#5Gc0 zr98Q=HtI=(yw9^-MaUwHRW)2O7;H&o=HewTo|>XD7CtH6S$fJB{J3#O{sI^eqK)Qb RrHcRn002ovPDHLkV1hGOfVKbt literal 0 HcmV?d00001 diff --git a/examples/positioning/geoflickr/flickrmobile/images/lineedit.sci b/examples/positioning/geoflickr/flickrmobile/images/lineedit.sci new file mode 100644 index 0000000..054bff7 --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/images/lineedit.sci @@ -0,0 +1,5 @@ +border.left: 10 +border.top: 10 +border.bottom: 10 +border.right: 10 +source: lineedit.png diff --git a/examples/positioning/geoflickr/flickrmobile/images/moon.png b/examples/positioning/geoflickr/flickrmobile/images/moon.png new file mode 100644 index 0000000000000000000000000000000000000000..1583ac83f7cfbfdca3f9c0818a2ff00e7585bef3 GIT binary patch literal 2366 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 literal 0 HcmV?d00001 diff --git a/examples/positioning/geoflickr/flickrmobile/images/quit.png b/examples/positioning/geoflickr/flickrmobile/images/quit.png new file mode 100644 index 0000000000000000000000000000000000000000..aef73853e362dd6787ecffaa057f21adf7d1d456 GIT binary patch literal 1785 zcmV({j3*ryZagbwx(==k`U_8JXxI-RukX6@Fn9*<|uYPJ5Jmv`vw{d;O1w&?VP$K0pa zn8Sg&?gBgEu-kjfOUtykyGIJc)pu!kSJlzc5w7o&!|tFrt8ab^yRx#vkYD6%j^3TV zqXWEn1LHP^W8=KMyiD-fkzk)oX6a&SaY?`&1FBxFsjAheHc+?Iq1`&e+5x-zLs+lZ z8wKQLHk%a|q8v)Df_Cwgh2m{{tHg2FsZEuS_OGCxB$AN~tWyyH=TMxPE$iO8hc&b929q5(agq zg@t}PnSyUBR0U+IP^2PMC=^s-LzGG-YPZ|M!Zw?YeNBT61OhccUZqkg0m&~|#B(K0 zMNPvo7BDA!%E)14!D#HqVcFxOV=807ot+)Z=ko%Te-G7cHU$X4c=;5c&&QCIOeRUe zi}DJHm)jXCPrRw}c(&DSQ56=wE97^uNx&-bBeq07Cu@K?#6p%V%XD~nC}2${6B&(0 zipP_P5DFDxd0FOrvc$1d#`AIx&jYl{&tU0|2IIXTHCUs;(2qx>Vq>v2*q7MvZi=sQ z7(;v|o7G0`RtMHE;(8AMPeZ92_sSw37vVa{P-B6-hSeMT(MW`jx?N#K_Pv`*rvZ~n zQ)91zfWw}^&WVTx=G z#J|H){Ei~-R>Vd!=ndZx`pS<|n4-9lW@cutI0SX!vkdA7c+h{XfX@&4pn%xn>uc*Y zH#<)jN$NgY1&z8?nVp@bwY4>|u{Jk1X%kSsP2T{{Pk#LNd4046pYC>!$YQqA^z_W% zlP_ONj{<46SQMR3M``TlLcTzo-c13eS}-6K1T^YH;k-J&Yx-R>h##Q#PFi zWL0dIaA-?~1zb235`Xi~7uQ&!@5K>h29|Hk-@bEpm|NV66spbyBvBs%?>pI_-{F^7*;> zD`YTH9dY5EjH~w~w1l9J!Ksy#Mi9?nbKV^mR?GqNlG7chJXlrw#ro65_hmc%|4C=`rT0w<1i?VJXt+7_iVoa_}WjQZG z7XP1nFRbLnD4%*J(24dOcEA?OqFoqP^lTl544NNutN%f|(;oPIL+d zgsL_mOWfBJ?zvlUFG-f3)8P_54>w?Z<_{2~Kn{<^#l^10V!7gWMYSL|h8>JqA*a)I zXGjg!B3XO<35OdnZl8HW3>q=r9b$3;K!0*(Q#;ctxm<3tSghmu{)F_|bGzNbC+`ZW z#b%Q!Et$<=e)7Dx>i!T*9kOpGJtF)T9DSGdCYI3cx9J+}=Tw+(|20r_JVQ(&Ie~(WXHxMd(c4()0X~bUEaWAuh*L~4 z{Jfce-bDO+d^GY}tR)x`VN=nnt$k&zd-uES(1G4cbWwT_nKPGanNAY@YMZG|-MUiC zm%xRquF{FA2Xl`mD%*z3u6;v0PTiP$^Wx62D&cRSP^e5idG&1O$VfN2<_Nj5nS0m9 zvV_bJ*wJ_H!mfOUbpzg0+iJ0LBc(x$AGe{`ocpiW^NTY}$0xWvdVkRau+;zn002ov JPDHLkV1nwjbWZ>P literal 0 HcmV?d00001 diff --git a/examples/positioning/geoflickr/flickrmobile/images/stripes.png b/examples/positioning/geoflickr/flickrmobile/images/stripes.png new file mode 100644 index 0000000000000000000000000000000000000000..75d2bf6c66ea20238f29bc6a086a31a5eecc4add GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqX`U{QArhD8o;BoRRupM@X#YWC z&AhG~(G?Tk7NzVx{!C}(WsWwToQawCX>C!bqA#&neMnfkRqW1Q<;|IC>;n4?mzErn zP+wi?SU!JJ+@EuLGLzb7+U`79``e!BN>uf^N)M~+&)ps{&z2X9U-Do=3(!UePgg&e IbxsLQ0E~z{qyPW_ literal 0 HcmV?d00001 diff --git a/examples/positioning/geoflickr/flickrmobile/images/sun.png b/examples/positioning/geoflickr/flickrmobile/images/sun.png new file mode 100644 index 0000000000000000000000000000000000000000..c5fd36ed39d42ce1f365f27e9637b3a0e1f3e73f GIT binary patch literal 8110 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 q(yB& zOPWXUkqNB+tdolMomUk)4O@@Y0>f0@7|R6Wf>wsUVz{WpCPpkilQcg zazMrbHFi?^h^eTP$s0`hY$(F4q5`C+UT~1K-4Y}AGg`vb|4V@Ugk%psQwKUtrq)4n zi~ayl?>}XwsfFQ|oCri911KdZvV06(ZoptIkgRK^cRbROt)awe@bddCgWg5E^a3C+ ze4p{@*)n|Pcv(Ij+e)eguC%O+Eug3o>;-w}Okk1FM& zN8~dkIqOIQf@txGLFiB{qXTt$XGP%_NlteraJUt;DGl#`sCWX(RJI>FxZF}IpaZ#b zSEfvmxKinmYn?`@&catt1c}#Z@4gL`c0=8WhUK?W8YLwoFG;plgNIAIx) z7l7B^HE!S&BCcpD>MYd2fNEgW$h#H_H%r>h2^EXb*&<9npH#Nzaw-l|^l3!AyS8x$ zSVVdnKcWMPzb`(713-S#;BMFS2cHVviMtG&o)PV?L6s}oSGSmJBSldO zReBRNlcG=r?+pU;Itd=$yu(72gi>g1M8?L*E|rR)h=T7E&6E|+ijGz?4>jmOj~1ZI z6QqmJv0zF3Btt)x-EuoF2U~>*pa_Cw0>&g7PnkzesFn5saR3$5R_-gcTL3s*T%JgL zKs+EqtgQ@1cuWW*GWk%98z{fKgH~;f%RNsID*>uFclM$@4FF%c!)0hvmzVvqdk^w{ zlC?()8U!UNF}uVmC(MpCJZ#eC@qmb>6fGVTOX2-W7f)-WGJUWLKn0*=qGAyP6itR8 z`WEdTCbS9%Av+!7gu?eF{XX!*G7)Rj*Wv(J?QK*&22O_H$cGT;6rwQ^O60Y~-?v;gyP@+kf4yDN`*bz!KN@6vndYW=k zHrWXjGb9KQcp}QC08Iix0-_3l>Qt!303wiB928hZKoTHu07?J`G>w!3fv}%SlqvAj zek3r&3%CHNt~R1ZaTOL$v~PooN5%RESF;<`7_Pwv3Qh+`JpsntSzC=F z6d@oN1=0yH-i8#3q1MHP3RG)Oygd#M$4Qk(EoIfH>-Ko1Y)S^8&X5qM17wJqnDS)O z2|(2eAgE5<1`z}nlRDLYtnh%i$@|a~EM@vW3V_`q82kMMhLi%A6KYG*P)SmtP-jWZ zMdHdj*rce5^J;gjlYwxOY5_C>5fg$Kglr6#IJl|HVBc25fyJEt1W*H2ZzQ(b3~>mw zAi*pt@m7#KST~?JclBk}-@Qg+QX$tXhVJAeS;0$>Z_F@zc7!=!}(@r;lHC2Kd|srE^$L@0Vpe05Gqavsi}Ll)mG zE-rC34!N;IkG&fXzp&1CuFKp-yYd2n?=}_MWRTaakL%V9RZF3W5DhYEZXE+^Fk228o481Rw^X0znld$<%f9lrZ?f5+=(+0um1v z3&Ytaw{nJu7~V&fyTTAC^4~*Wd6b|R5^!Hb&h&7BPrxnvU_hs$s^Xp549hN z-x$8>K44u@jLdVUn{zg9H%x=|YtTSV5*X z%%z8v9^yQ>4DbUWbEaH+INqe0wO3@^JV)tr1%snA=xK|_=w&?l@}?KmR9;uqU+g09 zR>+1vHXhGKj(VbR%022Kp9fxzG^?=<%Ri_aujeFhc4>PtrL=KAAbmxdG$N&!5o$-t z^sYh`#q4~wHfR+$-Cl8wszPKvFkNVTDQD;Iaf44pMq>5x^sc>yt#?Qn#&kJU^rb^r0;$4 zq%qjzg=;(N{xs?z2age1p( z?}}RGgnSi2Woz2_5&QaSr6R48-fP_l<7E3S?x-EtP!WTa2P*=p9Axup^*l_9J3Pr$ zAr?@TNdo~=OzFCqhK?y?c@OpuoEP9MhZG)MIPf^2+=b-ItQ@^xe@B6O7*{jN8!6RV zKO?PvosPZvk~iiR{qpCPBAd|G@j2c(E~=Ujuzaqx3)hKHA>Q3Z+36sdTNbxXnu;`P z7^}2C(l?d`H-|#M_4474fR!kRd6+c19k{Yd@!CZp*Mn<(vNh}0ic{{-M(4vX(Kgpe zhbebufF1#=4y~#Xs!i8-J%|ECVNw==2G~y-#6yCEgaYDnNST4N6k$A^fKoVwae&(q z6h6x49;&=lspBFf-ymR1#`9GvDkFA{8Wqi-qxS9_`nhkcP!l5nBl*)WDU51H-FSw~ zxzhoszY#iflhK-61hDIL_U)wy;KkeH%J-jF5|>RD)6zWLLv&*sJiA1Us@VQ@GK00b z8kpzeo0QL@Ig@}a~1BiiOUyy|PsWhhR5=6a$2DA0h1_ zq0F zDfuc-h0dL&kA0>3Bi46c|ExytYNCzj(ajaDoPjL1LF8cims$0-cFjkWZyldIkzd(S zDjMAw9K(i>j1NF7h43a21mYQR9+Z?2I|i8{pc4>iQu_$wDHwa;Vh^0}AdI)bqit}J zBa8ydT_7(_uW_RGT4xb&z?)eq7M_F4uTeC*?xYxo*1(z0##n!Q=)=>e*1YO0X;*nD zv^Q2p8^hh&ybt%9_xrE@zX0H|(+ZCsQBc9t>F+}R<2>Psjz#V)qLD&nU^so?5en5W zzzhi#9^h!$+*#YR{j=_q5qvwE0qGV&*AwXK3{+(XDrrMSO^~UAbUe-J10>`SpMuK) zINt+jy9nbRIClsGLAgilRJ1dDs_|xH*1oAE>Wj!lxP=qss*521HP6NC!CdMmk1LPv z4%0L%DBgH7qV90s(AcT^?k<|nec=5e?+etw2cZLnQ*F)pqbCunUnJYV0n1NHve!hs zwI+E~8*PP0wl8OoOW&PJDr|KV&zs*4=BmEA

;0zh{wA6OyP`PpX?5+&HR6pc9-BAki5H0 zByU68bx5^Jdi<^wtwng<5@1Cse;J?uPDbxFhe;A8w{D#I@c?=4DTV&>DUezUvh%hm z`=n~`%u#jwlJseZipKrWd4HA>n;rECYTSfh#=Aw(^ocbeuZ zc0TC&nh#|m^6IaUly+VMw2DWYuzr>sJ4Y1dd+4P%;g3~<8yV6e5|t@so1Yh(HYg46 zpgnku#z{chc?jA=g4Bdmlg#iEAS#H6QlS7R4X~}iqKl}wp+@yo*v6>%8j$^wRPhMy zEHw$^|AqQ!%k>gx%OfWc7dyE0_Bx*aY!mFNF3)zeNZUq^m=H4j+(Jt9`yb)f?km4d z@NpeVrTKVeEtI_&GbcuvXjzB(d1kaz&nXW^5ZAaw!7+`K~grnCozxONw* z6m~8YuVd8lQZ24@&wri4xP!vXJlXfhgdOckM^E*IncVU^_KH$mzMSj;fEa zqGSCC@yfQD>u=Komjs^%;N7`)QYu_e%=G!SkNUM~R`PZ*`bk63;%8zzyH;O$^Kl{g%Ws~}4r7B^Bua%B| z>-G(>@|QnDXs63a#yhg9&g--${P-11=N=u4qRWI@3CJ~sY6Zp(71hUq%Q2NkRK(0& z4-nZSK?PA5iml`#bd!9%$4Quzw3*nM)w8UkHp}9&dS-Z*0N~&JNg?0;DaJCa{C=aP zPk+bB*PkPN4xlGC0c?JBTf1+mxlZCvd*I*m%v;+xr8cgjGB~Hd*UR{qp*RBR!I7$P z@2ra3r$EC?B=rXbvL+-!k9ow_10!q5Mqo8gi7Ez4s-$Q@6*I=5!ra=tNcqe$tW;L9 z)O!`7`Yc}jm8YFO$j=eevZFeJ<}qpO={~oZYh{e&rL2lYX}_ z!y9^`H==(6U;!3I&5^=Lx7xV`J%l7fz>c7?D_k2RE&6JU2N*^vNs{aS?v`k^fmm`* ze1XEG&^4y$$~&gYb@)fD?)FEh_b;Klai9G4Zyt{C`OAU6HvF*jfFQPw zu>etUrahB$TqAB2dwMYP1<`p)q6t7!IL)PzpseMTJr% zV?j0?0!9ONh<0#@XySe^f{~snp|Tt^1d1dhLu1$2bYSry<8kJ=l=1dTh`A*Ih{lZS z-evBt%zhjXGHbN@Ls}dksKQDFV_jUryAet&kg7+$NP}hNt;;Zi6r2_NYs&bdXJ}Vo zb&a6%OiE-n1P#t|m^8y0P_%CDNHj)zZczh(R=atK`@Qn|smjOcAoFA+hvM53B?MH= zS~taLZ$!WP4WQ94(ANQ^pp7bEg2;Ho-?nS|Sm}|mfV`7Lr+Hl&EaoNt^Ll|t14R|_ z-01adQC$N7H37xhP&YO{K3|Tozj}Gwdt*>=Xvo{A50|U|?L--e*Zx6MUi(K{{`>4* zXZQ|QLcjMU$+e|f9!2fL&t{Ye(BFt-$>ZCEmh3`(s1xRY;~)*7WQgdVpS|_#6_FR- zH@Of2!gjryk5v5*OWfa}sP6$LeQW_K3g~-%9R>i%J6|#%@E+#Ijk`geTG3Br^6Dq$ zLaVFy)+-_auH_#{Mvun`mCD_Rl=ru&(chM@RJm77`P*{+UjTy4T^PfT-T(jq07*qo IM6N<$f(!}_`Tzg` literal 0 HcmV?d00001 diff --git a/examples/positioning/geoflickr/flickrmobile/images/titlebar.png b/examples/positioning/geoflickr/flickrmobile/images/titlebar.png new file mode 100644 index 0000000000000000000000000000000000000000..aa35c9e1b6583bbeb5b0b2ba070f236b36297eda GIT binary patch literal 1327 zcmV+~1000E`Nkl`f!%0rEEPTYqq|G_0m&4Ap+E=_4Pe8zLK6_*upKav37Z`-H{x7y3c#8H{EGoc5R3VI_Um%_ z{cq>z=Wuv<2-aF~j;AU6x)Gg{uVt*>)FaDuXZRLuJ%>YDeXWBeTsNj0yXWU;xV^oF zySuyJ%d)&N0l=RhKK$~>>FFti6Y%)>NNrIRG&F+n-jnGAJEqQ^-vaR(C*{06r?2Nx z+9=}=JN8=UtCWJv|W%Syrb)RaKA~+Y15*bPr6Em7}HGv0pYBVSnvU?<3M-!x;7~}0JfsSJ_GK74>&*pg^Q{TJf*L#xTlqQ|rgpLhP&7>7eVQo(;d}$q zva}p0Dc)ObN(wRt{^aBYa+J(^T?EH#n(!h%u-?4DbUJP5-pvM6?_(xB_2Wf)Dqm18 zm107J$XKR605P@qHJi>i^P|nP@Oj2cFeE<0s`sllP`*;n)_Rml#g*Y&89-oS zA`|t1T?ac;Ule&^M%osX@0|?v^cp|guX^)ErH3$lX(LC_Q#{-K<3ZY!Du1bix^B^h=)vUfe90@eZGAw8y&6-^pE#Wx_d zf@9sc+THHhc=&84V(?9r_bky!N@3v2gW5@-jdiU5c=^{ZlG^ehk)gTOAIV>gbun)NbT6A zcZ#G>CtrV=H@!<4>@#Lx&xW!Bz=F35{^zUB8qm<;RJ1>5_W2rzNWFMjh2qG_QhFcj z5lb*n6_KR&M|zO#rO@@E9X$wL;@`{BDa2S;WZl$v^SfkX?9OYc6A10_dx(6h2-?)L zLKnH@dg%*Ru>Yrwnw)qGa{Ya`uabZ5B;N}HU*E`*xB2qKfULEnkXHU0r06t)k;*P> zEBSIVbaL+nK`+o40IeR?s}sqcq2-k0`=%`GiDssyWk)$voY&W-pK2w|jQ lCTB{<{F8{3tU5o7_z#DajA}HOxLyDN002ovPDHLkV1k~ddi4MR literal 0 HcmV?d00001 diff --git a/examples/positioning/geoflickr/flickrmobile/images/titlebar.sci b/examples/positioning/geoflickr/flickrmobile/images/titlebar.sci new file mode 100644 index 0000000..0418d94 --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/images/titlebar.sci @@ -0,0 +1,5 @@ +border.left: 10 +border.top: 12 +border.bottom: 12 +border.right: 10 +source: titlebar.png diff --git a/examples/positioning/geoflickr/flickrmobile/images/toolbutton.png b/examples/positioning/geoflickr/flickrmobile/images/toolbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..11310013eedc808ca278e3068d7779e708422726 GIT binary patch literal 2550 zcmVe zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00~}6L_t(&-kn;@Ze&RjJrQ}U z%I)@yx>vMhM)Vd??@(_*@_Yb7zz49M58wmf14gXj57@F~Rl)u9qI_Ld^LrWu%xs>-#eH7mxJ+t3r+Iu8>W-a$yaW&t9&L`W+wBlS zfLl#pt~TpGtyZfqA$po-jn!)P$DjWACx19QJHykbPjPf~R4mp`GJ`_tnt(B$NSb8* zzQ_XH0$@3QS~Nt>JtdI!X60wP2}Fdu54Tva*SNgA#6MqugMWPW6^@UOzjlnD5dfT= zoc#X9ix;2&_Se6`#l;1#uCB2Ehi68#3$>GQ&EiUM!f+9^cl;4}c5EU-m|w1^6+#F& zK0e0jCqKliSFiB*FTTLXA3grv1OS`O=JT_&Gn}8F-8ETgyHgg=*R!L*lAJU z{d}G_7_K?rx!~xVyW<`}gnh^5si>^ZGTuyS(`M1OQK- zJi%tO!GCt&#n|)yL5pugLm^OmbDx@NlF)fEk><<7FboZW0t6@k(*1ymcxVtz(*#lg z>~M2)joaHB{OI{7`1arb`fLJ#^?IFS*aF2~Q>LCE71Q5IL$F-wUE-g~UqYuw-8W91xn+bx`P@ZMvZCNMK50N8G~P*pH9 zL^DqZ&ENt)LYaKi!;NQmGju%+0H$cKFZvjcX1 z3lfFb0OuH6e~;a63r9c*Ap$T>6TJ5j5$txmIfa;AP=S|S+yPBAj$oxZdvWW^Iga8uDf^wmPB@BB2s9<3e0NHtnC{zg|fO9SapsENVl)F#7d{_lx z-pvfaKzx?e2qF=uH4{adCkVLNp4_%?~t) zssKzS1E{~dcBd==y(}PTb9-<6EPnMkiUV1u74Wpq1j3YavSEM~xjDL{X3rBWDBq-> z<#R!#$?=6sL)*oQ4jItoebN65fQA9dSR|Tcb;siL5&)1=rR%hnCY8XlW7Txs2S9yq z0>A>#1BT^|;r6*U%nJZ|3@{&s&I^ZKU`=15OjQd~&kxk8G&4!G%Nngd3jkWw5uHpE zTE8-6K)Mh@i0G<9c`0x*wPo3Hi)5_megz#P>d zGxL=RL<$L!9Z94{MURcu-u1mZwpfV5?^^&0uOC9kjvofVUN1CY7d*fO<_XGa!O?dX zf~O3E191T9VoOx!Qh2aqDbFv(5YJLB(Cq5i_6u~3_ zwbCyDnC{Q~z#ImH9Be7Nq`|V2l%*Shs$cX-%JG%P28av*JeS*c9o7gn09Yqz84w3| zb2gJQ?adRETCugS_ytATwLZdBc$unVohIe{UT2{?1jH|FOJI8M;l1ytLGS$x0D06;<&KZf=^ zKY%!<#cH>~WXj~R2rdiYS@o08_Vcb{k)!l zS%IomIEvfYl$2q$^oEvSqYJt8Ub;xAvQZ7f=$f^{T^~S1nApJ)gE(4Rs=Ri5z0j3J zn$D49XHcjugx68ww7}X05jM&teE*hOT_2dK$1M0UOh0GN9i%v`kmmx6Dg0nD7!gpI3_$P_2Y zXj^4Hpb~@jABRyBv#xkv7@Q)ZX^GWhn9R`BTRahgC%42QakQgUs;|?9;zFSmM@zL% zV}$49@j^}607?MEnUtDLmgEhg>NvdsL1X{l?miLb6QL>F?fCeFO zsI4~A0h=mq2wEVgUjNPZ_SA?HcRhSr0ghPFZG_VDS?VlMl^)zw?WNM5^V=(e&bbVP zRz3Tk4%(EbBtv=*8={Q8u2YKVQik-7Z#x$-NEQ+htR;Wx&Yp9`fu<B652VFZWs1RLPz+Io`c{_x1PR z|MypJ{*KUJFE1~D@&5Yy`QyirvDs{LOy>%5{%%hECFmYHFNM)una#u=iB$nYl{xCd z>`+x%o@f%LY03<`ySu}iH*fIv?OUwZ>tEj9e)yIE;PmwL*>=1AaDTV`n{)0{=Nx!) z-Lt<3#d3W%kV<5&zUl_us};i&=sgCA%~?Pyr_&~;w%^s&Ri4EE09*nYhh&1)L;wH) M07*qoM6N<$g3{N*E&u=k literal 0 HcmV?d00001 diff --git a/examples/positioning/geoflickr/flickrmobile/images/toolbutton.sci b/examples/positioning/geoflickr/flickrmobile/images/toolbutton.sci new file mode 100644 index 0000000..9e4f965 --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/images/toolbutton.sci @@ -0,0 +1,5 @@ +border.left: 15 +border.top: 4 +border.bottom: 4 +border.right: 15 +source: toolbutton.png diff --git a/examples/positioning/geoflickr/flickrmobile/nmealog.txt b/examples/positioning/geoflickr/flickrmobile/nmealog.txt new file mode 100644 index 0000000..8c8286d --- /dev/null +++ b/examples/positioning/geoflickr/flickrmobile/nmealog.txt @@ -0,0 +1,1403 @@ +$GPGGA,222437.000,2734.33926,S,15305.44310,E,1,07,1.3,50.6,M,39.2,M,,*72 +$GPGLL,2734.33926,S,15305.44310,E,222437.000,A,A*49 +$GPGSA,A,3,16,25,23,20,13,27,11,,,,,,2.3,1.3,1.9*3D +$GPGST,222437.000,13.3,7.4,6.6,85.1,6.0,6.8,13.7*56 +$GPGSV,3,1,10,16,49,115,42,25,39,269,36,23,58,176,29,20,72,335,35*75 +$GPGSV,3,2,10,19,02,028,,04,06,241,22,13,30,223,30,27,19,284,35*78 +$GPGSV,3,3,10,11,06,337,30,03,13,055,25*7C +$GPRMC,222437.000,A,2734.33926,S,15305.44310,E,33.9,157.8,030308,11.2,W,A*0F +$GPVTG,157.8,T,169.0,M,33.9,N,62.9,K,A*22 +$GPGGA,222438.000,2734.34821,S,15305.44697,E,1,07,1.2,50.8,M,39.2,M,,*79 +$GPGLL,2734.34821,S,15305.44697,E,222438.000,A,A*4D +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222438.000,12.4,6.4,9.3,16.2,6.1,8.3,16.4*5F +$GPGSV,3,1,10,16,49,115,41,25,39,269,36,23,58,176,28,20,72,335,36*74 +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,30,223,28,27,19,284,35*73 +$GPGSV,3,3,10,11,06,337,28,03,13,055,25*75 +$GPRMC,222438.000,A,2734.34821,S,15305.44697,E,33.8,158.3,030308,11.2,W,A*0E +$GPVTG,158.3,T,169.5,M,33.8,N,62.5,K,A*2E +$GPGGA,222439.000,2734.35696,S,15305.45072,E,1,06,1.7,51.2,M,39.2,M,,*78 +$GPGLL,2734.35696,S,15305.45072,E,222439.000,A,A*43 +$GPGSA,A,3,16,25,23,20,13,27,,,,,,,3.3,1.7,2.8*3A +$GPGST,222439.000,10.3,9.1,12.2,44.6,9.8,9.9,25.2*62 +$GPGSV,3,1,10,16,49,115,34,25,39,269,36,23,58,175,29,20,72,335,35*77 +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,30,223,27,27,19,284,32*7B +$GPGSV,3,3,10,11,06,337,28,03,14,055,25*72 +$GPRMC,222439.000,A,2734.35696,S,15305.45072,E,33.2,158.7,030308,11.2,W,A*0E +$GPVTG,158.7,T,169.9,M,33.2,N,61.5,K,A*2F +$GPGGA,222440.000,2734.36580,S,15305.45446,E,1,07,1.3,52.0,M,39.2,M,,*76 +$GPGLL,2734.36580,S,15305.45446,E,222440.000,A,A*49 +$GPGSA,A,3,16,25,23,20,13,27,11,,,,,,2.3,1.3,1.9*3D +$GPGST,222440.000,13.0,8.0,13.4,6.2,7.4,12.2,20.9*64 +$GPGSV,3,1,10,16,49,115,40,25,39,269,38,23,58,175,31,20,72,335,34*72 +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,30,223,26,27,19,284,30*78 +$GPGSV,3,3,10,11,06,337,26,03,14,055,25*7C +$GPRMC,222440.000,A,2734.36580,S,15305.45446,E,33.7,159.1,030308,11.2,W,A*06 +$GPVTG,159.1,T,170.3,M,33.7,N,62.4,K,A*2D +$GPGGA,222441.000,2734.37483,S,15305.45825,E,1,07,1.3,52.7,M,39.2,M,,*7A +$GPGLL,2734.37483,S,15305.45825,E,222441.000,A,A*42 +$GPGSA,A,3,16,25,23,20,13,27,11,,,,,,2.3,1.3,1.9*3D +$GPGST,222441.000,14.0,7.6,14.1,17.6,7.7,12.5,21.0*51 +$GPGSV,3,1,10,16,49,115,41,25,39,269,39,23,58,175,29,20,72,335,35*7A +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,30,223,24,27,19,284,30*7A +$GPGSV,3,3,10,11,06,337,28,03,14,055,25*72 +$GPRMC,222441.000,A,2734.37483,S,15305.45825,E,34.6,159.4,030308,11.2,W,A*0E +$GPVTG,159.4,T,170.6,M,34.6,N,64.1,K,A*28 +$GPGGA,222442.000,2734.38407,S,15305.46216,E,1,06,1.3,53.3,M,39.2,M,,*77 +$GPGLL,2734.38407,S,15305.46216,E,222442.000,A,A*4B +$GPGSA,A,3,16,25,20,13,27,11,,,,,,,2.3,1.3,1.9*3C +$GPGST,222442.000,16.6,7.0,14.4,14.6,7.0,12.8,21.6*5A +$GPGSV,3,1,10,16,49,115,40,25,39,269,38,23,58,175,22,20,72,335,35*71 +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,30,223,25,27,19,284,29*73 +$GPGSV,3,3,10,11,06,337,27,03,14,055,25*7D +$GPRMC,222442.000,A,2734.38407,S,15305.46216,E,35.5,159.3,030308,11.2,W,A*02 +$GPVTG,159.3,T,170.5,M,35.5,N,65.8,K,A*26 +$GPGGA,222443.000,2734.39347,S,15305.46609,E,1,05,1.8,53.8,M,39.2,M,,*7D +$GPGLL,2734.39347,S,15305.46609,E,222443.000,A,A*42 +$GPGSA,A,3,16,25,20,27,11,,,,,,,,2.8,1.8,2.1*35 +$GPGST,222443.000,11.3,6.5,14.6,14.5,6.6,13.0,18.4*5A +$GPGSV,3,1,10,16,49,115,40,25,39,269,38,23,58,175,22,20,72,335,36*72 +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,30,223,26,27,19,284,31*79 +$GPGSV,3,3,10,11,06,337,28,03,14,055,25*72 +$GPRMC,222443.000,A,2734.39347,S,15305.46609,E,36.2,159.4,030308,11.2,W,A*08 +$GPVTG,159.4,T,170.6,M,36.2,N,67.0,K,A*2C +$GPGGA,222444.000,2734.40297,S,15305.47000,E,1,06,1.3,54.1,M,39.2,M,,*70 +$GPGLL,2734.40297,S,15305.47000,E,222444.000,A,A*49 +$GPGSA,A,3,16,25,20,13,27,11,,,,,,,2.3,1.3,1.9*3C +$GPGST,222444.000,17.6,6.3,12.7,14.4,6.3,11.4,16.2*55 +$GPGSV,3,1,10,16,49,115,38,25,39,269,38,23,58,175,22,20,72,335,35*7E +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,30,223,25,27,19,284,29*73 +$GPGSV,3,3,10,11,06,337,25,03,14,055,23*79 +$GPRMC,222444.000,A,2734.40297,S,15305.47000,E,36.5,159.5,030308,11.2,W,A*05 +$GPVTG,159.5,T,170.8,M,36.5,N,67.5,K,A*21 +$GPGGA,222445.000,2734.41247,S,15305.47390,E,1,07,1.3,54.2,M,39.2,M,,*75 +$GPGLL,2734.41247,S,15305.47390,E,222445.000,A,A*4E +$GPGSA,A,3,16,25,23,20,13,27,11,,,,,,2.3,1.3,1.9*3D +$GPGST,222445.000,16.0,7.0,14.4,10.4,6.7,13.0,20.7*52 +$GPGSV,3,1,10,16,49,115,36,25,39,269,36,23,58,175,22,20,72,335,34*7F +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,30,223,26,27,19,284,31*79 +$GPGSV,3,3,10,11,06,337,26,03,14,055,23*7A +$GPRMC,222445.000,A,2734.41247,S,15305.47390,E,36.6,159.7,030308,11.2,W,A*03 +$GPVTG,159.7,T,170.9,M,36.6,N,67.8,K,A*2C +$GPGGA,222446.000,2734.42201,S,15305.47790,E,1,07,1.3,54.4,M,39.2,M,,*75 +$GPGLL,2734.42201,S,15305.47790,E,222446.000,A,A*48 +$GPGSA,A,3,16,25,23,20,13,27,11,,,,,,2.3,1.3,1.9*3D +$GPGST,222446.000,13.0,7.4,12.3,6.6,6.9,11.2,17.9*60 +$GPGSV,3,1,10,16,49,115,36,25,39,269,37,23,58,175,27,20,72,335,35*7A +$GPGSV,3,2,10,19,02,028,,04,06,241,23,13,30,223,30,27,19,284,32*7E +$GPGSV,3,3,10,11,06,337,27,03,14,055,23*7B +$GPRMC,222446.000,A,2734.42201,S,15305.47790,E,36.6,159.3,030308,11.2,W,A*01 +$GPVTG,159.3,T,170.5,M,36.6,N,67.7,K,A*2B +$GPGGA,222447.000,2734.43157,S,15305.48195,E,1,07,1.3,54.3,M,39.2,M,,*7E +$GPGLL,2734.43157,S,15305.48195,E,222447.000,A,A*44 +$GPGSA,A,3,16,25,23,20,13,27,11,,,,,,2.3,1.3,1.9*3D +$GPGST,222447.000,10.7,6.7,10.5,6.6,6.2,9.6,15.5*5B +$GPGSV,3,1,11,16,49,115,32,25,39,269,37,23,58,175,28,20,72,335,33*76 +$GPGSV,3,2,11,19,02,028,,04,06,241,23,13,30,223,30,27,19,284,32*7F +$GPGSV,3,3,11,11,06,337,29,01,,,19,03,14,055,23*7D +$GPRMC,222447.000,A,2734.43157,S,15305.48195,E,36.7,159.1,030308,11.2,W,A*0E +$GPVTG,159.1,T,170.3,M,36.7,N,67.9,K,A*20 +$GPGGA,222448.000,2734.44111,S,15305.48610,E,1,08,1.1,54.1,M,39.2,M,,*71 +$GPGLL,2734.44111,S,15305.48610,E,222448.000,A,A*44 +$GPGSA,A,3,16,25,23,20,13,27,11,03,,,,,2.0,1.1,1.6*30 +$GPGST,222448.000,20.4,10.5,8.8,52.5,8.7,9.1,15.2*6C +$GPGSV,3,1,11,16,49,115,27,25,39,269,38,23,58,175,26,20,72,335,31*71 +$GPGSV,3,2,11,19,02,028,,04,06,241,23,13,30,223,37,27,19,284,27*7C +$GPGSV,3,3,11,11,06,337,27,01,,,19,03,14,055,23*73 +$GPRMC,222448.000,A,2734.44111,S,15305.48610,E,36.8,158.7,030308,11.2,W,A*06 +$GPVTG,158.7,T,169.9,M,36.8,N,68.2,K,A*2E +$GPGGA,222449.000,2734.45068,S,15305.49044,E,1,08,1.1,53.6,M,39.2,M,,*78 +$GPGLL,2734.45068,S,15305.49044,E,222449.000,A,A*4D +$GPGSA,A,3,16,25,23,20,13,27,11,03,,,,,2.0,1.1,1.6*30 +$GPGST,222449.000,17.1,8.8,10.2,14.2,8.2,9.3,17.4*6D +$GPGSV,3,1,11,16,49,115,28,25,39,269,37,23,58,175,25,20,72,335,28*7A +$GPGSV,3,2,11,19,02,028,,04,06,241,23,13,30,223,37,27,19,284,32*78 +$GPGSV,3,3,11,11,06,337,27,01,,,19,03,14,055,24*74 +$GPRMC,222449.000,A,2734.45068,S,15305.49044,E,37.2,157.8,030308,11.2,W,A*04 +$GPVTG,157.8,T,169.0,M,37.2,N,68.9,K,A*27 +$GPGGA,222450.000,2734.46041,S,15305.49485,E,1,08,1.1,53.3,M,39.2,M,,*74 +$GPGLL,2734.46041,S,15305.49485,E,222450.000,A,A*44 +$GPGSA,A,3,16,25,23,20,13,27,11,03,,,,,2.0,1.1,1.6*30 +$GPGST,222450.000,17.4,8.9,12.7,10.1,11.5,8.2,16.5*5E +$GPGSV,3,1,11,16,49,115,25,25,39,269,36,23,58,175,30,20,72,336,28*71 +$GPGSV,3,2,11,19,02,028,,04,06,241,21,13,30,223,38,27,19,284,34*73 +$GPGSV,3,3,11,11,06,337,27,01,,,19,03,14,055,22*72 +$GPRMC,222450.000,A,2734.46041,S,15305.49485,E,37.7,157.9,030308,11.2,W,A*09 +$GPVTG,157.9,T,169.1,M,37.7,N,69.8,K,A*22 +$GPGGA,222451.000,2734.47033,S,15305.49924,E,1,08,1.1,53.1,M,39.2,M,,*75 +$GPGLL,2734.47033,S,15305.49924,E,222451.000,A,A*47 +$GPGSA,A,3,16,25,23,20,13,27,11,03,,,,,2.0,1.1,1.6*30 +$GPGST,222451.000,14.1,8.0,10.5,14.3,9.5,7.5,15.0*61 +$GPGSV,3,1,11,16,49,115,27,25,39,269,38,23,58,175,28,20,72,336,25*79 +$GPGSV,3,2,11,19,02,028,,04,06,241,21,13,30,223,38,27,19,284,34*73 +$GPGSV,3,3,11,11,06,337,24,01,,,19,03,14,055,25*76 +$GPRMC,222451.000,A,2734.47033,S,15305.49924,E,38.1,158.1,030308,11.2,W,A*04 +$GPVTG,158.1,T,169.3,M,38.1,N,70.5,K,A*2B +$GPGGA,222452.000,2734.48022,S,15305.50375,E,1,08,1.1,52.5,M,39.2,M,,*7A +$GPGLL,2734.48022,S,15305.50375,E,222452.000,A,A*4D +$GPGSA,A,3,16,25,23,20,13,27,11,03,,,,,2.0,1.1,1.6*30 +$GPGST,222452.000,24.1,13.9,9.7,80.6,12.7,9.0,21.0*54 +$GPGSV,3,1,11,16,49,115,29,25,39,269,38,23,58,175,27,20,72,336,30*7C +$GPGSV,3,2,11,19,02,028,,04,06,241,21,13,30,223,35,27,19,284,34*7E +$GPGSV,3,3,11,11,06,337,22,01,,,19,03,14,055,24*71 +$GPRMC,222452.000,A,2734.48022,S,15305.50375,E,38.3,157.9,030308,11.2,W,A*0B +$GPVTG,157.9,T,169.1,M,38.3,N,70.9,K,A*20 +$GPGGA,222453.000,2734.49019,S,15305.50802,E,1,06,1.7,52.1,M,39.2,M,,*75 +$GPGLL,2734.49019,S,15305.50802,E,222453.000,A,A*4E +$GPGSA,A,3,16,25,23,20,13,27,,,,,,,3.3,1.7,2.8*3A +$GPGST,222453.000,10.4,15.4,9.3,66.3,13.4,9.6,24.6*52 +$GPGSV,3,1,11,16,49,115,31,25,39,269,36,23,58,175,28,20,71,336,25*73 +$GPGSV,3,2,11,19,02,028,,04,06,241,21,13,30,223,33,27,19,284,31*7D +$GPGSV,3,3,11,11,06,337,22,01,,,19,03,14,055,24*71 +$GPRMC,222453.000,A,2734.49019,S,15305.50802,E,38.3,159.1,030308,11.2,W,A*0E +$GPVTG,159.1,T,170.3,M,38.3,N,70.9,K,A*2C +$GPGGA,222454.000,2734.50008,S,15305.51221,E,1,07,1.3,52.1,M,39.2,M,,*75 +$GPGLL,2734.50008,S,15305.51221,E,222454.000,A,A*4B +$GPGSA,A,3,16,25,23,20,13,27,11,,,,,,2.3,1.3,1.9*3D +$GPGST,222454.000,12.5,11.7,8.3,70.6,10.4,8.0,19.0*5E +$GPGSV,3,1,11,16,49,115,30,25,39,269,36,23,58,175,26,20,71,336,28*71 +$GPGSV,3,2,11,19,02,028,,04,06,241,21,13,30,223,32,27,19,284,30*7D +$GPGSV,3,3,11,11,06,337,24,01,,,18,03,14,055,24*76 +$GPRMC,222454.000,A,2734.50008,S,15305.51221,E,38.1,159.4,030308,11.2,W,A*0C +$GPVTG,159.4,T,170.6,M,38.1,N,70.5,K,A*22 +$GPGGA,222455.000,2734.50992,S,15305.51642,E,1,07,1.3,52.2,M,39.2,M,,*7C +$GPGLL,2734.50992,S,15305.51642,E,222455.000,A,A*41 +$GPGSA,A,3,16,25,23,20,13,27,11,,,,,,2.3,1.3,1.9*3D +$GPGST,222455.000,11.5,10.8,9.3,83.5,9.9,8.6,23.3*65 +$GPGSV,3,1,11,16,49,115,33,25,39,269,36,23,58,175,32,20,71,336,31*7F +$GPGSV,3,2,11,19,02,028,,04,06,241,21,13,30,223,29,27,19,284,29*7F +$GPGSV,3,3,11,11,06,337,28,01,,,18,03,14,055,24*7A +$GPRMC,222455.000,A,2734.50992,S,15305.51642,E,37.8,159.0,030308,11.2,W,A*04 +$GPVTG,159.0,T,170.2,M,37.8,N,70.0,K,A*21 +$GPGGA,222456.000,2734.51963,S,15305.52059,E,1,07,1.3,52.5,M,39.2,M,,*78 +$GPGLL,2734.51963,S,15305.52059,E,222456.000,A,A*42 +$GPGSA,A,3,16,25,23,20,13,27,11,,,,,,2.3,1.3,1.9*3D +$GPGST,222456.000,11.3,9.0,13.1,11.0,8.4,11.9,20.9*55 +$GPGSV,3,1,11,16,49,115,31,25,39,269,37,23,58,175,27,20,71,336,33*7A +$GPGSV,3,2,11,19,02,028,,04,06,241,19,13,30,223,29,27,19,284,32*7E +$GPGSV,3,3,11,11,06,337,30,01,,,18,03,14,055,24*73 +$GPRMC,222456.000,A,2734.51963,S,15305.52059,E,37.3,158.8,030308,11.2,W,A*05 +$GPVTG,158.8,T,170.0,M,37.3,N,69.1,K,A*28 +$GPGGA,222457.000,2734.52908,S,15305.52467,E,1,06,1.3,53.2,M,39.2,M,,*79 +$GPGLL,2734.52908,S,15305.52467,E,222457.000,A,A*44 +$GPGSA,A,3,16,25,20,13,27,11,,,,,,,2.3,1.3,1.9*3C +$GPGST,222457.000,20.4,7.8,12.0,8.1,7.3,10.9,17.9*63 +$GPGSV,3,1,11,16,49,115,37,25,39,269,37,23,58,175,24,20,71,336,35*79 +$GPGSV,3,2,11,19,02,028,,04,06,241,19,13,30,223,29,27,19,284,32*7E +$GPGSV,3,3,11,11,06,337,28,01,,,18,03,14,055,23*7D +$GPRMC,222457.000,A,2734.52908,S,15305.52467,E,36.2,158.7,030308,11.2,W,A*0C +$GPVTG,158.7,T,169.9,M,36.2,N,67.1,K,A*28 +$GPGGA,222458.000,2734.53845,S,15305.52866,E,1,06,1.6,54.2,M,39.2,M,,*70 +$GPGLL,2734.53845,S,15305.52866,E,222458.000,A,A*4F +$GPGSA,A,3,16,25,23,20,27,11,,,,,,,2.6,1.6,2.1*34 +$GPGST,222458.000,16.7,7.3,13.9,11.0,7.0,12.5,17.9*5D +$GPGSV,3,1,11,16,49,115,36,25,39,269,37,23,58,175,24,20,71,336,35*78 +$GPGSV,3,2,11,19,02,028,19,04,06,241,19,13,30,223,26,27,19,284,31*7A +$GPGSV,3,3,11,11,06,337,23,01,,,18,03,14,055,23*76 +$GPRMC,222458.000,A,2734.53845,S,15305.52866,E,35.9,159.1,030308,11.2,W,A*08 +$GPVTG,159.1,T,170.3,M,35.9,N,66.5,K,A*20 +$GPGGA,222459.000,2734.54772,S,15305.53309,E,1,08,1.1,55.6,M,39.2,M,,*72 +$GPGLL,2734.54772,S,15305.53309,E,222459.000,A,A*41 +$GPGSA,A,3,16,25,23,20,13,27,11,03,,,,,2.0,1.1,1.6*30 +$GPGST,222459.000,17.5,6.5,9.3,15.5,6.1,8.3,15.2*5C +$GPGSV,3,1,11,16,49,115,38,25,39,268,36,23,58,175,35,20,71,336,35*76 +$GPGSV,3,2,11,19,02,028,19,04,06,241,19,13,30,223,33,27,19,284,34*7B +$GPGSV,3,3,11,11,06,337,29,01,,,18,03,14,055,22*7D +$GPRMC,222459.000,A,2734.54772,S,15305.53309,E,35.9,156.8,030308,11.2,W,A*00 +$GPVTG,156.8,T,168.0,M,35.9,N,66.4,K,A*2D +$GPGGA,222500.000,2734.55655,S,15305.53845,E,1,08,1.1,56.1,M,39.2,M,,*7D +$GPGLL,2734.55655,S,15305.53845,E,222500.000,A,A*4A +$GPGSA,A,3,16,25,23,20,13,27,11,03,,,,,2.0,1.1,1.6*30 +$GPGST,222500.000,14.5,9.8,7.9,83.3,7.3,9.0,14.3*5C +$GPGSV,3,1,11,16,49,115,36,25,39,268,31,23,58,175,39,20,71,336,33*75 +$GPGSV,3,2,11,19,02,028,21,04,06,241,19,13,30,223,30,27,19,284,29*7F +$GPGSV,3,3,11,11,06,337,25,01,,,18,03,14,055,28*7B +$GPRMC,222500.000,A,2734.55655,S,15305.53845,E,35.9,151.6,030308,11.2,W,A*02 +$GPVTG,151.6,T,162.8,M,35.9,N,66.5,K,A*27 +$GPGGA,222501.000,2734.56495,S,15305.54489,E,1,08,1.1,57.0,M,39.2,M,,*7A +$GPGLL,2734.56495,S,15305.54489,E,222501.000,A,A*4D +$GPGSA,A,3,16,25,23,20,13,27,11,03,,,,,2.0,1.1,1.6*30 +$GPGST,222501.000,14.3,8.0,10.9,27.0,8.0,9.5,15.6*64 +$GPGSV,3,1,11,16,49,115,41,25,39,268,31,23,58,175,36,20,71,336,31*78 +$GPGSV,3,2,11,19,02,028,21,04,06,241,19,13,30,223,27,27,19,284,29*79 +$GPGSV,3,3,11,11,06,337,22,01,,,18,03,14,055,31*74 +$GPRMC,222501.000,A,2734.56495,S,15305.54489,E,36.5,145.7,030308,11.2,W,A*0E +$GPVTG,145.7,T,156.9,M,36.5,N,67.5,K,A*2B +$GPGGA,222502.000,2734.57337,S,15305.55181,E,1,06,1.5,57.9,M,39.2,M,,*78 +$GPGLL,2734.57337,S,15305.55181,E,222502.000,A,A*4C +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222502.000,14.5,7.0,11.9,20.7,7.1,10.5,15.0*5F +$GPGSV,3,1,11,16,49,115,43,25,39,268,35,23,58,175,37,20,71,336,34*7A +$GPGSV,3,2,11,19,02,028,21,04,06,241,19,13,30,223,23,27,19,284,33*76 +$GPGSV,3,3,11,11,06,337,22,01,,,18,03,14,055,34*71 +$GPRMC,222502.000,A,2734.57337,S,15305.55181,E,37.4,143.8,030308,11.2,W,A*06 +$GPVTG,143.8,T,155.0,M,37.4,N,69.3,K,A*20 +$GPGGA,222503.000,2734.58184,S,15305.55887,E,1,08,1.1,58.5,M,39.2,M,,*7A +$GPGLL,2734.58184,S,15305.55887,E,222503.000,A,A*47 +$GPGSA,A,3,16,25,23,20,13,27,11,03,,,,,2.0,1.1,1.6*30 +$GPGST,222503.000,13.4,6.4,14.1,0.3,5.9,12.9,21.5*60 +$GPGSV,3,1,11,16,49,115,43,25,39,268,35,23,58,175,38,20,71,336,35*74 +$GPGSV,3,2,11,19,02,028,19,04,06,241,19,13,30,223,23,27,19,284,32*7C +$GPGSV,3,3,11,11,06,337,26,01,,,17,03,14,055,34*7A +$GPRMC,222503.000,A,2734.58184,S,15305.55887,E,37.8,143.4,030308,11.2,W,A*0D +$GPVTG,143.4,T,154.6,M,37.8,N,70.0,K,A*2C +$GPGGA,222504.000,2734.59032,S,15305.56580,E,1,07,1.2,58.9,M,39.2,M,,*79 +$GPGLL,2734.59032,S,15305.56580,E,222504.000,A,A*44 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222504.000,24.6,6.2,13.3,3.6,5.7,12.2,20.2*67 +$GPGSV,3,1,11,16,49,115,43,25,39,268,35,23,58,175,39,20,71,336,35*75 +$GPGSV,3,2,11,19,02,028,19,04,06,241,19,13,30,223,22,27,19,284,31*7E +$GPGSV,3,3,11,11,06,337,25,01,,,17,03,14,055,30*7D +$GPRMC,222504.000,A,2734.59032,S,15305.56580,E,37.6,143.6,030308,11.2,W,A*02 +$GPVTG,143.6,T,154.8,M,37.6,N,69.7,K,A*21 +$GPGGA,222505.000,2734.59874,S,15305.57271,E,1,06,1.5,59.4,M,39.2,M,,*70 +$GPGLL,2734.59874,S,15305.57271,E,222505.000,A,A*47 +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222505.000,17.4,5.5,11.8,3.4,5.0,10.8,17.3*61 +$GPGSV,3,1,11,16,49,115,44,25,39,268,35,23,58,175,39,20,71,336,36*71 +$GPGSV,3,2,11,19,02,028,21,04,06,241,19,13,30,223,24,27,19,284,31*73 +$GPGSV,3,3,11,11,06,337,,01,,,17,03,14,055,29*72 +$GPRMC,222505.000,A,2734.59874,S,15305.57271,E,37.3,143.7,030308,11.2,W,A*05 +$GPVTG,143.7,T,154.9,M,37.3,N,69.1,K,A*22 +$GPGGA,222506.000,2734.60703,S,15305.57943,E,1,07,1.2,60.1,M,39.2,M,,*75 +$GPGLL,2734.60703,S,15305.57943,E,222506.000,A,A*4B +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222506.000,16.1,6.2,11.7,15.3,6.1,10.4,17.4*54 +$GPGSV,3,1,11,16,49,115,43,25,39,268,34,23,58,175,37,20,71,336,36*79 +$GPGSV,3,2,11,19,02,028,21,04,06,241,,13,30,223,24,27,19,284,31*7B +$GPGSV,3,3,11,11,06,337,,01,,,17,03,14,055,28*73 +$GPRMC,222506.000,A,2734.60703,S,15305.57943,E,36.5,143.9,030308,11.2,W,A*00 +$GPVTG,143.9,T,155.1,M,36.5,N,67.6,K,A*2B +$GPGGA,222507.000,2734.61507,S,15305.58593,E,1,07,1.2,60.9,M,39.2,M,,*75 +$GPGLL,2734.61507,S,15305.58593,E,222507.000,A,A*43 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222507.000,14.0,6.8,11.8,10.9,6.4,10.7,18.5*54 +$GPGSV,3,1,11,16,49,115,43,25,39,268,34,23,58,175,37,20,71,336,34*7B +$GPGSV,3,2,11,19,02,028,21,04,06,241,,13,30,223,29,27,19,284,34*73 +$GPGSV,3,3,11,11,06,337,,01,,,17,03,14,055,29*72 +$GPRMC,222507.000,A,2734.61507,S,15305.58593,E,35.4,143.9,030308,11.2,W,A*0A +$GPVTG,143.9,T,155.1,M,35.4,N,65.5,K,A*28 +$GPGGA,222508.000,2734.62275,S,15305.59221,E,1,06,1.7,61.8,M,39.2,M,,*70 +$GPGLL,2734.62275,S,15305.59221,E,222508.000,A,A*42 +$GPGSA,A,3,16,25,23,20,13,27,,,,,,,3.3,1.7,2.8*3A +$GPGST,222508.000,12.5,9.3,16.3,19.5,9.4,14.4,27.5*51 +$GPGSV,3,1,11,16,49,115,43,25,39,268,33,23,58,175,38,20,71,336,31*76 +$GPGSV,3,2,11,19,02,028,21,04,06,241,,13,30,223,30,27,19,284,35*7A +$GPGSV,3,3,11,11,06,337,,01,,,17,03,14,055,28*73 +$GPRMC,222508.000,A,2734.62275,S,15305.59221,E,33.9,143.9,030308,11.2,W,A*00 +$GPVTG,143.9,T,155.1,M,33.9,N,62.9,K,A*28 +$GPGGA,222509.000,2734.63006,S,15305.59817,E,1,06,1.7,62.7,M,39.2,M,,*75 +$GPGLL,2734.63006,S,15305.59817,E,222509.000,A,A*4B +$GPGSA,A,3,16,25,23,20,13,27,,,,,,,3.3,1.7,2.8*3A +$GPGST,222509.000,10.4,8.1,14.2,21.9,8.4,12.3,23.7*52 +$GPGSV,3,1,11,16,49,115,44,25,39,268,32,23,58,175,37,20,71,336,29*76 +$GPGSV,3,2,11,19,02,028,21,04,06,241,,13,30,223,28,27,19,284,35*73 +$GPGSV,3,3,11,11,06,337,,01,,,17,03,14,055,30*7A +$GPRMC,222509.000,A,2734.63006,S,15305.59817,E,32.2,143.9,030308,11.2,W,A*03 +$GPVTG,143.9,T,155.1,M,32.2,N,59.7,K,A*24 +$GPGGA,222510.000,2734.63706,S,15305.60376,E,1,06,1.7,63.5,M,39.2,M,,*7F +$GPGLL,2734.63706,S,15305.60376,E,222510.000,A,A*42 +$GPGSA,A,3,16,25,23,20,13,27,,,,,,,3.3,1.7,2.8*3A +$GPGST,222510.000,12.4,8.3,12.9,28.0,8.7,11.0,21.6*57 +$GPGSV,3,1,11,16,48,115,43,25,39,268,32,23,58,175,37,20,71,336,29*70 +$GPGSV,3,2,11,19,02,028,20,04,06,241,,13,30,223,31,27,19,284,35*7A +$GPGSV,3,3,11,11,06,337,,01,,,17,03,14,055,25*7E +$GPRMC,222510.000,A,2734.63706,S,15305.60376,E,30.6,144.3,030308,11.2,W,A*01 +$GPVTG,144.3,T,155.5,M,30.6,N,56.7,K,A*24 +$GPGGA,222511.000,2734.64376,S,15305.60904,E,1,07,1.2,64.4,M,39.2,M,,*77 +$GPGLL,2734.64376,S,15305.60904,E,222511.000,A,A*48 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222511.000,14.6,5.7,10.5,2.7,5.2,9.6,15.0*5C +$GPGSV,3,1,11,16,48,115,44,25,39,268,32,23,58,175,37,20,71,336,28*76 +$GPGSV,3,2,11,19,02,028,20,04,06,241,23,13,30,223,29,27,19,284,36*71 +$GPGSV,3,3,11,11,06,337,,01,,,17,03,14,055,29*72 +$GPRMC,222511.000,A,2734.64376,S,15305.60904,E,29.1,144.2,030308,11.2,W,A*05 +$GPVTG,144.2,T,155.4,M,29.1,N,53.9,K,A*20 +$GPGGA,222512.000,2734.64992,S,15305.61405,E,1,07,1.2,65.4,M,39.2,M,,*78 +$GPGLL,2734.64992,S,15305.61405,E,222512.000,A,A*46 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222512.000,13.4,17.0,5.7,85.6,5.3,15.5,23.6*5A +$GPGSV,3,1,11,16,48,115,45,25,39,268,33,23,58,175,38,20,71,336,28*79 +$GPGSV,3,2,11,19,02,028,20,04,06,241,23,13,30,223,26,27,19,284,33*7B +$GPGSV,3,3,11,11,06,337,24,01,,,17,03,14,055,27*7A +$GPRMC,222512.000,A,2734.64992,S,15305.61405,E,27.2,143.6,030308,11.2,W,A*05 +$GPVTG,143.6,T,154.8,M,27.2,N,50.5,K,A*2C +$GPGGA,222513.000,2734.65572,S,15305.61884,E,1,07,1.2,66.2,M,39.2,M,,*7A +$GPGLL,2734.65572,S,15305.61884,E,222513.000,A,A*41 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222513.000,12.3,7.0,14.8,2.4,6.5,13.5,21.4*6D +$GPGSV,3,1,10,16,48,115,44,25,39,268,35,23,58,175,38,20,71,336,28*7F +$GPGSV,3,2,10,19,02,028,20,04,06,241,23,13,30,223,23,27,19,284,33*7F +$GPGSV,3,3,10,11,06,337,24,03,14,055,28*73 +$GPRMC,222513.000,A,2734.65572,S,15305.61884,E,25.8,143.6,030308,11.2,W,A*0A +$GPVTG,143.6,T,154.8,M,25.8,N,47.9,K,A*2E +$GPGGA,222514.000,2734.66155,S,15305.62364,E,1,06,1.5,67.0,M,39.2,M,,*7C +$GPGLL,2734.66155,S,15305.62364,E,222514.000,A,A*42 +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222514.000,25.5,6.1,14.4,3.1,5.6,13.2,19.2*6A +$GPGSV,3,1,10,16,48,115,44,25,39,268,33,23,58,175,38,20,71,336,26*77 +$GPGSV,3,2,10,19,02,028,18,04,06,241,23,13,30,223,23,27,19,284,34*73 +$GPGSV,3,3,10,11,06,337,28,03,14,055,29*7E +$GPRMC,222514.000,A,2734.66155,S,15305.62364,E,25.9,143.6,030308,11.2,W,A*08 +$GPVTG,143.6,T,154.8,M,25.9,N,48.0,K,A*29 +$GPGGA,222515.000,2734.66761,S,15305.62860,E,1,06,1.5,67.5,M,39.2,M,,*76 +$GPGLL,2734.66761,S,15305.62860,E,222515.000,A,A*4D +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222515.000,20.1,5.5,13.0,3.0,5.1,11.8,17.4*68 +$GPGSV,3,1,10,16,48,115,44,25,39,268,32,23,58,175,38,20,71,336,26*76 +$GPGSV,3,2,10,19,02,028,18,04,06,241,23,13,30,223,24,27,19,284,34*74 +$GPGSV,3,3,10,11,06,337,28,03,14,055,24*73 +$GPRMC,222515.000,A,2734.66761,S,15305.62860,E,26.9,143.7,030308,11.2,W,A*05 +$GPVTG,143.7,T,154.9,M,26.9,N,49.8,K,A*23 +$GPGGA,222516.000,2734.67384,S,15305.63376,E,1,06,1.5,68.2,M,39.2,M,,*7E +$GPGLL,2734.67384,S,15305.63376,E,222516.000,A,A*4D +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222516.000,16.4,5.8,11.5,4.5,5.3,10.5,17.3*6A +$GPGSV,3,1,10,16,48,115,44,25,39,268,32,23,58,175,38,20,71,336,30*71 +$GPGSV,3,2,10,19,02,028,18,04,06,241,23,13,30,223,24,27,19,284,33*73 +$GPGSV,3,3,10,11,06,337,28,03,14,055,28*7F +$GPRMC,222516.000,A,2734.67384,S,15305.63376,E,27.7,143.8,030308,11.2,W,A*05 +$GPVTG,143.8,T,155.0,M,27.7,N,51.4,K,A*2E +$GPGGA,222517.000,2734.68035,S,15305.63901,E,1,06,1.5,68.8,M,39.2,M,,*79 +$GPGLL,2734.68035,S,15305.63901,E,222517.000,A,A*40 +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222517.000,19.1,6.2,12.1,7.2,5.8,11.0,15.3*66 +$GPGSV,3,1,10,16,48,115,44,25,39,268,34,23,58,175,38,20,71,336,32*75 +$GPGSV,3,2,10,19,02,028,20,04,06,241,23,13,30,223,24,27,19,284,34*7F +$GPGSV,3,3,10,11,06,337,24,03,14,055,29*72 +$GPRMC,222517.000,A,2734.68035,S,15305.63901,E,28.6,144.1,030308,11.2,W,A*08 +$GPVTG,144.1,T,155.3,M,28.6,N,53.0,K,A*2B +$GPGGA,222518.000,2734.68718,S,15305.64446,E,1,07,1.4,69.1,M,39.2,M,,*7F +$GPGLL,2734.68718,S,15305.64446,E,222518.000,A,A*4E +$GPGSA,A,3,16,25,23,20,27,11,03,,,,,,2.4,1.4,1.9*3C +$GPGST,222518.000,13.8,5.5,10.6,5.8,5.1,9.6,13.4*54 +$GPGSV,3,1,10,16,48,115,43,25,39,268,33,23,58,175,37,20,71,336,35*7D +$GPGSV,3,2,10,19,02,028,20,04,06,241,18,13,30,223,24,27,19,284,33*70 +$GPGSV,3,3,10,11,06,337,22,03,14,055,33*7F +$GPRMC,222518.000,A,2734.68718,S,15305.64446,E,29.9,144.4,030308,11.2,W,A*0D +$GPVTG,144.4,T,155.6,M,29.9,N,55.3,K,A*20 +$GPGGA,222519.000,2734.69424,S,15305.65010,E,1,07,1.4,69.5,M,39.2,M,,*71 +$GPGLL,2734.69424,S,15305.65010,E,222519.000,A,A*44 +$GPGSA,A,3,16,25,23,20,27,11,03,,,,,,2.4,1.4,1.9*3C +$GPGST,222519.000,14.0,13.4,5.0,89.4,4.6,12.3,18.8*58 +$GPGSV,3,1,10,16,48,115,44,25,39,268,34,23,58,175,37,20,71,336,37*7F +$GPGSV,3,2,10,19,02,028,20,04,06,241,18,13,30,223,23,27,19,284,31*75 +$GPGSV,3,3,10,11,06,337,26,03,14,055,35*7D +$GPRMC,222519.000,A,2734.69424,S,15305.65010,E,31.0,144.4,030308,11.2,W,A*07 +$GPVTG,144.4,T,155.6,M,31.0,N,57.5,K,A*24 +$GPGGA,222520.000,2734.70163,S,15305.65604,E,1,07,1.4,69.6,M,39.2,M,,*75 +$GPGLL,2734.70163,S,15305.65604,E,222520.000,A,A*43 +$GPGSA,A,3,16,25,23,20,27,11,03,,,,,,2.4,1.4,1.9*3C +$GPGST,222520.000,9.8,11.6,4.7,89.0,4.3,10.6,15.9*6A +$GPGSV,3,1,10,16,48,115,44,25,39,268,35,23,58,175,39,20,71,336,36*71 +$GPGSV,3,2,10,19,02,028,20,04,06,241,19,13,30,223,23,27,19,284,31*74 +$GPGSV,3,3,10,11,06,337,31,03,14,055,37*79 +$GPRMC,222520.000,A,2734.70163,S,15305.65604,E,32.5,144.1,030308,11.2,W,A*03 +$GPVTG,144.1,T,155.3,M,32.5,N,60.2,K,A*21 +$GPGGA,222521.000,2734.70923,S,15305.66218,E,1,07,1.4,69.5,M,39.2,M,,*71 +$GPGLL,2734.70923,S,15305.66218,E,222521.000,A,A*44 +$GPGSA,A,3,16,25,23,20,27,11,03,,,,,,2.4,1.4,1.9*3C +$GPGST,222521.000,11.1,6.2,10.8,9.6,5.8,9.8,16.8*53 +$GPGSV,3,1,10,16,48,115,44,25,39,268,36,23,58,175,40,20,71,336,38*72 +$GPGSV,3,2,10,19,02,028,20,04,06,241,19,13,30,223,23,27,19,284,26*72 +$GPGSV,3,3,10,11,06,338,31,03,14,055,37*76 +$GPRMC,222521.000,A,2734.70923,S,15305.66218,E,33.5,144.0,030308,11.2,W,A*04 +$GPVTG,144.0,T,155.3,M,33.5,N,62.0,K,A*21 +$GPGGA,222522.000,2734.71700,S,15305.66845,E,1,06,1.5,69.1,M,39.2,M,,*7A +$GPGLL,2734.71700,S,15305.66845,E,222522.000,A,A*4B +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222522.000,15.0,5.7,9.9,5.5,5.2,9.0,15.4*68 +$GPGSV,3,1,10,16,48,115,43,25,39,268,36,23,58,175,40,20,71,336,37*7A +$GPGSV,3,2,10,19,02,028,20,04,06,241,24,13,30,223,23,27,19,284,26*7C +$GPGSV,3,3,10,11,06,338,25,03,14,055,37*73 +$GPRMC,222522.000,A,2734.71700,S,15305.66845,E,34.2,144.1,030308,11.2,W,A*0A +$GPVTG,144.1,T,155.3,M,34.2,N,63.4,K,A*25 +$GPGGA,222523.000,2734.72487,S,15305.67483,E,1,07,1.4,68.6,M,39.2,M,,*75 +$GPGLL,2734.72487,S,15305.67483,E,222523.000,A,A*42 +$GPGSA,A,3,16,25,23,20,27,11,03,,,,,,2.4,1.4,1.9*3C +$GPGST,222523.000,13.8,5.2,10.1,3.9,4.8,9.2,13.8*5B +$GPGSV,3,1,10,16,48,115,44,25,39,268,37,23,58,175,41,20,71,336,35*7F +$GPGSV,3,2,10,19,02,028,20,04,06,241,24,13,30,223,23,27,19,284,26*7C +$GPGSV,3,3,10,11,06,338,23,03,14,055,31*73 +$GPRMC,222523.000,A,2734.72487,S,15305.67483,E,34.7,144.2,030308,11.2,W,A*05 +$GPVTG,144.2,T,155.4,M,34.7,N,64.3,K,A*24 +$GPGGA,222524.000,2734.73280,S,15305.68126,E,1,07,1.4,68.1,M,39.2,M,,*70 +$GPGLL,2734.73280,S,15305.68126,E,222524.000,A,A*40 +$GPGSA,A,3,16,25,23,20,27,11,03,,,,,,2.4,1.4,1.9*3C +$GPGST,222524.000,10.5,4.9,9.4,3.5,4.5,8.6,12.8*60 +$GPGSV,3,1,10,16,48,115,44,25,39,268,37,23,58,175,41,20,71,336,35*7F +$GPGSV,3,2,10,19,02,028,20,04,06,241,22,13,30,223,23,27,19,284,26*7A +$GPGSV,3,3,10,11,06,338,25,03,14,055,29*7C +$GPRMC,222524.000,A,2734.73280,S,15305.68126,E,35.0,144.2,030308,11.2,W,A*01 +$GPVTG,144.2,T,155.4,M,35.0,N,64.9,K,A*28 +$GPGGA,222525.000,2734.74083,S,15305.68778,E,1,07,1.4,67.7,M,39.2,M,,*73 +$GPGLL,2734.74083,S,15305.68778,E,222525.000,A,A*4A +$GPGSA,A,3,16,25,23,20,27,11,03,,,,,,2.4,1.4,1.9*3C +$GPGST,222525.000,10.3,5.2,13.4,3.6,4.8,12.3,20.4*6B +$GPGSV,3,1,10,16,48,115,43,25,39,268,38,23,58,175,40,20,71,336,36*75 +$GPGSV,3,2,10,19,02,028,20,04,06,241,22,13,30,223,23,27,19,284,23*7F +$GPGSV,3,3,10,11,06,338,27,03,14,055,36*70 +$GPRMC,222525.000,A,2734.74083,S,15305.68778,E,35.6,144.2,030308,11.2,W,A*0D +$GPVTG,144.2,T,155.4,M,35.6,N,65.9,K,A*2F +$GPGGA,222526.000,2734.74894,S,15305.69428,E,1,06,1.5,67.2,M,39.2,M,,*7C +$GPGLL,2734.74894,S,15305.69428,E,222526.000,A,A*40 +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222526.000,8.0,5.7,12.0,2.6,5.2,10.9,17.4*54 +$GPGSV,3,1,10,16,48,115,43,25,39,268,37,23,58,175,40,20,71,336,35*79 +$GPGSV,3,2,10,19,02,028,20,04,06,241,22,13,30,223,23,27,19,284,24*78 +$GPGSV,3,3,10,11,06,338,27,03,14,055,39*7F +$GPRMC,222526.000,A,2734.74894,S,15305.69428,E,35.8,144.5,030308,11.2,W,A*0E +$GPVTG,144.5,T,155.7,M,35.8,N,66.3,K,A*2C +$GPGGA,222527.000,2734.75707,S,15305.70075,E,1,05,1.7,66.8,M,39.2,M,,*77 +$GPGLL,2734.75707,S,15305.70075,E,222527.000,A,A*41 +$GPGSA,A,3,16,25,23,20,03,,,,,,,,2.9,1.7,2.4*39 +$GPGST,222527.000,13.9,6.3,10.9,6.3,5.9,10.0,18.2*60 +$GPGSV,3,1,10,16,48,115,44,25,39,268,38,23,58,175,40,20,71,336,33*77 +$GPGSV,3,2,10,19,02,028,,04,06,241,25,13,30,223,23,27,19,284,26*7F +$GPGSV,3,3,10,11,06,338,,03,14,055,36*75 +$GPRMC,222527.000,A,2734.75707,S,15305.70075,E,35.8,144.6,030308,11.2,W,A*0C +$GPVTG,144.6,T,155.8,M,35.8,N,66.2,K,A*21 +$GPGGA,222528.000,2734.76518,S,15305.70724,E,1,06,1.5,66.1,M,39.2,M,,*7C +$GPGLL,2734.76518,S,15305.70724,E,222528.000,A,A*42 +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222528.000,11.3,5.6,10.1,4.5,5.2,9.2,16.1*51 +$GPGSV,3,1,10,16,48,115,43,25,39,268,39,23,58,175,39,20,71,336,28*75 +$GPGSV,3,2,10,19,02,028,,04,06,241,25,13,30,223,23,27,19,284,26*7F +$GPGSV,3,3,10,11,06,338,,03,14,055,36*75 +$GPRMC,222528.000,A,2734.76518,S,15305.70724,E,35.7,144.4,030308,11.2,W,A*02 +$GPVTG,144.4,T,155.6,M,35.7,N,66.2,K,A*22 +$GPGGA,222529.000,2734.77313,S,15305.71385,E,1,06,1.5,66.1,M,39.2,M,,*7F +$GPGLL,2734.77313,S,15305.71385,E,222529.000,A,A*41 +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222529.000,10.9,5.6,12.5,5.4,5.2,11.4,20.9*6F +$GPGSV,3,1,10,16,48,115,41,25,39,268,40,23,58,175,36,20,71,336,28*76 +$GPGSV,3,2,10,19,02,028,,04,06,241,25,13,30,223,23,27,19,284,24*7D +$GPGSV,3,3,10,11,06,338,27,03,14,055,30*76 +$GPRMC,222529.000,A,2734.77313,S,15305.71385,E,35.6,143.5,030308,11.2,W,A*06 +$GPVTG,143.5,T,154.7,M,35.6,N,65.8,K,A*2C +$GPGGA,222530.000,2734.78106,S,15305.72042,E,1,06,1.5,66.0,M,39.2,M,,*74 +$GPGLL,2734.78106,S,15305.72042,E,222530.000,A,A*4B +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222530.000,9.4,5.4,18.9,2.4,4.9,17.3,30.6*54 +$GPGSV,3,1,10,16,48,115,40,25,39,268,40,23,58,175,36,20,71,336,28*77 +$GPGSV,3,2,10,19,02,028,,04,06,241,25,13,30,223,23,27,19,284,26*7F +$GPGSV,3,3,10,11,06,338,27,03,14,055,29*7E +$GPRMC,222530.000,A,2734.78106,S,15305.72042,E,35.5,143.7,030308,11.2,W,A*0D +$GPVTG,143.7,T,155.0,M,35.5,N,65.8,K,A*2B +$GPGGA,222531.000,2734.78918,S,15305.72691,E,1,05,4.4,66.0,M,39.2,M,,*7D +$GPGLL,2734.78918,S,15305.72691,E,222531.000,A,A*45 +$GPGSA,A,3,16,25,23,27,11,,,,,,,,9.3,4.4,8.2*36 +$GPGST,222531.000,9.1,8.5,53.9,3.2,8.2,49.2,81.3*56 +$GPGSV,3,1,10,16,48,115,40,25,39,268,39,23,58,175,37,20,71,336,28*78 +$GPGSV,3,2,10,19,02,028,,04,06,241,25,13,31,223,23,27,19,284,25*7D +$GPGSV,3,3,10,11,06,338,24,03,14,055,22*76 +$GPRMC,222531.000,A,2734.78918,S,15305.72691,E,35.9,144.5,030308,11.2,W,A*0A +$GPVTG,144.5,T,155.7,M,35.9,N,66.4,K,A*2A +$GPGGA,222532.000,2734.79737,S,15305.73347,E,1,06,1.5,66.0,M,39.2,M,,*74 +$GPGLL,2734.79737,S,15305.73347,E,222532.000,A,A*4B +$GPGSA,A,3,16,25,23,20,27,03,,,,,,,2.5,1.5,2.0*36 +$GPGST,222532.000,11.0,6.8,38.7,1.2,6.3,35.3,58.4*69 +$GPGSV,3,1,10,16,48,115,40,25,39,268,39,23,58,175,36,20,71,336,28*79 +$GPGSV,3,2,10,19,02,028,,04,06,241,25,13,31,223,24,27,19,284,23*7C +$GPGSV,3,3,10,11,06,338,24,03,14,055,30*75 +$GPRMC,222532.000,A,2734.79737,S,15305.73347,E,36.1,144.5,030308,11.2,W,A*0F +$GPVTG,144.5,T,155.7,M,36.1,N,66.9,K,A*2C +$GPGGA,222533.000,2734.80571,S,15305.74004,E,1,06,4.1,66.1,M,39.2,M,,*70 +$GPGLL,2734.80571,S,15305.74004,E,222533.000,A,A*4F +$GPGSA,A,3,16,25,23,27,11,03,,,,,,,8.3,4.1,7.3*3F +$GPGST,222533.000,9.4,6.1,45.5,0.4,5.6,41.6,69.5*50 +$GPGSV,3,1,10,16,48,115,40,25,39,268,39,23,58,175,37,20,71,336,28*78 +$GPGSV,3,2,10,19,02,028,,04,06,241,23,13,31,223,24,27,19,284,25*7C +$GPGSV,3,3,10,11,06,338,22,03,14,055,25*77 +$GPRMC,222533.000,A,2734.80571,S,15305.74004,E,36.6,145.0,030308,11.2,W,A*08 +$GPVTG,145.0,T,156.2,M,36.6,N,67.7,K,A*26 +$GPGGA,222534.000,2734.81441,S,15305.74656,E,1,06,1.8,65.2,M,39.2,M,,*79 +$GPGLL,2734.81441,S,15305.74656,E,222534.000,A,A*4A +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.5,1.8,3.1*3A +$GPGST,222534.000,20.2,5.8,45.7,1.7,5.4,41.7,71.3*6C +$GPGSV,3,1,10,16,48,115,40,25,39,268,38,23,58,175,37,20,71,336,28*79 +$GPGSV,3,2,10,19,02,028,,04,06,241,23,13,31,223,25,27,19,284,25*7D +$GPGSV,3,3,10,11,06,338,22,03,14,055,31*72 +$GPRMC,222534.000,A,2734.81441,S,15305.74656,E,37.6,146.2,030308,11.2,W,A*0D +$GPVTG,146.2,T,157.5,M,37.6,N,69.7,K,A*2E +$GPGGA,222535.000,2734.82349,S,15305.75307,E,1,05,1.9,63.5,M,39.2,M,,*77 +$GPGLL,2734.82349,S,15305.75307,E,222535.000,A,A*47 +$GPGSA,A,3,16,25,23,13,03,,,,,,,,3.6,1.9,3.1*3D +$GPGST,222535.000,27.6,7.0,40.6,3.1,6.7,37.1,68.4*6C +$GPGSV,3,1,10,16,48,115,40,25,39,268,36,23,58,175,37,20,71,336,28*77 +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,31,223,25,27,19,284,23*78 +$GPGSV,3,3,10,11,06,338,22,03,14,055,26*74 +$GPRMC,222535.000,A,2734.82349,S,15305.75307,E,38.7,147.3,030308,11.2,W,A*0E +$GPVTG,147.3,T,158.6,M,38.7,N,71.7,K,A*25 +$GPGGA,222536.000,2734.83215,S,15305.75969,E,1,05,1.7,63.5,M,39.2,M,,*71 +$GPGLL,2734.83215,S,15305.75969,E,222536.000,A,A*4F +$GPGSA,A,3,16,25,23,20,03,,,,,,,,2.9,1.7,2.4*39 +$GPGST,222536.000,7.9,6.3,97.2,3.9,8.3,88.7,161.0*62 +$GPGSV,3,1,10,16,48,115,40,25,39,268,37,23,58,175,36,20,71,336,28*77 +$GPGSV,3,2,10,19,02,028,,04,06,241,20,13,31,223,25,27,19,284,23*78 +$GPGSV,3,3,10,11,06,338,22,03,14,055,26*74 +$GPRMC,222536.000,A,2734.83215,S,15305.75969,E,37.6,145.6,030308,11.2,W,A*0F +$GPVTG,145.6,T,156.8,M,37.6,N,69.7,K,A*25 +$GPGGA,222537.000,2734.84076,S,15305.76655,E,1,05,1.7,63.5,M,39.2,M,,*73 +$GPGLL,2734.84076,S,15305.76655,E,222537.000,A,A*4D +$GPGSA,A,3,16,25,23,20,03,,,,,,,,2.9,1.7,2.4*39 +$GPGST,222537.000,16.0,7.8,110.8,2.1,8.0,101.2,209.1*57 +$GPGSV,3,1,10,16,48,115,39,25,39,268,36,23,58,175,34,20,71,336,28*7A +$GPGSV,3,2,10,19,02,028,,04,06,241,22,13,31,223,25,27,19,284,23*7A +$GPGSV,3,3,10,11,06,338,22,03,14,055,25*77 +$GPRMC,222537.000,A,2734.84076,S,15305.76655,E,37.9,144.6,030308,11.2,W,A*03 +$GPVTG,144.6,T,155.8,M,37.9,N,70.3,K,A*24 +$GPGGA,222538.000,2734.84945,S,15305.77356,E,1,05,1.9,63.5,M,39.2,M,,*7C +$GPGLL,2734.84945,S,15305.77356,E,222538.000,A,A*4C +$GPGSA,A,3,16,25,23,13,03,,,,,,,,3.6,1.9,3.1*3D +$GPGST,222538.000,13.1,8.0,61.7,1.6,7.5,56.4,113.0*51 +$GPGSV,3,1,10,16,48,115,40,25,39,268,37,23,58,175,32,20,71,336,*79 +$GPGSV,3,2,10,19,02,028,,04,06,241,22,13,31,223,25,27,19,284,23*7A +$GPGSV,3,3,10,11,06,338,22,03,14,055,28*7A +$GPRMC,222538.000,A,2734.84945,S,15305.77356,E,38.4,144.2,030308,11.2,W,A*04 +$GPVTG,144.2,T,155.4,M,38.4,N,71.1,K,A*2D +$GPGGA,222539.000,2734.85792,S,15305.78022,E,1,05,1.9,63.5,M,39.2,M,,*77 +$GPGLL,2734.85792,S,15305.78022,E,222539.000,A,A*47 +$GPGSA,A,3,16,25,23,13,03,,,,,,,,3.6,1.9,3.1*3D +$GPGST,222539.000,6.4,7.5,72.7,1.5,7.1,66.5,137.2*68 +$GPGSV,3,1,10,16,48,115,40,25,39,268,38,23,58,175,32,20,71,336,*76 +$GPGSV,3,2,10,19,02,028,,04,06,241,22,13,31,223,25,27,19,284,23*7A +$GPGSV,3,3,10,11,06,338,22,03,14,055,28*7A +$GPRMC,222539.000,A,2734.85792,S,15305.78022,E,37.1,144.9,030308,11.2,W,A*0E +$GPVTG,144.9,T,156.1,M,37.1,N,68.7,K,A*24 +$GPGGA,222540.000,2734.86604,S,15305.78646,E,1,05,4.4,63.5,M,39.2,M,,*78 +$GPGLL,2734.86604,S,15305.78646,E,222540.000,A,A*40 +$GPGSA,A,3,16,25,23,27,11,,,,,,,,9.3,4.4,8.2*36 +$GPGST,222540.000,13.6,8.3,67.7,1.8,7.8,61.9,111.1*55 +$GPGSV,3,1,10,16,48,115,40,25,39,268,38,23,58,175,32,20,71,336,*76 +$GPGSV,3,2,10,19,02,028,,04,06,241,26,13,31,223,22,27,19,284,22*78 +$GPGSV,3,3,10,11,06,338,20,03,14,055,28*78 +$GPRMC,222540.000,A,2734.86604,S,15305.78646,E,35.3,145.6,030308,11.2,W,A*07 +$GPVTG,145.6,T,156.8,M,35.3,N,65.4,K,A*2D +$GPGGA,222541.000,2734.87421,S,15305.79222,E,1,06,1.8,63.5,M,39.2,M,,*70 +$GPGLL,2734.87421,S,15305.79222,E,222541.000,A,A*42 +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.5,1.8,3.1*3A +$GPGST,222541.000,11.7,33.3,8.1,87.4,7.5,30.4,54.6*51 +$GPGSV,3,1,10,16,48,115,39,25,39,268,33,23,58,175,36,20,71,336,*77 +$GPGSV,3,2,10,19,02,028,,04,06,241,26,13,31,223,22,27,19,284,22*78 +$GPGSV,3,3,10,11,06,338,20,03,14,055,26*76 +$GPRMC,222541.000,A,2734.87421,S,15305.79222,E,34.6,147.7,030308,11.2,W,A*02 +$GPVTG,147.7,T,158.9,M,34.6,N,64.1,K,A*21 +$GPGGA,222542.000,2734.88135,S,15305.79765,E,1,06,1.8,63.5,M,39.2,M,,*7A +$GPGLL,2734.88135,S,15305.79765,E,222542.000,A,A*48 +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.5,1.8,3.1*3A +$GPGST,222542.000,23.5,7.9,53.0,0.2,7.2,48.5,78.4*6F +$GPGSV,3,1,10,16,48,115,40,25,39,268,36,23,58,175,35,20,71,336,*7F +$GPGSV,3,2,10,19,02,028,,04,06,241,27,13,31,223,23,27,19,284,25*7F +$GPGSV,3,3,10,11,06,338,20,03,14,055,26*76 +$GPRMC,222542.000,A,2734.88135,S,15305.79765,E,31.0,145.8,030308,11.2,W,A*06 +$GPVTG,145.8,T,157.0,M,31.0,N,57.4,K,A*2C +$GPGGA,222543.000,2734.88798,S,15305.80287,E,1,07,1.2,63.0,M,39.2,M,,*7B +$GPGLL,2734.88798,S,15305.80287,E,222543.000,A,A*47 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222543.000,18.8,6.7,28.4,0.5,6.1,26.0,41.8*62 +$GPGSV,3,1,10,16,48,115,40,25,39,268,39,23,58,175,35,20,71,336,21*73 +$GPGSV,3,2,10,19,02,028,,04,06,241,37,13,31,223,22,27,19,284,24*7E +$GPGSV,3,3,10,11,06,338,20,03,14,055,32*73 +$GPRMC,222543.000,A,2734.88798,S,15305.80287,E,29.0,144.8,030308,11.2,W,A*01 +$GPVTG,144.8,T,156.0,M,29.0,N,53.8,K,A*2D +$GPGGA,222544.000,2734.89328,S,15305.80767,E,1,06,1.8,62.9,M,39.2,M,,*7A +$GPGLL,2734.89328,S,15305.80767,E,222544.000,A,A*45 +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.5,1.8,3.1*3A +$GPGST,222544.000,14.9,35.2,5.8,89.5,5.4,32.2,52.6*52 +$GPGSV,3,1,10,16,48,115,43,25,39,268,40,23,58,175,25,20,71,336,21*7F +$GPGSV,3,2,10,19,02,028,,04,06,241,28,13,31,223,30,27,19,284,29*7E +$GPGSV,3,3,10,11,06,338,20,03,14,055,33*72 +$GPRMC,222544.000,A,2734.89328,S,15305.80767,E,24.5,140.9,030308,11.2,W,A*0E +$GPVTG,140.9,T,152.1,M,24.5,N,45.3,K,A*29 +$GPGGA,222545.000,2734.89633,S,15305.81084,E,1,05,1.9,62.9,M,39.2,M,,*7D +$GPGLL,2734.89633,S,15305.81084,E,222545.000,A,A*40 +$GPGSA,A,3,16,25,23,13,03,,,,,,,,3.6,1.9,3.1*3D +$GPGST,222545.000,19.5,6.0,33.0,1.0,5.5,30.2,52.4*69 +$GPGSV,3,1,10,16,48,115,41,25,39,268,34,23,58,175,29,20,71,336,30*72 +$GPGSV,3,2,10,19,02,028,,04,06,241,25,13,31,223,29,27,19,284,30*73 +$GPGSV,3,3,10,11,06,338,20,03,14,055,30*71 +$GPRMC,222545.000,A,2734.89633,S,15305.81084,E,14.6,136.6,030308,11.2,W,A*05 +$GPVTG,136.6,T,147.8,M,14.6,N,27.0,K,A*2D +$GPGGA,222546.000,2734.89236,S,15305.81130,E,1,06,1.3,62.6,M,39.2,M,,*77 +$GPGLL,2734.89236,S,15305.81130,E,222546.000,A,A*4C +$GPGSA,A,3,16,25,23,20,13,03,,,,,,,2.3,1.3,1.9*3B +$GPGST,222546.000,11.5,5.6,15.6,0.9,5.2,14.3,26.4*6E +$GPGSV,3,1,10,16,48,115,35,25,39,268,40,23,58,175,41,20,71,336,38*74 +$GPGSV,3,2,10,19,02,028,,04,06,241,26,13,31,223,33,27,19,284,26*7C +$GPGSV,3,3,10,11,06,338,20,03,14,055,28*78 +$GPRMC,222546.000,A,2734.89236,S,15305.81130,E,16.9,2.1,030308,11.2,W,A*05 +$GPVTG,2.1,T,13.3,M,16.9,N,31.2,K,A*1F +$GPGGA,222547.000,2734.89429,S,15305.81239,E,1,05,2.3,62.5,M,39.2,M,,*77 +$GPGLL,2734.89429,S,15305.81239,E,222547.000,A,A*4F +$GPGSA,A,3,16,23,13,27,03,,,,,,,,4.6,2.3,3.9*39 +$GPGST,222547.000,11.4,38.3,9.7,69.8,14.7,33.0,49.2*6B +$GPGSV,3,1,10,16,48,115,32,25,39,268,37,23,58,175,42,20,71,336,37*7F +$GPGSV,3,2,10,19,02,028,,04,06,241,26,13,31,223,36,27,19,284,29*76 +$GPGSV,3,3,10,11,06,338,20,03,14,055,34*75 +$GPRMC,222547.000,A,2734.89429,S,15305.81239,E,8.4,154.5,030308,11.2,W,A*32 +$GPVTG,154.5,T,165.7,M,8.4,N,15.6,K,A*1D +$GPGGA,222548.000,2734.89474,S,15305.81253,E,1,07,1.2,62.0,M,39.2,M,,*79 +$GPGLL,2734.89474,S,15305.81253,E,222548.000,A,A*44 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222548.000,11.0,12.3,7.9,86.0,7.3,11.3,20.6*5F +$GPGSV,3,1,10,16,48,115,34,25,39,268,36,23,58,175,42,20,71,336,38*77 +$GPGSV,3,2,10,19,02,028,,04,06,241,25,13,31,223,36,27,19,284,29*75 +$GPGSV,3,3,10,11,06,338,20,03,14,055,33*72 +$GPRMC,222548.000,A,2734.89474,S,15305.81253,E,0.3,353.0,030308,11.2,W,A*36 +$GPVTG,353.0,T,4.3,M,0.3,N,0.5,K,A*27 +$GPGGA,222549.000,2734.89490,S,15305.81265,E,1,04,2.0,61.8,M,39.2,M,,*7E +$GPGLL,2734.89490,S,15305.81265,E,222549.000,A,A*4A +$GPGSA,A,3,25,23,13,03,,,,,,,,,3.9,2.0,3.3*3D +$GPGST,222549.000,17.0,16.1,8.8,81.3,8.3,14.6,29.3*57 +$GPGSV,3,1,11,16,48,115,33,25,39,268,37,23,58,175,42,20,71,336,36*7E +$GPGSV,3,2,11,19,02,028,,04,06,241,25,13,31,223,35,27,19,284,30*7F +$GPGSV,3,3,11,11,06,338,20,01,,,21,03,14,055,33*71 +$GPRMC,222549.000,A,2734.89490,S,15305.81265,E,0.2,330.7,030308,11.2,W,A*3B +$GPVTG,330.7,T,341.9,M,0.2,N,0.3,K,A*2A +$GPGGA,222550.000,2734.89526,S,15305.81278,E,1,04,2.6,61.7,M,39.2,M,,*7F +$GPGLL,2734.89526,S,15305.81278,E,222550.000,A,A*42 +$GPGSA,A,3,16,25,23,13,,,,,,,,,6.1,2.6,5.6*31 +$GPGST,222550.000,13.4,12.4,27.3,43.1,18.9,19.8,52.9*5A +$GPGSV,3,1,11,16,48,115,30,25,39,268,38,23,58,175,42,20,71,336,37*73 +$GPGSV,3,2,11,19,02,028,,04,06,241,25,13,31,223,36,27,19,284,31*7D +$GPGSV,3,3,11,11,06,338,,01,,,21,03,14,055,32*72 +$GPRMC,222550.000,A,2734.89526,S,15305.81278,E,0.1,350.7,030308,11.2,W,A*36 +$GPVTG,350.7,T,1.9,M,0.1,N,0.2,K,A*29 +$GPGGA,222551.000,2734.89528,S,15305.81279,E,1,05,2.3,61.7,M,39.2,M,,*75 +$GPGLL,2734.89528,S,15305.81279,E,222551.000,A,A*4C +$GPGSA,A,3,16,23,13,27,03,,,,,,,,4.6,2.3,3.9*39 +$GPGST,222551.000,13.7,31.0,11.1,60.5,16.5,25.2,39.3*5F +$GPGSV,3,1,11,16,48,115,30,25,39,268,39,23,58,175,41,20,71,336,35*73 +$GPGSV,3,2,11,19,02,028,,04,06,241,23,13,31,223,36,27,19,284,31*7B +$GPGSV,3,3,11,11,06,338,,01,,,20,03,14,055,30*71 +$GPRMC,222551.000,A,2734.89528,S,15305.81279,E,0.1,345.0,030308,11.2,W,A*3B +$GPVTG,345.0,T,356.2,M,0.1,N,0.2,K,A*20 +$GPGGA,222552.000,2734.89550,S,15305.81288,E,1,05,2.1,61.7,M,39.2,M,,*75 +$GPGLL,2734.89550,S,15305.81288,E,222552.000,A,A*4E +$GPGSA,A,3,16,25,23,13,27,,,,,,,,4.7,2.1,4.2*32 +$GPGST,222552.000,11.7,11.5,20.8,20.0,11.8,18.2,39.1*55 +$GPGSV,3,1,11,16,48,115,31,25,39,268,40,23,58,175,42,20,71,336,35*7F +$GPGSV,3,2,11,19,02,028,,04,06,241,23,13,31,223,38,27,19,284,31*75 +$GPGSV,3,3,11,11,06,338,,01,,,20,03,14,055,30*71 +$GPRMC,222552.000,A,2734.89550,S,15305.81288,E,0.1,11.2,030308,11.2,W,A*09 +$GPVTG,11.2,T,22.5,M,0.1,N,0.2,K,A*27 +$GPGGA,222553.000,2734.89573,S,15305.81294,E,1,05,2.1,61.5,M,39.2,M,,*7A +$GPGLL,2734.89573,S,15305.81294,E,222553.000,A,A*43 +$GPGSA,A,3,16,25,23,13,27,,,,,,,,4.7,2.1,4.2*32 +$GPGST,222553.000,9.8,10.3,17.6,19.1,10.4,15.6,33.7*6C +$GPGSV,3,1,10,16,48,115,29,25,39,268,38,23,58,175,41,20,71,336,37*79 +$GPGSV,3,2,10,19,02,028,,04,06,241,23,13,31,223,39,27,19,284,30*74 +$GPGSV,3,3,10,11,06,338,,03,14,055,29*7B +$GPRMC,222553.000,A,2734.89573,S,15305.81294,E,0.1,351.8,030308,11.2,W,A*39 +$GPVTG,351.8,T,3.0,M,0.1,N,0.2,K,A*2C +$GPGGA,222554.000,2734.89591,S,15305.81298,E,1,04,2.6,61.4,M,39.2,M,,*7A +$GPGLL,2734.89591,S,15305.81298,E,222554.000,A,A*44 +$GPGSA,A,3,16,25,23,13,,,,,,,,,6.1,2.6,5.6*31 +$GPGST,222554.000,16.9,10.4,17.8,12.8,10.0,16.1,37.3*5F +$GPGSV,3,1,10,16,48,115,32,25,39,268,38,23,58,175,41,20,71,336,36*72 +$GPGSV,3,2,10,19,02,028,,04,06,241,23,13,31,223,38,27,19,284,31*74 +$GPGSV,3,3,10,11,06,338,,03,14,055,26*74 +$GPRMC,222554.000,A,2734.89591,S,15305.81298,E,0.1,334.9,030308,11.2,W,A*3C +$GPVTG,334.9,T,346.1,M,0.1,N,0.2,K,A*2D +$GPGGA,222555.000,2734.89607,S,15305.81301,E,1,04,2.6,61.2,M,39.2,M,,*70 +$GPGLL,2734.89607,S,15305.81301,E,222555.000,A,A*48 +$GPGSA,A,3,16,25,23,13,,,,,,,,,6.1,2.6,5.6*31 +$GPGST,222555.000,14.4,11.9,23.3,40.8,16.2,17.6,44.5*56 +$GPGSV,3,1,11,16,48,115,32,25,39,268,37,23,58,175,40,20,71,336,38*73 +$GPGSV,3,2,11,19,02,028,,04,06,241,23,13,31,223,37,27,19,284,30*7B +$GPGSV,3,3,11,11,06,338,,01,,,20,03,14,055,25*75 +$GPRMC,222555.000,A,2734.89607,S,15305.81301,E,0.1,6.2,030308,11.2,W,A*39 +$GPVTG,6.2,T,17.4,M,0.1,N,0.2,K,A*16 +$GPGGA,222556.000,2734.89612,S,15305.81301,E,1,04,2.6,61.1,M,39.2,M,,*74 +$GPGLL,2734.89612,S,15305.81301,E,222556.000,A,A*4F +$GPGSA,A,3,16,25,23,13,,,,,,,,,6.1,2.6,5.6*31 +$GPGST,222556.000,12.4,15.8,24.1,4.1,14.5,22.0,39.9*69 +$GPGSV,3,1,11,16,48,115,33,25,39,268,38,23,58,174,41,20,71,336,38*7D +$GPGSV,3,2,11,19,02,028,,04,06,242,23,13,31,223,38,27,19,284,32*75 +$GPGSV,3,3,11,11,06,338,,01,,,20,03,14,055,28*78 +$GPRMC,222556.000,A,2734.89612,S,15305.81301,E,0.1,21.8,030308,11.2,W,A*01 +$GPVTG,21.8,T,33.0,M,0.1,N,0.2,K,A*2B +$GPGGA,222557.000,2734.89635,S,15305.81337,E,1,05,2.1,61.0,M,39.2,M,,*72 +$GPGLL,2734.89635,S,15305.81337,E,222557.000,A,A*4E +$GPGSA,A,3,16,25,23,13,27,,,,,,,,4.7,2.1,4.2*32 +$GPGST,222557.000,10.3,13.0,15.0,5.2,11.9,13.7,32.7*6B +$GPGSV,3,1,10,16,48,115,33,25,39,268,38,23,58,174,41,20,71,336,36*72 +$GPGSV,3,2,10,19,02,028,,04,06,242,23,13,31,223,39,27,19,284,28*7E +$GPGSV,3,3,10,11,06,338,,03,14,055,31*72 +$GPRMC,222557.000,A,2734.89635,S,15305.81337,E,1.2,96.1,030308,11.2,W,A*07 +$GPVTG,96.1,T,107.3,M,1.2,N,2.2,K,A*1B +$GPGGA,222558.000,2734.89638,S,15305.81517,E,1,06,1.8,60.6,M,39.2,M,,*7A +$GPGLL,2734.89638,S,15305.81517,E,222558.000,A,A*48 +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.5,1.8,3.1*3A +$GPGST,222558.000,18.6,12.9,7.5,72.4,7.5,11.4,21.9*57 +$GPGSV,3,1,10,16,48,115,39,25,39,268,38,23,58,174,37,20,71,336,26*78 +$GPGSV,3,2,10,19,02,028,,04,06,242,23,13,31,223,34,27,19,284,29*72 +$GPGSV,3,3,10,11,06,338,,03,14,055,30*73 +$GPRMC,222558.000,A,2734.89638,S,15305.81517,E,5.6,82.0,030308,11.2,W,A*05 +$GPVTG,82.0,T,93.2,M,5.6,N,10.3,K,A*10 +$GPGGA,222559.000,2734.89631,S,15305.81911,E,1,05,2.2,60.5,M,39.2,M,,*71 +$GPGLL,2734.89631,S,15305.81911,E,222559.000,A,A*4A +$GPGSA,A,3,16,25,23,20,27,,,,,,,,3.6,2.2,2.9*3A +$GPGST,222559.000,17.0,15.1,33.1,15.1,15.5,29.4,59.0*59 +$GPGSV,3,1,10,16,48,115,34,25,39,268,35,23,58,174,30,20,71,336,29*70 +$GPGSV,3,2,10,19,02,028,,04,06,242,23,13,31,223,35,27,19,284,28*72 +$GPGSV,3,3,10,11,06,338,,03,14,055,27*75 +$GPRMC,222559.000,A,2734.89631,S,15305.81911,E,12.5,87.6,030308,11.2,W,A*31 +$GPVTG,87.6,T,98.8,M,12.5,N,23.2,K,A*26 +$GPGGA,222600.000,2734.89436,S,15305.82359,E,1,06,1.8,61.2,M,39.2,M,,*72 +$GPGLL,2734.89436,S,15305.82359,E,222600.000,A,A*45 +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.5,1.8,3.1*3A +$GPGST,222600.000,14.4,14.9,6.9,79.1,6.7,13.4,28.4*57 +$GPGSV,3,1,10,16,48,115,37,25,39,268,36,23,58,174,29,20,71,336,33*73 +$GPGSV,3,2,10,19,02,028,,04,06,242,23,13,31,223,39,27,19,284,28*7E +$GPGSV,3,3,10,11,06,338,,03,14,055,28*7A +$GPRMC,222600.000,A,2734.89436,S,15305.82359,E,15.9,62.5,030308,11.2,W,A*3D +$GPVTG,62.5,T,73.7,M,15.9,N,29.5,K,A*22 +$GPGGA,222601.000,2734.89190,S,15305.82850,E,1,06,1.8,60.5,M,39.2,M,,*7E +$GPGLL,2734.89190,S,15305.82850,E,222601.000,A,A*4F +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.6,1.8,3.1*39 +$GPGST,222601.000,11.5,13.4,6.1,78.5,6.0,12.0,24.9*56 +$GPGSV,3,1,10,16,48,115,40,25,39,268,27,23,58,174,35,20,71,336,28*74 +$GPGSV,3,2,10,19,02,028,,04,06,242,,13,31,223,39,27,19,284,27*70 +$GPGSV,3,3,10,11,06,338,,03,14,055,23*71 +$GPRMC,222601.000,A,2734.89190,S,15305.82850,E,18.0,58.9,030308,11.2,W,A*36 +$GPVTG,58.9,T,70.1,M,18.0,N,33.4,K,A*2C +$GPGGA,222602.000,2734.88904,S,15305.83361,E,1,05,1.6,60.0,M,39.2,M,,*79 +$GPGLL,2734.88904,S,15305.83361,E,222602.000,A,A*40 +$GPGSA,A,3,16,23,20,13,03,,,,,,,,2.5,1.6,1.9*3F +$GPGST,222602.000,11.3,17.1,6.5,54.9,10.2,13.2,18.6*62 +$GPGSV,3,1,10,16,48,115,27,25,39,268,29,23,58,174,33,20,71,336,33*77 +$GPGSV,3,2,10,19,02,028,,04,06,242,,13,31,223,35,27,19,284,32*78 +$GPGSV,3,3,10,11,06,338,,03,14,055,24*76 +$GPRMC,222602.000,A,2734.88904,S,15305.83361,E,19.8,55.1,030308,11.2,W,A*35 +$GPVTG,55.1,T,66.3,M,19.8,N,36.7,K,A*23 +$GPGGA,222603.000,2734.88556,S,15305.83944,E,1,04,3.8,58.8,M,39.2,M,,*70 +$GPGLL,2734.88556,S,15305.83944,E,222603.000,A,A*47 +$GPGSA,A,3,25,23,20,27,,,,,,,,,6.5,3.8,5.2*3C +$GPGST,222603.000,12.6,48.6,15.6,66.9,41.3,21.9,62.5*59 +$GPGSV,3,1,10,16,48,115,23,25,39,268,29,23,58,174,31,20,71,337,37*74 +$GPGSV,3,2,10,19,02,028,,04,06,242,,13,31,223,34,27,19,284,26*7C +$GPGSV,3,3,10,11,06,338,,03,14,055,28*7A +$GPRMC,222603.000,A,2734.88556,S,15305.83944,E,22.7,54.4,030308,11.2,W,A*31 +$GPVTG,54.4,T,65.6,M,22.7,N,42.1,K,A*23 +$GPGGA,222604.000,2734.88238,S,15305.84546,E,1,04,4.4,57.8,M,39.2,M,,*75 +$GPGLL,2734.88238,S,15305.84546,E,222604.000,A,A*46 +$GPGSA,A,3,25,20,13,27,,,,,,,,,7.2,4.4,5.7*37 +$GPGST,222604.000,10.4,46.8,10.9,81.3,42.4,11.8,57.5*57 +$GPGSV,3,1,10,16,48,115,27,25,39,268,32,23,58,174,32,20,71,337,37*79 +$GPGSV,3,2,10,19,02,028,,04,06,242,,13,31,223,34,27,19,284,34*7F +$GPGSV,3,3,10,11,06,338,,03,14,055,29*7B +$GPRMC,222604.000,A,2734.88238,S,15305.84546,E,23.3,54.5,030308,11.2,W,A*34 +$GPVTG,54.5,T,65.7,M,23.3,N,43.2,K,A*24 +$GPGGA,222605.000,2734.87917,S,15305.85182,E,1,04,4.4,56.6,M,39.2,M,,*7F +$GPGLL,2734.87917,S,15305.85182,E,222605.000,A,A*43 +$GPGSA,A,3,25,20,13,27,,,,,,,,,7.2,4.4,5.7*37 +$GPGST,222605.000,14.3,40.3,10.1,81.5,36.5,10.7,49.8*58 +$GPGSV,3,1,10,16,48,115,24,25,39,268,32,23,58,174,33,20,71,337,37*7B +$GPGSV,3,2,10,19,02,028,,04,06,242,20,13,31,223,35,27,19,284,32*7A +$GPGSV,3,3,10,11,06,338,,03,14,055,26*74 +$GPRMC,222605.000,A,2734.87917,S,15305.85182,E,24.5,55.0,030308,11.2,W,A*34 +$GPVTG,55.0,T,66.2,M,24.5,N,45.3,K,A*20 +$GPGGA,222606.000,2734.87588,S,15305.85846,E,1,05,3.6,55.8,M,39.2,M,,*7E +$GPGLL,2734.87588,S,15305.85846,E,222606.000,A,A*4B +$GPGSA,A,3,25,23,20,13,27,,,,,,,,6.3,3.6,5.2*36 +$GPGST,222606.000,11.7,30.4,8.7,77.7,27.2,9.8,40.6*59 +$GPGSV,3,1,10,16,48,115,24,25,39,268,31,23,58,174,32,20,71,337,34*7A +$GPGSV,3,2,10,19,02,029,,04,06,242,20,13,31,223,35,27,19,284,34*7D +$GPGSV,3,3,10,11,06,338,,03,14,055,24*76 +$GPRMC,222606.000,A,2734.87588,S,15305.85846,E,25.3,55.9,030308,11.2,W,A*32 +$GPVTG,55.9,T,67.1,M,25.3,N,46.9,K,A*25 +$GPGGA,222607.000,2734.87218,S,15305.86543,E,1,06,1.8,55.5,M,39.2,M,,*78 +$GPGLL,2734.87218,S,15305.86543,E,222607.000,A,A*4F +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.6,1.8,3.1*39 +$GPGST,222607.000,11.2,13.8,8.5,86.1,7.8,12.6,27.3*52 +$GPGSV,3,1,10,16,48,115,29,25,39,268,26,23,58,174,34,20,71,337,32*71 +$GPGSV,3,2,10,19,02,029,,04,06,242,27,13,31,223,36,27,19,284,30*7D +$GPGSV,3,3,10,11,06,338,,03,14,055,24*76 +$GPRMC,222607.000,A,2734.87218,S,15305.86543,E,26.0,57.6,030308,11.2,W,A*3B +$GPVTG,57.6,T,68.8,M,26.0,N,48.1,K,A*28 +$GPGGA,222608.000,2734.86867,S,15305.87251,E,1,06,1.8,55.1,M,39.2,M,,*75 +$GPGLL,2734.86867,S,15305.87251,E,222608.000,A,A*46 +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.6,1.8,3.1*39 +$GPGST,222608.000,15.1,15.8,7.1,68.8,8.0,13.6,36.7*5C +$GPGSV,3,1,10,16,48,115,39,25,39,268,28,23,58,174,35,20,71,337,30*7D +$GPGSV,3,2,10,19,02,029,,04,06,242,27,13,31,223,40,27,19,284,28*75 +$GPGSV,3,3,10,11,06,338,,03,14,055,28*7A +$GPRMC,222608.000,A,2734.86867,S,15305.87251,E,25.9,60.7,030308,11.2,W,A*3D +$GPVTG,60.7,T,71.9,M,25.9,N,47.9,K,A*29 +$GPGGA,222609.000,2734.86555,S,15305.87973,E,1,06,1.8,54.7,M,39.2,M,,*74 +$GPGLL,2734.86555,S,15305.87973,E,222609.000,A,A*40 +$GPGSA,A,3,16,25,23,13,27,03,,,,,,,3.6,1.8,3.1*39 +$GPGST,222609.000,10.9,10.9,5.1,85.4,4.7,10.0,22.0*55 +$GPGSV,3,1,10,16,48,116,34,25,39,268,39,23,58,174,29,20,71,337,33*7D +$GPGSV,3,2,10,19,02,029,,04,06,242,24,13,31,223,42,27,19,284,23*7F +$GPGSV,3,3,10,11,06,338,,03,14,055,26*74 +$GPRMC,222609.000,A,2734.86555,S,15305.87973,E,25.7,64.3,030308,11.2,W,A*35 +$GPVTG,64.3,T,75.6,M,25.7,N,47.5,K,A*20 +$GPGGA,222610.000,2734.86253,S,15305.88695,E,1,05,1.9,54.1,M,39.2,M,,*71 +$GPGLL,2734.86253,S,15305.88695,E,222610.000,A,A*41 +$GPGSA,A,3,16,25,23,13,03,,,,,,,,3.7,1.9,3.1*3C +$GPGST,222610.000,11.1,14.6,7.4,64.7,8.3,12.4,28.7*57 +$GPGSV,3,1,10,16,48,116,29,25,39,268,42,23,58,174,28,20,71,337,32*7D +$GPGSV,3,2,10,19,02,029,,04,06,242,24,13,31,223,42,27,19,284,23*7F +$GPGSV,3,3,10,11,06,338,,03,14,055,24*76 +$GPRMC,222610.000,A,2734.86253,S,15305.88695,E,25.3,65.0,030308,11.2,W,A*32 +$GPVTG,65.0,T,76.3,M,25.3,N,46.8,K,A*2C +$GPGGA,222611.000,2734.85924,S,15305.89387,E,1,05,2.2,53.4,M,39.2,M,,*75 +$GPGLL,2734.85924,S,15305.89387,E,222611.000,A,A*4F +$GPGSA,A,3,16,25,23,20,13,,,,,,,,5.3,2.2,4.8*39 +$GPGST,222611.000,17.7,8.4,21.7,17.4,9.4,19.0,57.8*57 +$GPGSV,3,1,10,16,48,116,39,25,39,268,32,23,58,174,35,20,71,337,26*72 +$GPGSV,3,2,10,19,02,029,,04,06,242,31,13,31,223,39,27,19,284,24*70 +$GPGSV,3,3,10,11,06,338,,03,14,055,26*74 +$GPRMC,222611.000,A,2734.85924,S,15305.89387,E,24.9,61.4,030308,11.2,W,A*37 +$GPVTG,61.4,T,72.6,M,24.9,N,46.1,K,A*2F +$GPGGA,222612.000,2734.85516,S,15305.90007,E,1,07,1.2,53.3,M,39.2,M,,*7E +$GPGLL,2734.85516,S,15305.90007,E,222612.000,A,A*42 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222612.000,15.5,16.3,8.2,77.8,8.0,14.7,27.8*50 +$GPGSV,3,1,10,16,48,116,29,25,39,268,29,23,58,174,30,20,71,337,34*7F +$GPGSV,3,2,10,19,02,029,,04,06,242,27,13,31,223,35,27,19,284,28*77 +$GPGSV,3,3,10,11,06,338,,03,14,055,25*77 +$GPRMC,222612.000,A,2734.85516,S,15305.90007,E,24.5,53.1,030308,11.2,W,A*32 +$GPVTG,53.1,T,64.3,M,24.5,N,45.4,K,A*23 +$GPGGA,222613.000,2734.85043,S,15305.90565,E,1,05,1.9,53.2,M,39.2,M,,*73 +$GPGLL,2734.85043,S,15305.90565,E,222613.000,A,A*47 +$GPGSA,A,3,16,25,20,13,27,,,,,,,,3.7,1.9,3.2*3A +$GPGST,222613.000,21.1,11.0,20.9,21.8,11.7,18.1,48.5*55 +$GPGSV,3,1,10,16,48,116,30,25,39,268,32,23,58,174,24,20,71,337,36*7A +$GPGSV,3,2,10,19,02,029,,04,06,242,22,13,31,223,35,27,19,284,29*73 +$GPGSV,3,3,10,11,06,338,,03,14,055,29*7B +$GPRMC,222613.000,A,2734.85043,S,15305.90565,E,24.9,45.4,030308,11.2,W,A*39 +$GPVTG,45.4,T,56.6,M,24.9,N,46.1,K,A*2F +$GPGGA,222614.000,2734.84523,S,15305.91064,E,1,05,1.9,53.2,M,39.2,M,,*73 +$GPGLL,2734.84523,S,15305.91064,E,222614.000,A,A*47 +$GPGSA,A,3,16,25,20,13,27,,,,,,,,3.7,1.9,3.2*3A +$GPGST,222614.000,18.8,16.8,28.3,28.1,18.2,24.0,43.4*54 +$GPGSV,3,1,10,16,48,116,27,25,39,268,30,23,58,174,32,20,71,337,35*7A +$GPGSV,3,2,10,19,02,029,,04,06,242,28,13,31,223,26,27,19,284,32*71 +$GPGSV,3,3,10,11,06,338,,03,14,055,30*73 +$GPRMC,222614.000,A,2734.84523,S,15305.91064,E,24.7,40.2,030308,11.2,W,A*34 +$GPVTG,40.2,T,51.4,M,24.7,N,45.7,K,A*22 +$GPGGA,222615.000,2734.84002,S,15305.91547,E,1,07,1.2,53.0,M,39.2,M,,*7B +$GPGLL,2734.84002,S,15305.91547,E,222615.000,A,A*44 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222615.000,19.7,18.9,7.9,71.0,8.8,16.5,18.9*52 +$GPGSV,3,1,10,16,48,116,25,25,39,268,26,23,58,174,35,20,71,337,34*79 +$GPGSV,3,2,10,19,02,029,,04,06,242,24,13,31,223,25,27,19,284,30*7C +$GPGSV,3,3,10,11,06,338,,03,14,055,29*7B +$GPRMC,222615.000,A,2734.84002,S,15305.91547,E,24.1,38.7,030308,11.2,W,A*3B +$GPVTG,38.7,T,49.9,M,24.1,N,44.7,K,A*2B +$GPGGA,222616.000,2734.83488,S,15305.92024,E,1,07,1.2,53.1,M,39.2,M,,*7B +$GPGLL,2734.83488,S,15305.92024,E,222616.000,A,A*45 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222616.000,18.1,15.8,11.2,70.8,10.8,14.1,19.9*51 +$GPGSV,3,1,10,16,48,116,27,25,39,268,29,23,58,174,34,20,71,337,30*71 +$GPGSV,3,2,10,19,02,029,,04,06,242,27,13,31,223,24,27,19,284,28*77 +$GPGSV,3,3,10,11,06,338,,03,14,055,32*71 +$GPRMC,222616.000,A,2734.83488,S,15305.92024,E,24.0,39.3,030308,11.2,W,A*3E +$GPVTG,39.3,T,50.5,M,24.0,N,44.5,K,A*29 +$GPGGA,222617.000,2734.82955,S,15305.92505,E,1,07,1.2,53.2,M,39.2,M,,*73 +$GPGLL,2734.82955,S,15305.92505,E,222617.000,A,A*4E +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222617.000,14.7,13.3,9.2,70.7,8.9,11.8,16.8*5B +$GPGSV,3,1,10,16,48,116,23,25,39,268,30,23,58,174,35,20,71,337,33*7F +$GPGSV,3,2,10,19,02,029,,04,06,242,25,13,31,223,25,27,19,284,26*7A +$GPGSV,3,3,10,11,06,338,,03,14,055,32*71 +$GPRMC,222617.000,A,2734.82955,S,15305.92505,E,24.7,38.8,030308,11.2,W,A*38 +$GPVTG,38.8,T,50.0,M,24.7,N,45.7,K,A*22 +$GPGGA,222618.000,2734.82405,S,15305.92991,E,1,07,1.2,53.5,M,39.2,M,,*72 +$GPGLL,2734.82405,S,15305.92991,E,222618.000,A,A*48 +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222618.000,13.7,14.7,7.8,77.2,7.6,13.2,24.3*54 +$GPGSV,3,1,10,16,48,116,27,25,39,268,30,23,58,174,36,20,71,337,32*79 +$GPGSV,3,2,10,19,02,029,,04,06,242,31,13,31,223,25,27,19,283,23*7D +$GPGSV,3,3,10,11,06,338,,03,14,056,32*72 +$GPRMC,222618.000,A,2734.82405,S,15305.92991,E,25.3,38.3,030308,11.2,W,A*30 +$GPVTG,38.3,T,49.5,M,25.3,N,46.8,K,A*2D +$GPGGA,222619.000,2734.81867,S,15305.93485,E,1,07,1.2,53.8,M,39.2,M,,*7C +$GPGLL,2734.81867,S,15305.93485,E,222619.000,A,A*4B +$GPGSA,A,3,16,25,23,20,13,27,03,,,,,,2.1,1.2,1.7*33 +$GPGST,222619.000,20.5,18.3,9.9,85.5,9.1,16.7,27.9*5A +$GPGSV,3,1,10,16,48,116,27,25,39,268,30,23,58,174,35,20,71,337,33*7B +$GPGSV,3,2,10,19,02,029,,04,06,242,29,13,31,223,23,27,19,283,23*72 +$GPGSV,3,3,10,11,06,338,,03,14,056,32*72 +$GPRMC,222619.000,A,2734.81867,S,15305.93485,E,25.3,39.2,030308,11.2,W,A*33 +$GPVTG,39.2,T,50.4,M,25.3,N,46.8,K,A*24 +$GPGGA,222620.000,2734.81344,S,15305.93979,E,1,05,1.6,53.8,M,39.2,M,,*74 +$GPGLL,2734.81344,S,15305.93979,E,222620.000,A,A*45 +$GPGSA,A,3,16,23,20,13,03,,,,,,,,2.5,1.6,1.9*3F +$GPGST,222620.000,18.2,14.7,21.1,9.8,19.1,13.7,29.8*64 +$GPGSV,3,1,10,16,48,116,26,25,39,268,30,23,58,174,33,20,71,337,34*7B +$GPGSV,3,2,10,19,02,029,,04,06,242,24,13,31,223,25,27,19,283,29*73 +$GPGSV,3,3,10,11,06,338,,03,14,056,33*73 +$GPRMC,222620.000,A,2734.81344,S,15305.93979,E,24.7,39.5,030308,11.2,W,A*3F +$GPVTG,39.5,T,50.7,M,24.7,N,45.8,K,A*26 +$GPGGA,222621.000,2734.80838,S,15305.94469,E,1,06,1.3,53.5,M,39.2,M,,*74 +$GPGLL,2734.80838,S,15305.94469,E,222621.000,A,A*4E +$GPGSA,A,3,16,25,23,20,13,03,,,,,,,2.3,1.3,1.9*3B +$GPGST,222621.000,14.6,11.8,13.4,39.2,11.7,11.4,21.8*5D +$GPGSV,3,1,10,16,48,116,25,25,39,268,31,23,58,174,33,20,71,337,37*7A +$GPGSV,3,2,10,19,03,029,,04,06,242,24,13,31,223,24,27,19,283,36*7D +$GPGSV,3,3,10,11,06,338,,03,14,056,32*72 +$GPRMC,222621.000,A,2734.80838,S,15305.94469,E,24.1,40.1,030308,11.2,W,A*38 +$GPVTG,40.1,T,51.3,M,24.1,N,44.7,K,A*21 +$GPGGA,222622.000,2734.80354,S,15305.94956,E,1,06,1.7,53.3,M,39.2,M,,*75 +$GPGLL,2734.80354,S,15305.94956,E,222622.000,A,A*4D +$GPGSA,A,3,16,25,23,20,13,27,,,,,,,3.3,1.7,2.9*3B +$GPGST,222622.000,13.2,10.2,17.1,41.6,12.5,13.2,30.8*59 +$GPGSV,3,1,10,16,48,116,26,25,39,268,31,23,58,174,26,20,71,337,39*73 +$GPGSV,3,2,10,19,03,029,,04,06,242,24,13,31,223,28,27,19,283,36*71 +$GPGSV,3,3,10,11,06,338,,03,14,056,35*75 +$GPRMC,222622.000,A,2734.80354,S,15305.94956,E,23.6,41.3,030308,11.2,W,A*38 +$GPVTG,41.3,T,52.5,M,23.6,N,43.7,K,A*20 +$GPGGA,222623.000,2734.79878,S,15305.95470,E,1,05,1.9,52.8,M,39.2,M,,*78 +$GPGLL,2734.79878,S,15305.95470,E,222623.000,A,A*47 +$GPGSA,A,3,16,25,20,13,27,,,,,,,,3.7,1.9,3.2*3A +$GPGST,222623.000,14.7,24.8,16.6,50.3,19.9,18.6,39.3*5E +$GPGSV,3,1,11,16,48,116,24,25,39,268,32,23,58,174,24,20,71,337,38*70 +$GPGSV,3,2,11,19,03,029,,04,06,242,24,13,31,223,32,27,19,283,34*79 +$GPGSV,3,3,11,11,06,338,,01,,,18,03,14,056,34*7D +$GPRMC,222623.000,A,2734.79878,S,15305.95470,E,24.0,43.4,030308,11.2,W,A*36 +$GPVTG,43.4,T,54.6,M,24.0,N,44.4,K,A*25 +$GPGGA,222624.000,2734.79400,S,15305.96019,E,1,05,1.9,52.3,M,39.2,M,,*7F +$GPGLL,2734.79400,S,15305.96019,E,222624.000,A,A*4B +$GPGSA,A,3,16,25,20,27,03,,,,,,,,2.7,1.9,2.0*39 +$GPGST,222624.000,12.3,24.0,7.9,80.2,8.0,21.7,15.3*5C +$GPGSV,3,1,11,16,48,116,23,25,39,268,32,23,58,174,26,20,71,337,37*7A +$GPGSV,3,2,11,19,03,029,,04,06,242,24,13,31,223,35,27,19,283,25*7E +$GPGSV,3,3,11,11,06,338,,01,,,18,03,14,056,31*78 +$GPRMC,222624.000,A,2734.79400,S,15305.96019,E,24.7,45.3,030308,11.2,W,A*3C +$GPVTG,45.3,T,56.6,M,24.7,N,45.7,K,A*23 +$GPGGA,222625.000,2734.78953,S,15305.96595,E,1,05,1.9,51.8,M,39.2,M,,*7D +$GPGLL,2734.78953,S,15305.96595,E,222625.000,A,A*41 +$GPGSA,A,3,16,25,20,13,27,,,,,,,,3.7,1.9,3.2*3A +$GPGST,222625.000,24.0,12.2,19.9,37.5,14.2,16.0,36.1*57 +$GPGSV,3,1,11,16,48,116,30,25,39,268,32,23,58,174,23,20,71,337,37*7D +$GPGSV,3,2,11,19,03,029,,04,06,242,25,13,31,223,36,27,19,283,25*7C +$GPGSV,3,3,11,11,06,338,,01,,,18,03,14,056,24*7C +$GPRMC,222625.000,A,2734.78953,S,15305.96595,E,24.8,47.9,030308,11.2,W,A*31 +$GPVTG,47.9,T,59.1,M,24.8,N,46.0,K,A*28 +$GPGGA,222626.000,2734.78516,S,15305.97172,E,1,05,2.5,51.1,M,39.2,M,,*79 +$GPGLL,2734.78516,S,15305.97172,E,222626.000,A,A*43 +$GPGSA,A,3,16,25,20,04,13,,,,,,,,4.2,2.5,3.3*37 +$GPGST,222626.000,20.2,12.6,23.1,19.8,13.0,20.2,38.4*5F +$GPGSV,3,1,11,16,48,116,31,25,39,268,32,23,58,174,25,20,71,337,38*75 +$GPGSV,3,2,11,19,03,029,,04,06,242,25,13,31,223,36,27,19,283,23*7A +$GPGSV,3,3,11,11,06,338,,01,,,18,03,14,056,24*7C +$GPRMC,222626.000,A,2734.78516,S,15305.97172,E,24.7,48.3,030308,11.2,W,A*39 +$GPVTG,48.3,T,59.5,M,24.7,N,45.7,K,A*22 +$GPGGA,222627.000,2734.78079,S,15305.97737,E,1,04,2.5,50.5,M,39.2,M,,*77 +$GPGLL,2734.78079,S,15305.97737,E,222627.000,A,A*49 +$GPGSA,A,3,16,23,20,13,,,,,,,,,5.8,2.5,5.2*39 +$GPGST,222627.000,16.3,12.4,42.2,11.3,13.5,37.9,90.0*51 +$GPGSV,3,1,11,16,48,116,28,25,39,268,32,23,58,174,25,20,71,337,37*72 +$GPGSV,3,2,11,19,03,029,,04,06,242,29,13,31,223,37,27,19,283,30*75 +$GPGSV,3,3,11,11,06,338,,01,,,18,03,14,056,24*7C +$GPRMC,222627.000,A,2734.78079,S,15305.97737,E,24.3,48.1,030308,11.2,W,A*35 +$GPVTG,48.1,T,59.3,M,24.3,N,45.1,K,A*24 +$GPGGA,222628.000,2734.77637,S,15305.98293,E,1,07,1.3,50.2,M,39.2,M,,*7E +$GPGLL,2734.77637,S,15305.98293,E,222628.000,A,A*41 +$GPGSA,A,3,16,25,23,04,13,27,03,,,,,,2.2,1.3,1.8*38 +$GPGST,222628.000,13.2,14.5,8.8,76.9,8.4,13.0,22.0*5F +$GPGSV,3,1,11,16,48,116,30,25,39,268,32,23,58,174,27,20,71,337,37*79 +$GPGSV,3,2,11,19,03,029,,04,06,242,32,13,31,223,37,27,19,283,28*76 +$GPGSV,3,3,11,11,06,338,,01,,,18,03,14,056,25*7D +$GPRMC,222628.000,A,2734.77637,S,15305.98293,E,23.9,48.5,030308,11.2,W,A*34 +$GPVTG,48.5,T,59.7,M,23.9,N,44.3,K,A*2A +$GPGGA,222629.000,2734.77226,S,15305.98850,E,1,08,1.1,49.5,M,39.2,M,,*7C +$GPGLL,2734.77226,S,15305.98850,E,222629.000,A,A*41 +$GPGSA,A,3,16,25,23,20,04,13,27,03,,,,,1.7,1.1,1.3*35 +$GPGST,222629.000,11.0,9.5,7.3,89.6,6.7,8.6,14.3*5E +$GPGSV,3,1,11,16,48,116,31,25,39,268,32,23,58,174,25,20,71,337,36*7B +$GPGSV,3,2,11,19,03,029,,04,06,242,30,13,31,223,37,27,19,283,28*74 +$GPGSV,3,3,11,11,06,338,,01,,,18,03,14,056,27*7F +$GPRMC,222629.000,A,2734.77226,S,15305.98850,E,23.5,49.4,030308,11.2,W,A*38 +$GPVTG,49.4,T,60.6,M,23.5,N,43.5,K,A*2C +$GPGGA,222630.000,2734.76859,S,15305.99361,E,1,04,3.5,49.4,M,39.2,M,,*74 +$GPGLL,2734.76859,S,15305.99361,E,222630.000,A,A*42 +$GPGSA,A,3,16,25,20,27,,,,,,,,,4.9,3.5,3.4*39 +$GPGST,222630.000,13.4,7.9,50.0,19.9,17.0,43.1,69.5*66 +$GPGSV,3,1,11,16,48,116,31,25,39,268,30,23,58,174,22,20,71,337,36*7E +$GPGSV,3,2,11,19,03,029,,04,06,242,32,13,31,223,35,27,19,283,24*78 +$GPGSV,3,3,11,11,06,338,18,01,,,18,03,14,056,29*78 +$GPRMC,222630.000,A,2734.76859,S,15305.99361,E,21.1,50.3,030308,11.2,W,A*32 +$GPVTG,50.3,T,61.5,M,21.1,N,39.1,K,A*2E +$GPGGA,222631.000,2734.76498,S,15305.99809,E,1,08,1.1,48.2,M,39.2,M,,*7C +$GPGLL,2734.76498,S,15305.99809,E,222631.000,A,A*47 +$GPGSA,A,3,16,25,23,20,04,13,27,03,,,,,1.7,1.1,1.3*35 +$GPGST,222631.000,18.4,10.8,5.8,77.6,5.6,9.7,13.2*63 +$GPGSV,3,1,11,16,48,116,32,25,39,268,31,23,58,174,22,20,71,337,35*7F +$GPGSV,3,2,11,19,03,029,,04,06,242,30,13,31,223,36,27,19,283,25*78 +$GPGSV,3,3,11,11,06,338,18,01,,,18,03,14,056,33*73 +$GPRMC,222631.000,A,2734.76498,S,15305.99809,E,19.4,47.4,030308,11.2,W,A*38 +$GPVTG,47.4,T,58.6,M,19.4,N,35.9,K,A*2C +$GPGGA,222632.000,2734.76359,S,15306.00162,E,1,08,1.1,49.6,M,39.2,M,,*77 +$GPGLL,2734.76359,S,15306.00162,E,222632.000,A,A*49 +$GPGSA,A,3,16,25,23,20,04,13,27,03,,,,,1.7,1.1,1.3*35 +$GPGST,222632.000,18.1,14.9,6.7,53.4,9.5,11.5,16.1*5A +$GPGSV,3,1,11,16,48,116,30,25,39,268,31,23,58,174,31,20,71,337,31*7B +$GPGSV,3,2,11,19,03,029,,04,06,242,22,13,31,223,37,27,19,283,29*76 +$GPGSV,3,3,11,11,06,338,24,01,,,18,03,14,056,25*7B +$GPRMC,222632.000,A,2734.76359,S,15306.00162,E,12.4,65.5,030308,11.2,W,A*3C +$GPVTG,65.5,T,76.7,M,12.4,N,22.9,K,A*2D +$GPGGA,222633.000,2734.76254,S,15306.00553,E,1,08,1.1,49.0,M,39.2,M,,*7A +$GPGLL,2734.76254,S,15306.00553,E,222633.000,A,A*42 +$GPGSA,A,3,16,25,23,20,04,13,27,03,,,,,1.7,1.1,1.3*35 +$GPGST,222633.000,13.9,12.3,5.8,55.3,7.8,9.7,13.7*62 +$GPGSV,3,1,10,16,48,116,35,25,39,268,35,23,58,174,30,20,71,337,35*7E +$GPGSV,3,2,10,19,03,029,,04,06,242,26,13,31,223,39,27,19,283,26*72 +$GPGSV,3,3,10,11,06,338,24,03,14,056,30*76 +$GPRMC,222633.000,A,2734.76254,S,15306.00553,E,12.9,72.5,030308,11.2,W,A*3C +$GPVTG,72.5,T,83.7,M,12.9,N,23.9,K,A*2D +$GPGGA,222634.000,2734.76305,S,15306.00850,E,1,08,1.1,48.4,M,39.2,M,,*73 +$GPGLL,2734.76305,S,15306.00850,E,222634.000,A,A*4E +$GPGSA,A,3,16,25,23,20,04,13,27,03,,,,,1.7,1.1,1.3*35 +$GPGST,222634.000,11.3,10.3,5.3,58.0,6.5,8.4,12.0*62 +$GPGSV,3,1,10,16,48,116,33,25,39,268,38,23,58,174,30,20,71,337,30*70 +$GPGSV,3,2,10,19,03,029,,04,06,242,29,13,31,223,34,27,19,283,30*77 +$GPGSV,3,3,10,11,06,338,23,03,14,056,25*75 +$GPRMC,222634.000,A,2734.76305,S,15306.00850,E,9.4,99.3,030308,11.2,W,A*04 +$GPVTG,99.3,T,110.5,M,9.4,N,17.3,K,A*2D +$GPGGA,222635.000,2734.76418,S,15306.01089,E,1,08,1.1,47.9,M,39.2,M,,*76 +$GPGLL,2734.76418,S,15306.01089,E,222635.000,A,A*49 +$GPGSA,A,3,16,25,23,20,04,13,27,03,,,,,1.7,1.1,1.3*35 +$GPGST,222635.000,13.4,9.3,7.7,66.3,7.3,8.3,14.0*50 +$GPGSV,3,1,10,16,48,116,36,25,39,268,31,23,58,174,37,20,71,337,36*7D +$GPGSV,3,2,10,19,03,029,,04,06,242,31,13,31,223,34,27,19,283,31*7F +$GPGSV,3,3,10,11,06,338,23,03,14,056,23*73 +$GPRMC,222635.000,A,2734.76418,S,15306.01089,E,8.3,116.7,030308,11.2,W,A*37 +$GPVTG,116.7,T,127.9,M,8.3,N,15.4,K,A*14 +$GPGGA,222636.000,2734.76577,S,15306.01258,E,1,07,1.7,47.9,M,39.2,M,,*7A +$GPGLL,2734.76577,S,15306.01258,E,222636.000,A,A*4C +$GPGSA,A,3,16,25,23,20,04,13,27,,,,,,2.8,1.7,2.3*3F +$GPGST,222636.000,10.6,11.9,8.1,45.4,9.3,9.3,18.8*65 +$GPGSV,3,1,10,16,48,116,41,25,39,268,35,23,58,174,40,20,71,337,33*7C +$GPGSV,3,2,10,19,03,029,,04,06,242,32,13,31,223,28,27,19,283,34*74 +$GPGSV,3,3,10,11,06,338,25,03,14,056,23*75 +$GPRMC,222636.000,A,2734.76577,S,15306.01258,E,7.6,135.7,030308,11.2,W,A*39 +$GPVTG,135.7,T,146.9,M,7.6,N,14.1,K,A*1C +$GPGGA,222637.000,2734.76759,S,15306.01422,E,1,07,1.7,47.8,M,39.2,M,,*7F +$GPGLL,2734.76759,S,15306.01422,E,222637.000,A,A*48 +$GPGSA,A,3,16,25,23,20,04,13,27,,,,,,2.8,1.7,2.3*3F +$GPGST,222637.000,8.9,7.2,10.9,44.4,8.4,8.5,17.1*59 +$GPGSV,3,1,10,16,48,116,44,25,39,268,36,23,58,174,33,20,71,337,33*7E +$GPGSV,3,2,10,19,03,029,,04,06,242,24,13,31,223,29,27,19,283,36*70 +$GPGSV,3,3,10,11,06,338,25,03,14,056,23*75 +$GPRMC,222637.000,A,2734.76759,S,15306.01422,E,8.0,140.6,030308,11.2,W,A*37 +$GPVTG,140.6,T,151.8,M,8.0,N,14.8,K,A*18 +$GPGGA,222638.000,2734.76957,S,15306.01592,E,1,06,2.0,47.6,M,39.2,M,,*71 +$GPGLL,2734.76957,S,15306.01592,E,222638.000,A,A*4D +$GPGSA,A,3,16,25,23,20,04,13,,,,,,,3.2,2.0,2.6*30 +$GPGST,222638.000,10.4,8.9,16.3,14.1,8.7,14.6,19.9*51 +$GPGSV,3,1,10,16,48,116,38,25,39,268,30,23,58,174,29,20,71,337,29*73 +$GPGSV,3,2,10,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,29*7B +$GPGSV,3,3,10,11,06,338,21,03,14,056,23*71 +$GPRMC,222638.000,A,2734.76957,S,15306.01592,E,8.8,142.8,030308,11.2,W,A*36 +$GPVTG,142.8,T,154.0,M,8.8,N,16.2,K,A*19 +$GPGGA,222639.000,2734.77109,S,15306.01748,E,1,07,1.7,47.8,M,39.2,M,,*7C +$GPGLL,2734.77109,S,15306.01748,E,222639.000,A,A*4B +$GPGSA,A,3,16,25,23,20,04,13,27,,,,,,2.8,1.7,2.3*3F +$GPGST,222639.000,9.5,9.6,14.7,43.8,11.3,11.5,19.8*51 +$GPGSV,3,1,10,16,48,116,37,25,39,268,32,23,58,174,37,20,71,337,29*71 +$GPGSV,3,2,10,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,27*75 +$GPGSV,3,3,10,11,06,338,21,03,14,056,23*71 +$GPRMC,222639.000,A,2734.77109,S,15306.01748,E,7.1,137.2,030308,11.2,W,A*3E +$GPVTG,137.2,T,148.4,M,7.1,N,13.1,K,A*18 +$GPGGA,222640.000,2734.77216,S,15306.02018,E,1,04,2.6,47.8,M,39.2,M,,*7F +$GPGLL,2734.77216,S,15306.02018,E,222640.000,A,A*49 +$GPGSA,A,3,16,25,23,13,,,,,,,,,6.2,2.6,5.6*32 +$GPGST,222640.000,20.0,11.7,22.3,7.0,10.9,20.3,46.8*65 +$GPGSV,3,1,10,16,48,116,32,25,39,268,29,23,58,174,24,20,71,337,25*70 +$GPGSV,3,2,10,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,25*77 +$GPGSV,3,3,10,11,06,338,21,03,14,056,23*71 +$GPRMC,222640.000,A,2734.77216,S,15306.02018,E,9.4,114.2,030308,11.2,W,A*36 +$GPVTG,114.2,T,125.4,M,9.4,N,17.4,K,A*18 +$GPGGA,222641.000,2734.77543,S,15306.02149,E,1,04,2.6,47.7,M,39.2,M,,*73 +$GPGLL,2734.77543,S,15306.02149,E,222641.000,A,A*4A +$GPGSA,A,3,16,25,23,13,,,,,,,,,6.2,2.6,5.6*32 +$GPGST,222641.000,18.3,40.3,13.2,79.5,13.7,36.3,59.1*5E +$GPGSV,3,1,10,16,48,116,34,25,39,268,23,23,58,174,26,20,71,337,27*7C +$GPGSV,3,2,10,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,23*71 +$GPGSV,3,3,10,11,06,338,21,03,14,056,23*71 +$GPRMC,222641.000,A,2734.77543,S,15306.02149,E,12.5,160.9,030308,11.2,W,A*06 +$GPVTG,160.9,T,172.1,M,12.5,N,23.1,K,A*2E +$GPGGA,222642.000,2734.77709,S,15306.02300,E,1,04,2.6,47.7,M,39.2,M,,*73 +$GPGLL,2734.77709,S,15306.02300,E,222642.000,A,A*4A +$GPGSA,A,3,16,20,04,13,,,,,,,,,4.3,2.6,3.4*35 +$GPGST,222642.000,15.8,14.5,71.8,20.7,26.3,61.6,97.2*58 +$GPGSV,3,1,10,16,48,116,41,25,40,268,23,23,58,174,27,20,71,337,34*73 +$GPGSV,3,2,10,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,23*71 +$GPGSV,3,3,10,11,06,338,21,03,14,056,23*71 +$GPRMC,222642.000,A,2734.77709,S,15306.02300,E,7.5,140.7,030308,11.2,W,A*3E +$GPVTG,140.7,T,151.9,M,7.5,N,13.9,K,A*14 +$GPGGA,222643.000,2734.77831,S,15306.02427,E,1,04,2.6,47.5,M,39.2,M,,*76 +$GPGLL,2734.77831,S,15306.02427,E,222643.000,A,A*4D +$GPGSA,A,3,16,20,04,13,,,,,,,,,4.3,2.6,3.4*35 +$GPGST,222643.000,12.9,15.5,112.8,21.4,39.9,96.1,154.3*5C +$GPGSV,3,1,10,16,48,116,41,25,40,268,23,23,58,174,26,20,71,337,35*73 +$GPGSV,3,2,10,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,23*71 +$GPGSV,3,3,10,11,06,338,21,03,14,056,23*71 +$GPRMC,222643.000,A,2734.77831,S,15306.02427,E,5.6,136.2,030308,11.2,W,A*3C +$GPVTG,136.2,T,147.4,M,5.6,N,10.4,K,A*15 +$GPGGA,222644.000,2734.77900,S,15306.02522,E,1,04,2.5,47.4,M,39.2,M,,*74 +$GPGLL,2734.77900,S,15306.02522,E,222644.000,A,A*4D +$GPGSA,A,3,16,23,20,13,,,,,,,,,5.8,2.5,5.2*39 +$GPGST,222644.000,11.9,32.3,39.9,23.1,35.5,30.7,165.7*69 +$GPGSV,3,1,10,16,48,116,38,25,40,268,23,23,58,174,25,20,71,337,37*7C +$GPGSV,3,2,10,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,23*71 +$GPGSV,3,3,10,11,06,338,21,03,14,056,26*74 +$GPRMC,222644.000,A,2734.77900,S,15306.02522,E,3.7,127.8,030308,11.2,W,A*31 +$GPVTG,127.8,T,139.0,M,3.7,N,6.9,K,A*2F +$GPGGA,222645.000,2734.77991,S,15306.02594,E,1,04,3.4,47.4,M,39.2,M,,*70 +$GPGLL,2734.77991,S,15306.02594,E,222645.000,A,A*49 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.4,5.0*30 +$GPGST,222645.000,15.3,40.1,15.0,66.6,34.1,19.2,69.1*50 +$GPGSV,3,1,10,16,48,116,33,25,40,268,23,23,58,174,23,20,71,337,35*73 +$GPGSV,3,2,10,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,23*71 +$GPGSV,3,3,10,11,06,338,21,03,14,056,30*73 +$GPRMC,222645.000,A,2734.77991,S,15306.02594,E,3.9,145.4,030308,11.2,W,A*33 +$GPVTG,145.4,T,156.6,M,3.9,N,7.2,K,A*2C +$GPGGA,222646.000,2734.78074,S,15306.02652,E,1,04,3.4,47.4,M,39.2,M,,*77 +$GPGLL,2734.78074,S,15306.02652,E,222646.000,A,A*4E +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.4,5.0*30 +$GPGST,222646.000,14.8,53.5,15.6,78.9,48.1,16.9,76.9*50 +$GPGSV,3,1,11,16,48,116,32,25,40,268,23,23,58,174,26,20,71,337,32*71 +$GPGSV,3,2,11,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,23*70 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,34*4D +$GPRMC,222646.000,A,2734.78074,S,15306.02652,E,3.5,149.5,030308,11.2,W,A*35 +$GPVTG,149.5,T,160.7,M,3.5,N,6.6,K,A*2C +$GPGGA,222647.000,2734.78140,S,15306.02704,E,1,04,3.4,47.3,M,39.2,M,,*75 +$GPGLL,2734.78140,S,15306.02704,E,222647.000,A,A*4B +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.4,5.0*30 +$GPGST,222647.000,11.7,42.4,13.1,77.7,38.0,14.3,60.9*53 +$GPGSV,3,1,11,16,48,116,37,25,40,268,23,23,58,174,31,20,71,337,36*76 +$GPGSV,3,2,11,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,23*70 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,34*4D +$GPRMC,222647.000,A,2734.78140,S,15306.02704,E,2.8,145.4,030308,11.2,W,A*31 +$GPVTG,145.4,T,156.6,M,2.8,N,5.1,K,A*2D +$GPGGA,222648.000,2734.78183,S,15306.02753,E,1,04,3.5,47.2,M,39.2,M,,*77 +$GPGLL,2734.78183,S,15306.02753,E,222648.000,A,A*49 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222648.000,11.3,35.2,16.9,76.3,31.5,16.9,53.8*53 +$GPGSV,3,1,11,16,48,116,35,25,40,267,23,23,58,174,37,20,71,337,35*7E +$GPGSV,3,2,11,19,03,029,,04,06,242,28,13,31,223,31,27,19,283,23*70 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,35*4C +$GPRMC,222648.000,A,2734.78183,S,15306.02753,E,2.1,135.8,030308,11.2,W,A*31 +$GPVTG,135.8,T,147.1,M,2.1,N,3.9,K,A*26 +$GPGGA,222649.000,2734.78197,S,15306.02773,E,1,04,3.5,46.8,M,39.2,M,,*7A +$GPGLL,2734.78197,S,15306.02773,E,222649.000,A,A*4F +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222649.000,13.9,60.8,14.8,84.0,55.3,14.7,79.7*52 +$GPGSV,3,1,11,16,48,116,31,25,40,267,23,23,58,174,36,20,71,337,31*7F +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,19,283,23*78 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,29*41 +$GPRMC,222649.000,A,2734.78197,S,15306.02773,E,0.6,148.6,030308,11.2,W,A*36 +$GPVTG,148.6,T,159.8,M,0.6,N,1.2,K,A*28 +$GPGGA,222650.000,2734.78195,S,15306.02785,E,1,04,3.5,46.4,M,39.2,M,,*75 +$GPGLL,2734.78195,S,15306.02785,E,222650.000,A,A*4C +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222650.000,11.4,48.0,12.9,83.3,43.6,12.8,63.8*5B +$GPGSV,3,1,11,16,48,116,30,25,40,267,23,23,58,174,33,20,71,337,30*7A +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,19,283,23*78 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,26*4E +$GPRMC,222650.000,A,2734.78195,S,15306.02785,E,0.2,6.4,030308,11.2,W,A*38 +$GPVTG,6.4,T,17.6,M,0.2,N,0.3,K,A*10 +$GPGGA,222651.000,2734.78191,S,15306.02798,E,1,04,3.5,45.9,M,39.2,M,,*72 +$GPGLL,2734.78191,S,15306.02798,E,222651.000,A,A*45 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222651.000,9.8,40.0,12.0,82.4,36.3,11.9,53.8*6E +$GPGSV,3,1,11,16,48,116,28,25,40,267,,23,58,174,35,20,71,337,26*73 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,19,283,*79 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,28*40 +$GPRMC,222651.000,A,2734.78191,S,15306.02798,E,0.1,352.6,030308,11.2,W,A*32 +$GPVTG,352.6,T,3.8,M,0.1,N,0.3,K,A*28 +$GPGGA,222652.000,2734.78198,S,15306.02797,E,1,04,3.5,45.9,M,39.2,M,,*77 +$GPGLL,2734.78198,S,15306.02797,E,222652.000,A,A*40 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222652.000,21.7,41.2,15.0,83.8,37.5,14.2,91.6*58 +$GPGSV,3,1,11,16,48,116,24,25,40,267,,23,58,174,23,20,71,337,26*78 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,19,283,*79 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,24*4C +$GPRMC,222652.000,A,2734.78198,S,15306.02797,E,0.2,263.5,030308,11.2,W,A*34 +$GPVTG,263.5,T,274.7,M,0.2,N,0.3,K,A*26 +$GPGGA,222653.000,2734.78226,S,15306.02793,E,1,04,3.5,45.9,M,39.2,M,,*74 +$GPGLL,2734.78226,S,15306.02793,E,222653.000,A,A*43 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222653.000,18.8,36.4,20.7,81.4,33.0,19.3,78.6*5F +$GPGSV,3,1,11,16,48,116,24,25,40,267,,23,58,174,25,20,71,337,25*7D +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,24*4C +$GPRMC,222653.000,A,2734.78226,S,15306.02793,E,0.8,188.1,030308,11.2,W,A*3F +$GPVTG,188.1,T,199.3,M,0.8,N,1.5,K,A*2D +$GPGGA,222654.000,2734.78231,S,15306.02789,E,1,04,3.5,45.9,M,39.2,M,,*7E +$GPGLL,2734.78231,S,15306.02789,E,222654.000,A,A*49 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222654.000,14.4,23.3,16.5,69.8,20.7,16.0,57.9*5D +$GPGSV,3,1,11,16,48,116,34,25,40,267,,23,58,174,34,20,71,337,31*79 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,31*48 +$GPRMC,222654.000,A,2734.78231,S,15306.02789,E,0.1,145.6,030308,11.2,W,A*3A +$GPVTG,145.6,T,156.8,M,0.1,N,0.2,K,A*2C +$GPGGA,222655.000,2734.78251,S,15306.02806,E,1,04,3.5,45.8,M,39.2,M,,*70 +$GPGLL,2734.78251,S,15306.02806,E,222655.000,A,A*46 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222655.000,12.0,28.1,14.0,78.7,25.3,13.6,52.5*54 +$GPGSV,3,1,11,16,48,116,40,25,40,267,,23,58,174,38,20,71,337,35*72 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,36*4F +$GPRMC,222655.000,A,2734.78251,S,15306.02806,E,0.7,135.5,030308,11.2,W,A*37 +$GPVTG,135.5,T,146.8,M,0.7,N,1.2,K,A*2E +$GPGGA,222656.000,2734.78273,S,15306.02823,E,1,04,3.5,45.7,M,39.2,M,,*7B +$GPGLL,2734.78273,S,15306.02823,E,222656.000,A,A*42 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222656.000,9.9,24.8,12.2,76.6,22.2,12.0,44.0*69 +$GPGSV,3,1,11,16,48,116,38,25,40,267,,23,58,174,36,20,71,337,30*76 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,35*4C +$GPRMC,222656.000,A,2734.78273,S,15306.02823,E,0.5,135.5,030308,11.2,W,A*31 +$GPVTG,135.5,T,146.7,M,0.5,N,1.0,K,A*21 +$GPGGA,222657.000,2734.78312,S,15306.02858,E,1,04,3.5,45.5,M,39.2,M,,*72 +$GPGLL,2734.78312,S,15306.02858,E,222657.000,A,A*49 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222657.000,8.4,22.4,10.9,74.9,19.9,11.0,38.1*60 +$GPGSV,3,1,11,16,48,116,37,25,40,267,,23,58,174,36,20,71,337,30*79 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,21,08,00,298,,03,14,056,34*4D +$GPRMC,222657.000,A,2734.78312,S,15306.02858,E,1.3,137.6,030308,11.2,W,A*3C +$GPVTG,137.6,T,148.8,M,1.3,N,2.4,K,A*21 +$GPGGA,222658.000,2734.78370,S,15306.02910,E,1,04,3.5,45.4,M,39.2,M,,*75 +$GPGLL,2734.78370,S,15306.02910,E,222658.000,A,A*4F +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222658.000,11.5,25.4,19.7,45.8,20.9,20.7,46.1*53 +$GPGSV,3,1,11,16,48,116,34,25,40,267,18,23,58,174,36,20,71,337,32*71 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,32*48 +$GPRMC,222658.000,A,2734.78370,S,15306.02910,E,2.7,141.8,030308,11.2,W,A*32 +$GPVTG,141.8,T,153.0,M,2.7,N,5.0,K,A*28 +$GPGGA,222659.000,2734.78463,S,15306.02956,E,1,04,3.5,45.4,M,39.2,M,,*73 +$GPGLL,2734.78463,S,15306.02956,E,222659.000,A,A*49 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222659.000,10.9,34.8,17.8,79.2,31.4,17.1,52.2*5E +$GPGSV,3,1,11,16,48,116,32,25,40,267,18,23,58,174,37,20,71,337,32*76 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,26*4D +$GPRMC,222659.000,A,2734.78463,S,15306.02956,E,3.6,157.2,030308,11.2,W,A*39 +$GPVTG,157.2,T,168.4,M,3.6,N,6.7,K,A*2D +$GPGGA,222700.000,2734.78553,S,15306.02974,E,1,04,3.5,45.3,M,39.2,M,,*7B +$GPGLL,2734.78553,S,15306.02974,E,222700.000,A,A*46 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222700.000,13.4,30.5,15.4,80.9,27.6,14.6,58.2*5C +$GPGSV,3,1,11,16,48,116,38,25,40,267,18,23,58,174,42,20,71,337,34*78 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,30*4A +$GPRMC,222700.000,A,2734.78553,S,15306.02974,E,3.1,171.1,030308,11.2,W,A*36 +$GPVTG,171.1,T,182.3,M,3.1,N,5.7,K,A*2D +$GPGGA,222701.000,2734.78678,S,15306.02887,E,1,05,1.6,45.4,M,39.2,M,,*7A +$GPGLL,2734.78678,S,15306.02887,E,222701.000,A,A*40 +$GPGSA,A,3,16,23,20,13,03,,,,,,,,2.5,1.6,1.9*3F +$GPGST,222701.000,18.6,22.3,33.4,25.2,29.0,22.6,46.2*53 +$GPGSV,3,1,11,16,48,116,30,25,40,267,18,23,58,174,33,20,71,337,27*74 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,20,27,20,283,*71 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,27*4C +$GPRMC,222701.000,A,2734.78678,S,15306.02887,E,5.1,212.6,030308,11.2,W,A*37 +$GPVTG,212.6,T,223.8,M,5.1,N,9.5,K,A*27 +$GPGGA,222702.000,2734.78726,S,15306.02769,E,1,04,3.5,45.3,M,39.2,M,,*7B +$GPGLL,2734.78726,S,15306.02769,E,222702.000,A,A*46 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.0*31 +$GPGST,222702.000,14.7,49.5,20.8,84.3,45.1,19.5,72.8*5F +$GPGSV,3,1,11,16,48,116,36,25,40,267,18,23,58,174,40,20,71,337,31*71 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,20,27,20,283,*71 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,28*43 +$GPRMC,222702.000,A,2734.78726,S,15306.02769,E,4.2,246.4,030308,11.2,W,A*30 +$GPVTG,246.4,T,257.6,M,4.2,N,7.7,K,A*27 +$GPGGA,222703.000,2734.78745,S,15306.02617,E,1,05,1.6,45.3,M,39.2,M,,*77 +$GPGLL,2734.78745,S,15306.02617,E,222703.000,A,A*4A +$GPGSA,A,3,16,23,20,13,03,,,,,,,,2.5,1.6,1.9*3F +$GPGST,222703.000,12.3,16.0,23.0,21.5,20.3,15.6,33.1*53 +$GPGSV,3,1,11,16,48,116,31,25,40,267,18,23,58,174,29,20,70,337,23*7B +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,22,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,27*4C +$GPRMC,222703.000,A,2734.78745,S,15306.02617,E,4.9,265.5,030308,11.2,W,A*37 +$GPVTG,265.5,T,276.7,M,4.9,N,9.1,K,A*26 +$GPGGA,222704.000,2734.78730,S,15306.02555,E,1,04,3.5,45.3,M,39.2,M,,*77 +$GPGLL,2734.78730,S,15306.02555,E,222704.000,A,A*4A +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.1*30 +$GPGST,222704.000,9.5,34.0,22.1,80.5,30.8,20.6,55.7*69 +$GPGSV,3,1,11,16,48,116,31,25,40,267,18,23,58,174,29,20,70,337,23*7B +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,22,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,31*4B +$GPRMC,222704.000,A,2734.78730,S,15306.02555,E,2.1,286.5,030308,11.2,W,A*34 +$GPVTG,286.5,T,297.7,M,2.1,N,3.9,K,A*28 +$GPGGA,222705.000,2734.78698,S,15306.02452,E,1,04,3.5,45.3,M,39.2,M,,*73 +$GPGLL,2734.78698,S,15306.02452,E,222705.000,A,A*4E +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.1*30 +$GPGST,222705.000,93.8,65.1,31.9,84.0,59.3,29.7,94.1*5F +$GPGSV,3,1,11,16,48,116,29,25,40,267,18,23,58,174,29,20,70,337,23*72 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,22,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,30*4A +$GPRMC,222705.000,A,2734.78698,S,15306.02452,E,3.6,290.3,030308,11.2,W,A*37 +$GPVTG,290.3,T,301.5,M,3.6,N,6.8,K,A*27 +$GPGGA,222706.000,2734.78686,S,15306.02339,E,1,04,3.5,45.3,M,39.2,M,,*75 +$GPGLL,2734.78686,S,15306.02339,E,222706.000,A,A*48 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.1*30 +$GPGST,222706.000,114.3,62.0,48.3,70.3,55.4,45.7,116.3*5B +$GPGSV,3,1,11,16,48,116,24,25,40,267,18,23,58,174,29,20,70,337,23*7F +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,22,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,24*4F +$GPRMC,222706.000,A,2734.78686,S,15306.02339,E,3.4,277.7,030308,11.2,W,A*3E +$GPVTG,277.7,T,288.9,M,3.4,N,6.3,K,A*2F +$GPGGA,222707.000,2734.78679,S,15306.02251,E,1,04,3.5,45.3,M,39.2,M,,*7B +$GPGLL,2734.78679,S,15306.02251,E,222707.000,A,A*46 +$GPGSA,A,3,16,23,20,03,,,,,,,,,6.1,3.5,5.1*30 +$GPGST,222707.000,128.0,90.0,67.0,53.0,75.4,69.6,158.6*55 +$GPGSV,3,1,11,16,48,116,24,25,40,267,18,23,58,174,29,20,70,337,23*7F +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,223,22,27,20,283,*73 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,24*4F +$GPRMC,222707.000,A,2734.78679,S,15306.02251,E,,,030308,,,A*79 +$GPVTG,,T,,M,,N,,K,N*2C +$GPGGA,222708.000,2734.78673,S,15306.02181,E,1,05,1.6,45.3,M,39.2,M,,*70 +$GPGLL,2734.78673,S,15306.02181,E,222708.000,A,A*4D +$GPGSA,A,3,16,23,20,13,03,,,,,,,,2.5,1.6,1.9*3F +$GPGST,222708.000,66.8,93.0,70.5,62.6,69.3,81.1,141.7*6F +$GPGSV,3,1,11,16,48,116,24,25,40,267,,23,58,174,29,20,70,337,23*76 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,222,22,27,20,283,*72 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,24*4F +$GPRMC,222708.000,A,2734.78673,S,15306.02181,E,,,030308,,,A*72 +$GPVTG,,T,,M,,N,,K,N*2C +$GPGGA,222709.000,2734.78668,S,15306.02124,E,1,05,1.6,45.3,M,39.2,M,,*74 +$GPGLL,2734.78668,S,15306.02124,E,222709.000,A,A*49 +$GPGSA,A,2,16,23,20,13,03,,,,,,,,1.8,1.6,0.9*31 +$GPGST,222709.000,89.5,61.2,117.7,37.9,91.7,79.5,4.3*54 +$GPGSV,3,1,11,16,48,116,24,25,40,267,,23,58,174,29,20,70,337,23*76 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,222,22,27,20,283,*72 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,24*4F +$GPRMC,222709.000,A,2734.78668,S,15306.02124,E,,,030308,,,A*76 +$GPVTG,,T,,M,,N,,K,N*2C +$GPGGA,222710.000,2734.78664,S,15306.02080,E,1,05,1.6,45.3,M,39.2,M,,*7F +$GPGLL,2734.78664,S,15306.02080,E,222710.000,A,A*42 +$GPGSA,A,2,16,23,20,13,03,,,,,,,,1.8,1.6,0.9*31 +$GPGST,222710.000,117.0,82.3,157.6,39.0,121.7,107.9,3.9*62 +$GPGSV,3,1,11,16,48,116,24,25,40,267,,23,58,174,29,20,70,337,23*76 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,222,22,27,20,283,*72 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,24*4F +$GPRMC,222710.000,A,2734.78664,S,15306.02080,E,,,030308,,,A*7D +$GPVTG,,T,,M,,N,,K,N*2C +$GPGGA,222711.000,2734.78660,S,15306.02044,E,1,05,1.6,45.3,M,39.2,M,,*72 +$GPGLL,2734.78660,S,15306.02044,E,222711.000,A,A*4F +$GPGSA,A,2,16,23,20,13,03,,,,,,,,1.8,1.6,0.9*31 +$GPGST,222711.000,128.0,107.6,205.2,40.2,156.7,142.6,3.5*58 +$GPGSV,3,1,11,16,48,116,24,25,40,267,,23,58,174,29,20,70,337,23*76 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,222,22,27,20,283,*72 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,24*4F +$GPRMC,222711.000,A,2734.78660,S,15306.02044,E,,,030308,,,A*70 +$GPVTG,,T,,M,,N,,K,N*2C +$GPGGA,222712.000,2734.78658,S,15306.02015,E,1,05,1.6,45.3,M,39.2,M,,*7E +$GPGLL,2734.78658,S,15306.02015,E,222712.000,A,A*43 +$GPGSA,A,2,16,23,20,13,03,,,,,,,,1.8,1.6,0.9*31 +$GPGST,222712.000,128.0,137.2,261.1,41.4,197.4,183.9,3.2*51 +$GPGSV,3,1,11,16,48,116,24,25,40,267,,23,58,173,29,20,70,337,23*71 +$GPGSV,3,2,11,19,03,029,,04,06,242,,13,31,222,22,27,20,283,*72 +$GPGSV,3,3,11,11,06,338,,08,00,298,,03,14,056,24*4F +$GPRMC,222712.000,A,2734.78658,S,15306.02015,E,,,030308,,,A*7C \ No newline at end of file diff --git a/examples/positioning/geoflickr/geoflickr.pro b/examples/positioning/geoflickr/geoflickr.pro new file mode 100644 index 0000000..4bbb795 --- /dev/null +++ b/examples/positioning/geoflickr/geoflickr.pro @@ -0,0 +1,15 @@ +TEMPLATE = app +TARGET = geoflickr + +QT += qml quick network positioning +SOURCES += qmllocationflickr.cpp + +RESOURCES += \ + flickr.qrc + +OTHER_FILES += flickr.qml \ + flickrcommon/* \ + flickrmobile/* + +target.path = $$[QT_INSTALL_EXAMPLES]/positioning/geoflickr +INSTALLS += target diff --git a/examples/positioning/geoflickr/geoflickr.qmlproject b/examples/positioning/geoflickr/geoflickr.qmlproject new file mode 100644 index 0000000..d4909f8 --- /dev/null +++ b/examples/positioning/geoflickr/geoflickr.qmlproject @@ -0,0 +1,16 @@ +import QmlProject 1.0 + +Project { + /* Include .qml, .js, and image files from current directory and subdirectories */ + QmlFiles { + directory: "." + } + JavaScriptFiles { + directory: "." + } + ImageFiles { + directory: "." + } + /* List of plugin directories passed to QML runtime */ + // importPaths: [ " ../exampleplugin " ] +} diff --git a/examples/positioning/geoflickr/qmllocationflickr.cpp b/examples/positioning/geoflickr/qmllocationflickr.cpp new file mode 100644 index 0000000..2ad9ce6 --- /dev/null +++ b/examples/positioning/geoflickr/qmllocationflickr.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication application(argc, argv); + const QString mainQmlApp = QStringLiteral("qrc:///flickr.qml"); + QQuickView view; + view.setSource(QUrl(mainQmlApp)); + view.setResizeMode(QQuickView::SizeRootObjectToView); + // Qt.quit() called in embedded .qml by default only emits + // quit() signal, so do this (optionally use Qt.exit()). + QObject::connect(view.engine(), SIGNAL(quit()), qApp, SLOT(quit())); + view.setGeometry(QRect(100, 100, 360, 640)); + view.show(); + return application.exec(); +} diff --git a/examples/positioning/logfilepositionsource/clientapplication.cpp b/examples/positioning/logfilepositionsource/clientapplication.cpp new file mode 100644 index 0000000..a37ee00 --- /dev/null +++ b/examples/positioning/logfilepositionsource/clientapplication.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include +#include + +#include "logfilepositionsource.h" +#include "clientapplication.h" + +ClientApplication::ClientApplication(QWidget *parent) + : QMainWindow(parent) +{ + textEdit = new QTextEdit; + setCentralWidget(textEdit); + + LogFilePositionSource *source = new LogFilePositionSource(this); + connect(source, SIGNAL(positionUpdated(QGeoPositionInfo)), + this, SLOT(positionUpdated(QGeoPositionInfo))); + + source->startUpdates(); +} + +void ClientApplication::positionUpdated(const QGeoPositionInfo &info) +{ + textEdit->append(QString("Position updated: Date/time = %1, Coordinate = %2").arg(info.timestamp().toString()).arg(info.coordinate().toString())); +} diff --git a/examples/positioning/logfilepositionsource/clientapplication.h b/examples/positioning/logfilepositionsource/clientapplication.h new file mode 100644 index 0000000..0e67c53 --- /dev/null +++ b/examples/positioning/logfilepositionsource/clientapplication.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef CLIENTAPPLICATION_H +#define CLIENTAPPLICATION_H + +#include + +QT_BEGIN_NAMESPACE +class QGeoPositionInfo; +class QTextEdit; +QT_END_NAMESPACE + +class ClientApplication : public QMainWindow +{ + Q_OBJECT +public: + ClientApplication(QWidget *parent = 0); + +private slots: + void positionUpdated(const QGeoPositionInfo &info); + +private: + QTextEdit *textEdit; +}; + + +#endif diff --git a/examples/positioning/logfilepositionsource/doc/src/logfilepositionsource.qdoc b/examples/positioning/logfilepositionsource/doc/src/logfilepositionsource.qdoc new file mode 100644 index 0000000..6b008af --- /dev/null +++ b/examples/positioning/logfilepositionsource/doc/src/logfilepositionsource.qdoc @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\example logfilepositionsource +\title Log File Position Source (C++) +\ingroup qtpositioning-examples + +\brief The Logfile Position Source shows how to create and work with a custom NMEA position source, + for platforms without GPS. + +The data is read from a file which has positional data in NMEA format. The resulting time and +position information is then displayed to the screen as simple text in date/time and +latitude/longitude format. + +This example class reads position data from a text file, \e log.txt. The file specifies position +data using a simple text format: it contains one position update per line, where each line contains +a date/time, a latitude and a longitude, separated by spaces. The date/time is in ISO 8601 format +and the latitude and longitude are in degrees decimal format. Here is an excerpt from \e log.txt: + +\code +2009-08-24T22:25:01 -27.576082 153.092415 +2009-08-24T22:25:02 -27.576223 153.092530 +2009-08-24T22:25:03 -27.576364 153.092648 +\endcode + +The class reads this data and distributes it via the +\l{QGeoPositionInfoSource::positionUpdated()}{positionUpdated()} signal. + +Here is the definition of the \c LogFilePositionSource class: + +\quotefromfile logfilepositionsource/logfilepositionsource.h +\skipto class LogFilePositionSource +\printuntil }; + +The main methods overrided by the subclass are: + +\list + \li \l{QGeoPositionInfoSource::startUpdates()}{startUpdates()}: called by client applications + to start regular position updates. + \li \l{QGeoPositionInfoSource::stopUpdates()}{stopUpdates()}: called by client applications to + stop regular position updates. + \li \l{QGeoPositionInfoSource::requestUpdate()}{requestUpdate()}: called by client applications + to request a single update, with a specified timeout. +\endlist + +When a position update is available, the subclass emits the +\l{QGeoPositionInfoSource::positionUpdated()}{positionUpdated()} signal. + +Here are the key methods in the class implementation: + +\quotefromfile logfilepositionsource/logfilepositionsource.cpp +\skipto LogFilePositionSource::LogFilePositionSource +\printuntil /^\}/ +\skipto LogFilePositionSource::startUpdates +\printuntil /^\}/ +\skipto LogFilePositionSource::stopUpdates +\printuntil /^\}/ +\skipto LogFilePositionSource::requestUpdate +\printuntil /^\}/ +\printuntil LogFilePositionSource::readNextPosition +\printuntil /^\}/ +*/ diff --git a/examples/positioning/logfilepositionsource/logfile.qrc b/examples/positioning/logfilepositionsource/logfile.qrc new file mode 100644 index 0000000..6121394 --- /dev/null +++ b/examples/positioning/logfilepositionsource/logfile.qrc @@ -0,0 +1,5 @@ + + + simplelog.txt + + diff --git a/examples/positioning/logfilepositionsource/logfilepositionsource.cpp b/examples/positioning/logfilepositionsource/logfilepositionsource.cpp new file mode 100644 index 0000000..d299a66 --- /dev/null +++ b/examples/positioning/logfilepositionsource/logfilepositionsource.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include + +#include "logfilepositionsource.h" + +LogFilePositionSource::LogFilePositionSource(QObject *parent) + : QGeoPositionInfoSource(parent), + logFile(new QFile(this)), + timer(new QTimer(this)) +{ + connect(timer, SIGNAL(timeout()), this, SLOT(readNextPosition())); + + logFile->setFileName(":/simplelog.txt"); + if (!logFile->open(QIODevice::ReadOnly)) + qWarning() << "Error: cannot open source file" << logFile->fileName(); +} + +QGeoPositionInfo LogFilePositionSource::lastKnownPosition(bool /*fromSatellitePositioningMethodsOnly*/) const +{ + return lastPosition; +} + +LogFilePositionSource::PositioningMethods LogFilePositionSource::supportedPositioningMethods() const +{ + return AllPositioningMethods; +} + +int LogFilePositionSource::minimumUpdateInterval() const +{ + return 500; +} + +void LogFilePositionSource::startUpdates() +{ + int interval = updateInterval(); + if (interval < minimumUpdateInterval()) + interval = minimumUpdateInterval(); + + timer->start(interval); +} + +void LogFilePositionSource::stopUpdates() +{ + timer->stop(); +} + +void LogFilePositionSource::requestUpdate(int /*timeout*/) +{ + // For simplicity, ignore timeout - assume that if data is not available + // now, no data will be added to the file later + if (logFile->canReadLine()) + readNextPosition(); + else + emit updateTimeout(); +} + +void LogFilePositionSource::readNextPosition() +{ + QByteArray line = logFile->readLine().trimmed(); + if (!line.isEmpty()) { + QList data = line.split(' '); + double latitude; + double longitude; + bool hasLatitude = false; + bool hasLongitude = false; + QDateTime timestamp = QDateTime::fromString(QString(data.value(0)), Qt::ISODate); + latitude = data.value(1).toDouble(&hasLatitude); + longitude = data.value(2).toDouble(&hasLongitude); + + if (hasLatitude && hasLongitude && timestamp.isValid()) { + QGeoCoordinate coordinate(latitude, longitude); + QGeoPositionInfo info(coordinate, timestamp); + if (info.isValid()) { + lastPosition = info; + emit positionUpdated(info); + } + } + } +} + +QGeoPositionInfoSource::Error LogFilePositionSource::error() const +{ + return QGeoPositionInfoSource::NoError; +} diff --git a/examples/positioning/logfilepositionsource/logfilepositionsource.h b/examples/positioning/logfilepositionsource/logfilepositionsource.h new file mode 100644 index 0000000..099531f --- /dev/null +++ b/examples/positioning/logfilepositionsource/logfilepositionsource.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef LOGFILEPOSITIONSOURCE_H +#define LOGFILEPOSITIONSOURCE_H + +#include + +QT_BEGIN_NAMESPACE +class QFile; +class QTimer; +QT_END_NAMESPACE + +class LogFilePositionSource : public QGeoPositionInfoSource +{ + Q_OBJECT +public: + LogFilePositionSource(QObject *parent = 0); + + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const; + + PositioningMethods supportedPositioningMethods() const; + int minimumUpdateInterval() const; + Error error() const; + +public slots: + virtual void startUpdates(); + virtual void stopUpdates(); + + virtual void requestUpdate(int timeout = 5000); + +private slots: + void readNextPosition(); + +private: + QFile *logFile; + QTimer *timer; + QGeoPositionInfo lastPosition; +}; + +#endif diff --git a/examples/positioning/logfilepositionsource/logfilepositionsource.pro b/examples/positioning/logfilepositionsource/logfilepositionsource.pro new file mode 100644 index 0000000..cb9e30a --- /dev/null +++ b/examples/positioning/logfilepositionsource/logfilepositionsource.pro @@ -0,0 +1,16 @@ +TEMPLATE = app +TARGET = logfilepositionsource +QT = positioning core widgets + + +HEADERS = logfilepositionsource.h \ + clientapplication.h +SOURCES = logfilepositionsource.cpp \ + clientapplication.cpp \ + main.cpp + +RESOURCES += \ + logfile.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/positioning/logfilepositionsource +INSTALLS += target diff --git a/examples/positioning/logfilepositionsource/main.cpp b/examples/positioning/logfilepositionsource/main.cpp new file mode 100644 index 0000000..03338cf --- /dev/null +++ b/examples/positioning/logfilepositionsource/main.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include + +#include "clientapplication.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + ClientApplication client; + client.show(); + + return app.exec(); +} diff --git a/examples/positioning/logfilepositionsource/simplelog.txt b/examples/positioning/logfilepositionsource/simplelog.txt new file mode 100644 index 0000000..d016b2a --- /dev/null +++ b/examples/positioning/logfilepositionsource/simplelog.txt @@ -0,0 +1,156 @@ +2009-08-24T22:24:37 -27.572321 153.090718 +2009-08-24T22:24:38 -27.572470 153.090783 +2009-08-24T22:24:39 -27.572616 153.090845 +2009-08-24T22:24:40 -27.572763 153.090908 +2009-08-24T22:24:41 -27.572914 153.090971 +2009-08-24T22:24:42 -27.573068 153.091036 +2009-08-24T22:24:43 -27.573224 153.091102 +2009-08-24T22:24:44 -27.573383 153.091167 +2009-08-24T22:24:45 -27.573541 153.091232 +2009-08-24T22:24:46 -27.573700 153.091298 +2009-08-24T22:24:47 -27.573860 153.091366 +2009-08-24T22:24:48 -27.574019 153.091435 +2009-08-24T22:24:49 -27.574178 153.091507 +2009-08-24T22:24:50 -27.574340 153.091581 +2009-08-24T22:24:51 -27.574506 153.091654 +2009-08-24T22:24:52 -27.574670 153.091729 +2009-08-24T22:24:53 -27.574836 153.091800 +2009-08-24T22:24:54 -27.575001 153.091870 +2009-08-24T22:24:55 -27.575165 153.091940 +2009-08-24T22:24:56 -27.575327 153.092010 +2009-08-24T22:24:57 -27.575485 153.092078 +2009-08-24T22:24:58 -27.575641 153.092144 +2009-08-24T22:24:59 -27.575795 153.092218 +2009-08-24T22:25:00 -27.575942 153.092308 +2009-08-24T22:25:01 -27.576082 153.092415 +2009-08-24T22:25:02 -27.576223 153.092530 +2009-08-24T22:25:03 -27.576364 153.092648 +2009-08-24T22:25:04 -27.576505 153.092763 +2009-08-24T22:25:05 -27.576646 153.092879 +2009-08-24T22:25:06 -27.576784 153.092990 +2009-08-24T22:25:07 -27.576918 153.093099 +2009-08-24T22:25:08 -27.577046 153.093204 +2009-08-24T22:25:09 -27.577168 153.093303 +2009-08-24T22:25:10 -27.577284 153.093396 +2009-08-24T22:25:11 -27.577396 153.093484 +2009-08-24T22:25:12 -27.577499 153.093568 +2009-08-24T22:25:13 -27.577595 153.093647 +2009-08-24T22:25:14 -27.577692 153.093727 +2009-08-24T22:25:15 -27.577793 153.093810 +2009-08-24T22:25:16 -27.577897 153.093896 +2009-08-24T22:25:17 -27.578006 153.093984 +2009-08-24T22:25:18 -27.578120 153.094074 +2009-08-24T22:25:19 -27.578237 153.094168 +2009-08-24T22:25:20 -27.578360 153.094267 +2009-08-24T22:25:21 -27.578487 153.094370 +2009-08-24T22:25:22 -27.578617 153.094474 +2009-08-24T22:25:23 -27.578748 153.094581 +2009-08-24T22:25:24 -27.578880 153.094688 +2009-08-24T22:25:25 -27.579014 153.094796 +2009-08-24T22:25:26 -27.579149 153.094905 +2009-08-24T22:25:27 -27.579285 153.095012 +2009-08-24T22:25:28 -27.579420 153.095121 +2009-08-24T22:25:29 -27.579552 153.095231 +2009-08-24T22:25:30 -27.579684 153.095340 +2009-08-24T22:25:31 -27.579820 153.095449 +2009-08-24T22:25:32 -27.579956 153.095558 +2009-08-24T22:25:33 -27.580095 153.095667 +2009-08-24T22:25:34 -27.580240 153.095776 +2009-08-24T22:25:35 -27.580392 153.095885 +2009-08-24T22:25:36 -27.580536 153.095995 +2009-08-24T22:25:37 -27.580679 153.096109 +2009-08-24T22:25:38 -27.580824 153.096226 +2009-08-24T22:25:39 -27.580965 153.096337 +2009-08-24T22:25:40 -27.581101 153.096441 +2009-08-24T22:25:41 -27.581237 153.096537 +2009-08-24T22:25:42 -27.581356 153.096628 +2009-08-24T22:25:43 -27.581466 153.096714 +2009-08-24T22:25:44 -27.581555 153.096795 +2009-08-24T22:25:45 -27.581606 153.096847 +2009-08-24T22:25:46 -27.581539 153.096855 +2009-08-24T22:25:47 -27.581572 153.096873 +2009-08-24T22:25:48 -27.581579 153.096875 +2009-08-24T22:25:49 -27.581582 153.096878 +2009-08-24T22:25:50 -27.581588 153.096880 +2009-08-24T22:25:51 -27.581588 153.096880 +2009-08-24T22:25:52 -27.581592 153.096881 +2009-08-24T22:25:53 -27.581596 153.096882 +2009-08-24T22:25:54 -27.581599 153.096883 +2009-08-24T22:25:55 -27.581601 153.096883 +2009-08-24T22:25:56 -27.581602 153.096883 +2009-08-24T22:25:57 -27.581606 153.096890 +2009-08-24T22:25:58 -27.581606 153.096919 +2009-08-24T22:25:59 -27.581605 153.096985 +2009-08-24T22:26:00 -27.581573 153.097060 +2009-08-24T22:26:01 -27.581532 153.097142 +2009-08-24T22:26:02 -27.581484 153.097227 +2009-08-24T22:26:03 -27.581426 153.097324 +2009-08-24T22:26:04 -27.581373 153.097424 +2009-08-24T22:26:05 -27.581320 153.097530 +2009-08-24T22:26:06 -27.581265 153.097641 +2009-08-24T22:26:07 -27.581203 153.097757 +2009-08-24T22:26:08 -27.581144 153.097875 +2009-08-24T22:26:09 -27.581093 153.097995 +2009-08-24T22:26:10 -27.581042 153.098116 +2009-08-24T22:26:11 -27.580987 153.098231 +2009-08-24T22:26:12 -27.580919 153.098334 +2009-08-24T22:26:13 -27.580841 153.098428 +2009-08-24T22:26:14 -27.580754 153.098511 +2009-08-24T22:26:15 -27.580667 153.098591 +2009-08-24T22:26:16 -27.580581 153.098671 +2009-08-24T22:26:17 -27.580492 153.098751 +2009-08-24T22:26:18 -27.580401 153.098832 +2009-08-24T22:26:19 -27.580311 153.098914 +2009-08-24T22:26:20 -27.580224 153.098996 +2009-08-24T22:26:21 -27.580140 153.099078 +2009-08-24T22:26:22 -27.580059 153.099159 +2009-08-24T22:26:23 -27.579980 153.099245 +2009-08-24T22:26:24 -27.579900 153.099336 +2009-08-24T22:26:25 -27.579826 153.099433 +2009-08-24T22:26:26 -27.579753 153.099529 +2009-08-24T22:26:27 -27.579680 153.099623 +2009-08-24T22:26:28 -27.579606 153.099716 +2009-08-24T22:26:29 -27.579538 153.099808 +2009-08-24T22:26:30 -27.579477 153.099893 +2009-08-24T22:26:31 -27.579416 153.099968 +2009-08-24T22:26:32 -27.579393 153.100027 +2009-08-24T22:26:33 -27.579376 153.100092 +2009-08-24T22:26:34 -27.579384 153.100142 +2009-08-24T22:26:35 -27.579403 153.100181 +2009-08-24T22:26:36 -27.579429 153.100210 +2009-08-24T22:26:37 -27.579460 153.100237 +2009-08-24T22:26:38 -27.579493 153.100265 +2009-08-24T22:26:39 -27.579518 153.100291 +2009-08-24T22:26:40 -27.579536 153.100336 +2009-08-24T22:26:41 -27.579591 153.100358 +2009-08-24T22:26:42 -27.579618 153.100383 +2009-08-24T22:26:43 -27.579639 153.100404 +2009-08-24T22:26:44 -27.579650 153.100420 +2009-08-24T22:26:45 -27.579665 153.100432 +2009-08-24T22:26:46 -27.579679 153.100442 +2009-08-24T22:26:47 -27.579690 153.100451 +2009-08-24T22:26:48 -27.579697 153.100459 +2009-08-24T22:26:49 -27.579700 153.100462 +2009-08-24T22:26:50 -27.579699 153.100464 +2009-08-24T22:26:51 -27.579699 153.100466 +2009-08-24T22:26:52 -27.579700 153.100466 +2009-08-24T22:26:53 -27.579704 153.100466 +2009-08-24T22:26:54 -27.579705 153.100465 +2009-08-24T22:26:55 -27.579708 153.100468 +2009-08-24T22:26:56 -27.579712 153.100471 +2009-08-24T22:26:57 -27.579719 153.100476 +2009-08-24T22:26:58 -27.579728 153.100485 +2009-08-24T22:26:59 -27.579744 153.100493 +2009-08-24T22:27:00 -27.579759 153.100496 +2009-08-24T22:27:01 -27.579780 153.100481 +2009-08-24T22:27:02 -27.579788 153.100462 +2009-08-24T22:27:03 -27.579791 153.100436 +2009-08-24T22:27:04 -27.579788 153.100426 +2009-08-24T22:27:05 -27.579783 153.100409 +2009-08-24T22:27:06 -27.579781 153.100390 +2009-08-24T22:27:07 -27.579780 153.100375 +2009-08-24T22:27:08 -27.579779 153.100364 +2009-08-24T22:27:09 -27.579778 153.100354 +2009-08-24T22:27:10 -27.579777 153.100347 +2009-08-24T22:27:11 -27.579777 153.100341 +2009-08-24T22:27:12 -27.579776 153.100336 diff --git a/examples/positioning/positioning.pro b/examples/positioning/positioning.pro new file mode 100644 index 0000000..0e436b4 --- /dev/null +++ b/examples/positioning/positioning.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs + +qtHaveModule(widgets): SUBDIRS += logfilepositionsource +qtHaveModule(quick): SUBDIRS += geoflickr weatherinfo satelliteinfo diff --git a/examples/positioning/satelliteinfo/doc/images/example-satelliteinfo.png b/examples/positioning/satelliteinfo/doc/images/example-satelliteinfo.png new file mode 100644 index 0000000000000000000000000000000000000000..aa9a217c232773407fe0bcec4665723d3ed4565d GIT binary patch literal 27371 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 k63DQRexEtLIU8q)dJ~KYEcqim+ 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 KWlN>sef&T0c$BCB literal 0 HcmV?d00001 diff --git a/examples/positioning/satelliteinfo/doc/src/satelliteinfo.qdoc b/examples/positioning/satelliteinfo/doc/src/satelliteinfo.qdoc new file mode 100644 index 0000000..51e4086 --- /dev/null +++ b/examples/positioning/satelliteinfo/doc/src/satelliteinfo.qdoc @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example satelliteinfo + \title SatelliteInfo (C++/QML) + + \brief The SatelliteInfo example shows how the available satellites + at the user's current position and marks the satellites + currently contributing to the GPS fix as pink. + + \ingroup qtpositioning-examples + + Key \l{Qt Positioning} classes used in this example: + + \list + \li \l{QGeoSatelliteInfo} + \li \l{QGeoSatelliteInfoSource} + \endlist + + \image ../images/example-satelliteinfo.png + + The example displays the signal strength of all satellites in view. Any satellite + that is currently used to calculate the GPS fix has been marked pink. The number at + the bottom of each signal bar is the individual satellite identifier. + + The application operates in three different modes: + + \table + \header + \li Application mode + \li Description + \row + \li running + \li The application continuously queries the system for satellite updates. When new data + is available it will be displayed. + \row + \li stopped + \li The application stops updating the satellite information. + \row + \li single + \li The application makes a single update request with a timeout of 10s. The display + remains empty until the request was answered by the system. + \endtable + + If the platform does not provide satellite information the application automatically + switches into a demo mode whereby it continuously switches between predefined + sets of satellite data. + + \include examples-run.qdocinc +*/ diff --git a/examples/positioning/satelliteinfo/main.cpp b/examples/positioning/satelliteinfo/main.cpp new file mode 100644 index 0000000..8d94d01 --- /dev/null +++ b/examples/positioning/satelliteinfo/main.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include "satellitemodel.h" + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + qmlRegisterType("Local", 1, 0, "SatelliteModel"); + + QQuickView view; + view.setSource(QStringLiteral("qrc:///satelliteinfo.qml")); + view.setResizeMode(QQuickView::SizeRootObjectToView); + + QObject::connect(view.engine(), SIGNAL(quit()), qApp, SLOT(quit())); + view.show(); + + return app.exec(); +} + + diff --git a/examples/positioning/satelliteinfo/satelliteinfo.pro b/examples/positioning/satelliteinfo/satelliteinfo.pro new file mode 100644 index 0000000..4aef9d0 --- /dev/null +++ b/examples/positioning/satelliteinfo/satelliteinfo.pro @@ -0,0 +1,22 @@ +TEMPLATE = app +TARGET = satelliteinfo + +QT += quick positioning + +SOURCES += main.cpp \ + satellitemodel.cpp + +HEADERS += \ + satellitemodel.h + +OTHER_FILES += \ + satelliteinfo.qml + +RESOURCES += \ + satelliteinfo.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/positioning/satelliteinfo +INSTALLS += target + + + diff --git a/examples/positioning/satelliteinfo/satelliteinfo.qml b/examples/positioning/satelliteinfo/satelliteinfo.qml new file mode 100644 index 0000000..185b21f --- /dev/null +++ b/examples/positioning/satelliteinfo/satelliteinfo.qml @@ -0,0 +1,304 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Local 1.0 + +Rectangle { + id: page + width: 360 + height: 360 + + SatelliteModel { + id: satelliteModel + running: true + onErrorFound: errorLabel.text = qsTr("Last Error: %1", "%1=error number").arg(code) + } + + Item { + id: header + height: column.height + 30 + width: parent.width + state: "running" + + anchors.top: parent.top + + function toggle() + { + switch (header.state) { + case "single": header.state = "running"; break; + default: + case "running": header.state = "stopped"; break; + case "stopped": header.state = "single"; break; + } + } + + function enterSingle() + { + satelliteModel.singleRequestMode = true; + satelliteModel.running = true; + } + + function enterRunning() + { + satelliteModel.running = false; + satelliteModel.singleRequestMode = false; + satelliteModel.running = true; + } + + states: [ + State { + name: "stopped" + PropertyChanges { target: startStop; bText: qsTr("Single") } + PropertyChanges { + target: modeLabel; text: qsTr("Current Mode: stopped") + } + PropertyChanges { target: satelliteModel; running: false; } + }, + State { + name: "single" + PropertyChanges { target: startStop; bText: qsTr("Start") } + PropertyChanges { + target: modeLabel; text: qsTr("Current Mode: single") + } + StateChangeScript { script: header.enterSingle(); } + }, + State { + name: "running" + PropertyChanges { target: startStop; bText: qsTr("Stop") } + PropertyChanges { + target: modeLabel; text: qsTr("Current Mode: running") + } + StateChangeScript { script: header.enterRunning(); } + } + ] + + Column { + id: column + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.margins: 7 + Text { + id: overview + text: satelliteModel.satelliteInfoAvailable + ? qsTr("Known Satellites") + : qsTr("Known Satellites (Demo Mode)") + font.pointSize: 12 + } + + Text { + id: modeLabel + font.pointSize: 12 + } + + Text { + id: errorLabel + text: qsTr("Last Error: None") + font.pointSize: 12 + } + } + Rectangle { + id: startStop + border.color: "black" + border.width: 3 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 7 + radius: 10 + height: maxField.height*1.4 + width: maxField.width*1.4 + + property string bText: qsTr("Stop"); + + Text { //need this for sizing + id: maxField + text: qsTr("Single") + font.pointSize: 13 + opacity: 0 + } + + Text { + id: buttonText + anchors.centerIn: parent + text: startStop.bText + font.pointSize: 13 + } + + MouseArea { + anchors.fill: parent + onPressed: { startStop.color = "lightGray" } + onClicked: { header.toggle() } + onReleased: { startStop.color = "white" } + } + } + } + + Rectangle { + anchors.top: header.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: rect.myMargin + border.width: 3 + radius: 10 + border.color: "black" + + Item { + id: rect + anchors.fill: parent + anchors.margins: myMargin + property int myMargin: 7 + + Row { + id: view + property int rows: repeater.model.entryCount + property int singleWidth: ((rect.width - scale.width)/rows )-rect.myMargin + spacing: rect.myMargin + + Rectangle { + id: scale + width: strengthLabel.width+10 + height: rect.height + color: "#32cd32" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: lawngreenRect.top + font.pointSize: 11 + text: "50" + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font.pointSize: 11 + text: "100" + } + + Rectangle { + id: redRect + width: parent.width + color: "red" + height: parent.height*10/100 + anchors.bottom: parent.bottom + Text { + id: strengthLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "00" + } + } + Rectangle { + id: orangeRect + height: parent.height*10/100 + anchors.bottom: redRect.top + width: parent.width + color: "#ffa500" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "10" + } + } + Rectangle { + id: goldRect + height: parent.height*10/100 + anchors.bottom: orangeRect.top + width: parent.width + color: "#ffd700" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "20" + } + } + Rectangle { + id: yellowRect + height: parent.height*10/100 + anchors.bottom: goldRect.top + width: parent.width + color: "yellow" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "30" + } + } + Rectangle { + id: lawngreenRect + height: parent.height*10/100 + anchors.bottom: yellowRect.top + width: parent.width + color: "#7cFc00" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "40" + } + } + } + + Repeater { + id: repeater + model: satelliteModel + delegate: Rectangle { + height: rect.height + width: view.singleWidth + Rectangle { + anchors.bottom: parent.bottom + width: parent.width + height: parent.height*signalStrength/100 + color: isInUse ? "#7FFF0000" : "#7F0000FF" + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + text: satelliteIdentifier + } + } + } + } + } + } +} + diff --git a/examples/positioning/satelliteinfo/satelliteinfo.qrc b/examples/positioning/satelliteinfo/satelliteinfo.qrc new file mode 100644 index 0000000..8745f87 --- /dev/null +++ b/examples/positioning/satelliteinfo/satelliteinfo.qrc @@ -0,0 +1,5 @@ + + + satelliteinfo.qml + + diff --git a/examples/positioning/satelliteinfo/satellitemodel.cpp b/examples/positioning/satelliteinfo/satellitemodel.cpp new file mode 100644 index 0000000..d1ae514 --- /dev/null +++ b/examples/positioning/satelliteinfo/satellitemodel.cpp @@ -0,0 +1,309 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "satellitemodel.h" +#include +#include + +SatelliteModel::SatelliteModel(QObject *parent) : + QAbstractListModel(parent), source(0), m_componentCompleted(false), m_running(false), + m_runningRequested(false), demo(false), isSingle(false), singleRequestServed(false) +{ + source = QGeoSatelliteInfoSource::createDefaultSource(this); + if (!demo && !source) { + qWarning() << "No satellite data source found. Changing to demo mode."; + demo = true; + } + if (!demo) { + source->setUpdateInterval(3000); + connect(source, SIGNAL(satellitesInViewUpdated(QList)), + this, SLOT(satellitesInViewUpdated(QList))); + connect(source, SIGNAL(satellitesInUseUpdated(QList)), + this, SLOT(satellitesInUseUpdated(QList))); + connect(source, SIGNAL(error(QGeoSatelliteInfoSource::Error)), + this, SLOT(error(QGeoSatelliteInfoSource::Error))); + } + + if (demo) { + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(updateDemoData())); + timer->start(3000); + } +} + +int SatelliteModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + if (!source && !demo) + return 0; + + return knownSatellites.count(); +} + +QVariant SatelliteModel::data(const QModelIndex &index, int role) const +{ + if (!demo && !source) + return QVariant(); + + if (!index.isValid() || index.row() < 0) + return QVariant(); + + if (index.row() >= knownSatellites.count()) { + qWarning() << "SatelliteModel: Index out of bound"; + return QVariant(); + } + + const QGeoSatelliteInfo &info = knownSatellites.at(index.row()); + switch (role) { + case IdentifierRole: + return info.satelliteIdentifier(); + case InUseRole: + return satellitesInUse.contains(info.satelliteIdentifier()); + case SignalStrengthRole: + return info.signalStrength(); + case ElevationRole: + if (!info.hasAttribute(QGeoSatelliteInfo::Elevation)) + return QVariant(); + return info.attribute(QGeoSatelliteInfo::Elevation); + case AzimuthRole: + if (!info.hasAttribute(QGeoSatelliteInfo::Azimuth)) + return QVariant(); + return info.attribute(QGeoSatelliteInfo::Azimuth); + default: + break; + + } + + return QVariant(); +} + +QHash SatelliteModel::roleNames() const +{ + QHash roleNames; + roleNames.insert(IdentifierRole, "satelliteIdentifier"); + roleNames.insert(InUseRole, "isInUse"); + roleNames.insert(SignalStrengthRole, "signalStrength"); + roleNames.insert(ElevationRole, "elevation"); + roleNames.insert(AzimuthRole, "azimuth"); + return roleNames; +} + +void SatelliteModel::componentComplete() +{ + m_componentCompleted = true; + if (m_runningRequested) + setRunning(true); +} + +bool SatelliteModel::running() const +{ + return m_running; +} + +bool SatelliteModel::isSingleRequest() const +{ + return isSingle; +} + +void SatelliteModel::setSingleRequest(bool single) +{ + if (running()) { + qWarning() << "Cannot change single request mode while running"; + return; + } + + if (single != isSingle) { //flag changed + isSingle = single; + emit singleRequestChanged(); + } +} + +void SatelliteModel::setRunning(bool isActive) +{ + if (!source && !demo) + return; + + if (!m_componentCompleted) { + m_runningRequested = isActive; + return; + } + + if (m_running == isActive) + return; + + m_running = isActive; + + if (m_running) { + clearModel(); + if (demo) + timer->start(2000); + else if (isSingleRequest()) + source->requestUpdate(10000); + else + source->startUpdates(); + + if (demo) + singleRequestServed = false; + } else { + if (demo) + timer->stop(); + else if (!isSingleRequest()) + source->stopUpdates(); + } + + + Q_EMIT runningChanged(); +} + +int SatelliteModel::entryCount() const +{ + return knownSatellites.count(); +} + +bool SatelliteModel::canProvideSatelliteInfo() const +{ + return !demo; +} + +void SatelliteModel::clearModel() +{ + beginResetModel(); + knownSatelliteIds.clear(); + knownSatellites.clear(); + satellitesInUse.clear(); + endResetModel(); +} + +void SatelliteModel::updateDemoData() +{ + static bool flag = true; + QList satellites; + if (flag) { + for (int i = 0; i<5; i++) { + QGeoSatelliteInfo info; + info.setSatelliteIdentifier(i); + info.setSignalStrength(20 + 20*i); + satellites.append(info); + } + } else { + for (int i = 0; i<9; i++) { + QGeoSatelliteInfo info; + info.setSatelliteIdentifier(i*2); + info.setSignalStrength(20 + 10*i); + satellites.append(info); + } + } + + + satellitesInViewUpdated(satellites); + flag ? satellitesInUseUpdated(QList() << satellites.at(2)) + : satellitesInUseUpdated(QList() << satellites.at(3)); + flag = !flag; + + emit errorFound(flag); + + if (isSingleRequest() && !singleRequestServed) { + singleRequestServed = true; + setRunning(false); + } +} + +void SatelliteModel::error(QGeoSatelliteInfoSource::Error error) +{ + emit errorFound((int)error); +} + +QT_BEGIN_NAMESPACE +inline bool operator<(const QGeoSatelliteInfo& a, const QGeoSatelliteInfo& b) +{ + return a.satelliteIdentifier() < b.satelliteIdentifier(); +} +QT_END_NAMESPACE + +void SatelliteModel::satellitesInViewUpdated(const QList &infos) +{ + if (!running()) + return; + + int oldEntryCount = knownSatellites.count(); + + + QSet satelliteIdsInUpdate; + foreach (const QGeoSatelliteInfo &info, infos) + satelliteIdsInUpdate.insert(info.satelliteIdentifier()); + + QSet toBeRemoved = knownSatelliteIds - satelliteIdsInUpdate; + + //We reset the model as in reality just about all entry values change + //and there are generally a lot of inserts and removals each time + //Hence we don't bother with complex model update logic beyond resetModel() + beginResetModel(); + + knownSatellites = infos; + + //sort them for presentation purposes + std::sort(knownSatellites.begin(), knownSatellites.end()); + + //remove old "InUse" data + //new satellites are by default not in "InUse" + //existing satellites keep their "inUse" state + satellitesInUse -= toBeRemoved; + + knownSatelliteIds = satelliteIdsInUpdate; + endResetModel(); + + if (oldEntryCount != knownSatellites.count()) + emit entryCountChanged(); +} + +void SatelliteModel::satellitesInUseUpdated(const QList &infos) +{ + if (!running()) + return; + + beginResetModel(); + + satellitesInUse.clear(); + foreach (const QGeoSatelliteInfo &info, infos) + satellitesInUse.insert(info.satelliteIdentifier()); + + endResetModel(); +} + diff --git a/examples/positioning/satelliteinfo/satellitemodel.h b/examples/positioning/satelliteinfo/satellitemodel.h new file mode 100644 index 0000000..5890cb2 --- /dev/null +++ b/examples/positioning/satelliteinfo/satellitemodel.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SATELLITEMODEL_H +#define SATELLITEMODEL_H + +#include +#include +#include +#include +#include + +QT_FORWARD_DECLARE_CLASS(QTimer) +QT_FORWARD_DECLARE_CLASS(QGeoSatelliteInfoSource) + +class SatelliteModel : public QAbstractListModel, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged) + Q_PROPERTY(bool satelliteInfoAvailable READ canProvideSatelliteInfo NOTIFY canProvideSatelliteInfoChanged) + Q_PROPERTY(int entryCount READ entryCount NOTIFY entryCountChanged) + Q_PROPERTY(bool singleRequestMode READ isSingleRequest WRITE setSingleRequest NOTIFY singleRequestChanged) + Q_INTERFACES(QQmlParserStatus) +public: + explicit SatelliteModel(QObject *parent = 0); + + enum { + IdentifierRole = Qt::UserRole + 1, + InUseRole, + SignalStrengthRole, + ElevationRole, + AzimuthRole + }; + + //From QAbstractListModel + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QHash roleNames() const; + + //From QQmlParserStatus + void classBegin() {} + void componentComplete(); + + bool running() const; + void setRunning(bool isActive); + + bool isSingleRequest() const; + void setSingleRequest(bool single); + + int entryCount() const; + + bool canProvideSatelliteInfo() const; + +signals: + void runningChanged(); + void entryCountChanged(); + void errorFound(int code); + void canProvideSatelliteInfoChanged(); + void singleRequestChanged(); + +public slots: + void clearModel(); + void updateDemoData(); + +private slots: + void error(QGeoSatelliteInfoSource::Error); + void satellitesInViewUpdated(const QList &infos); + void satellitesInUseUpdated(const QList &infos); + +private: + QGeoSatelliteInfoSource *source; + bool m_componentCompleted; + bool m_running; + bool m_runningRequested; + QList knownSatellites; + QSet knownSatelliteIds; + QSet satellitesInUse; + bool demo; + QTimer *timer; + bool isSingle; + bool singleRequestServed; + +}; + +QML_DECLARE_TYPE(SatelliteModel) + +#endif // SATELLITEMODEL_H diff --git a/examples/positioning/weatherinfo/appmodel.cpp b/examples/positioning/weatherinfo/appmodel.cpp new file mode 100644 index 0000000..4d8806b --- /dev/null +++ b/examples/positioning/weatherinfo/appmodel.cpp @@ -0,0 +1,580 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "appmodel.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + *This application uses http://openweathermap.org/api + **/ + +#define ZERO_KELVIN 273.15 + +Q_LOGGING_CATEGORY(requestsLog,"wapp.requests") + +WeatherData::WeatherData(QObject *parent) : + QObject(parent) +{ +} + +WeatherData::WeatherData(const WeatherData &other) : + QObject(0), + m_dayOfWeek(other.m_dayOfWeek), + m_weather(other.m_weather), + m_weatherDescription(other.m_weatherDescription), + m_temperature(other.m_temperature) +{ +} + +QString WeatherData::dayOfWeek() const +{ + return m_dayOfWeek; +} + +/*! + * The icon value is based on OpenWeatherMap.org icon set. For details + * see http://bugs.openweathermap.org/projects/api/wiki/Weather_Condition_Codes + * + * e.g. 01d ->sunny day + * + * The icon string will be translated to + * http://openweathermap.org/img/w/01d.png + */ +QString WeatherData::weatherIcon() const +{ + return m_weather; +} + +QString WeatherData::weatherDescription() const +{ + return m_weatherDescription; +} + +QString WeatherData::temperature() const +{ + return m_temperature; +} + +void WeatherData::setDayOfWeek(const QString &value) +{ + m_dayOfWeek = value; + emit dataChanged(); +} + +void WeatherData::setWeatherIcon(const QString &value) +{ + m_weather = value; + emit dataChanged(); +} + +void WeatherData::setWeatherDescription(const QString &value) +{ + m_weatherDescription = value; + emit dataChanged(); +} + +void WeatherData::setTemperature(const QString &value) +{ + m_temperature = value; + emit dataChanged(); +} + +class AppModelPrivate +{ +public: + static const int baseMsBeforeNewRequest = 5 * 1000; // 5 s, increased after each missing answer up to 10x + QGeoPositionInfoSource *src; + QGeoCoordinate coord; + QString longitude, latitude; + QString city; + QNetworkAccessManager *nam; + QNetworkSession *ns; + WeatherData now; + QList forecast; + QQmlListProperty *fcProp; + QSignalMapper *geoReplyMapper; + QSignalMapper *weatherReplyMapper, *forecastReplyMapper; + bool ready; + bool useGps; + QElapsedTimer throttle; + int nErrors; + int minMsBeforeNewRequest; + QTimer delayedCityRequestTimer; + QTimer requestNewWeatherTimer; + QString app_ident; + + AppModelPrivate() : + src(NULL), + nam(NULL), + ns(NULL), + fcProp(NULL), + ready(false), + useGps(true), + nErrors(0), + minMsBeforeNewRequest(baseMsBeforeNewRequest) + { + delayedCityRequestTimer.setSingleShot(true); + delayedCityRequestTimer.setInterval(1000); // 1 s + requestNewWeatherTimer.setSingleShot(false); + requestNewWeatherTimer.setInterval(20*60*1000); // 20 min + throttle.invalidate(); + app_ident = QStringLiteral("36496bad1955bf3365448965a42b9eac"); + } +}; + +static void forecastAppend(QQmlListProperty *prop, WeatherData *val) +{ + Q_UNUSED(val); + Q_UNUSED(prop); +} + +static WeatherData *forecastAt(QQmlListProperty *prop, int index) +{ + AppModelPrivate *d = static_cast(prop->data); + return d->forecast.at(index); +} + +static int forecastCount(QQmlListProperty *prop) +{ + AppModelPrivate *d = static_cast(prop->data); + return d->forecast.size(); +} + +static void forecastClear(QQmlListProperty *prop) +{ + static_cast(prop->data)->forecast.clear(); +} + +//! [0] +AppModel::AppModel(QObject *parent) : + QObject(parent), + d(new AppModelPrivate) +{ +//! [0] + d->fcProp = new QQmlListProperty(this, d, + forecastAppend, + forecastCount, + forecastAt, + forecastClear); + + d->geoReplyMapper = new QSignalMapper(this); + d->weatherReplyMapper = new QSignalMapper(this); + d->forecastReplyMapper = new QSignalMapper(this); + + connect(d->geoReplyMapper, SIGNAL(mapped(QObject*)), + this, SLOT(handleGeoNetworkData(QObject*))); + connect(d->weatherReplyMapper, SIGNAL(mapped(QObject*)), + this, SLOT(handleWeatherNetworkData(QObject*))); + connect(d->forecastReplyMapper, SIGNAL(mapped(QObject*)), + this, SLOT(handleForecastNetworkData(QObject*))); + connect(&d->delayedCityRequestTimer, SIGNAL(timeout()), + this, SLOT(queryCity())); + connect(&d->requestNewWeatherTimer, SIGNAL(timeout()), + this, SLOT(refreshWeather())); + d->requestNewWeatherTimer.start(); + + +//! [1] + // make sure we have an active network session + d->nam = new QNetworkAccessManager(this); + + QNetworkConfigurationManager ncm; + d->ns = new QNetworkSession(ncm.defaultConfiguration(), this); + connect(d->ns, SIGNAL(opened()), this, SLOT(networkSessionOpened())); + // the session may be already open. if it is, run the slot directly + if (d->ns->isOpen()) + this->networkSessionOpened(); + // tell the system we want network + d->ns->open(); +} +//! [1] + +AppModel::~AppModel() +{ + d->ns->close(); + if (d->src) + d->src->stopUpdates(); + delete d; +} + +//! [2] +void AppModel::networkSessionOpened() +{ + d->src = QGeoPositionInfoSource::createDefaultSource(this); + + if (d->src) { + d->useGps = true; + connect(d->src, SIGNAL(positionUpdated(QGeoPositionInfo)), + this, SLOT(positionUpdated(QGeoPositionInfo))); + connect(d->src, SIGNAL(error(QGeoPositionInfoSource::Error)), + this, SLOT(positionError(QGeoPositionInfoSource::Error))); + d->src->startUpdates(); + } else { + d->useGps = false; + d->city = "Brisbane"; + emit cityChanged(); + this->refreshWeather(); + } +} +//! [2] + +//! [3] +void AppModel::positionUpdated(QGeoPositionInfo gpsPos) +{ + d->coord = gpsPos.coordinate(); + + if (!(d->useGps)) + return; + + queryCity(); +} +//! [3] + +void AppModel::queryCity() +{ + //don't update more often then once a minute + //to keep load on server low + if (d->throttle.isValid() && d->throttle.elapsed() < d->minMsBeforeNewRequest ) { + qCDebug(requestsLog) << "delaying query of city"; + if (!d->delayedCityRequestTimer.isActive()) + d->delayedCityRequestTimer.start(); + return; + } + qDebug(requestsLog) << "requested query of city"; + d->throttle.start(); + d->minMsBeforeNewRequest = (d->nErrors + 1) * d->baseMsBeforeNewRequest; + + QString latitude, longitude; + longitude.setNum(d->coord.longitude()); + latitude.setNum(d->coord.latitude()); + + QUrl url("http://api.openweathermap.org/data/2.5/weather"); + QUrlQuery query; + query.addQueryItem("lat", latitude); + query.addQueryItem("lon", longitude); + query.addQueryItem("mode", "json"); + query.addQueryItem("APPID", d->app_ident); + url.setQuery(query); + qCDebug(requestsLog) << "submitting request"; + + QNetworkReply *rep = d->nam->get(QNetworkRequest(url)); + // connect up the signal right away + d->geoReplyMapper->setMapping(rep, rep); + connect(rep, SIGNAL(finished()), + d->geoReplyMapper, SLOT(map())); +} + +void AppModel::positionError(QGeoPositionInfoSource::Error e) +{ + Q_UNUSED(e); + qWarning() << "Position source error. Falling back to simulation mode."; + // cleanup insufficient QGeoPositionInfoSource instance + d->src->stopUpdates(); + d->src->deleteLater(); + d->src = 0; + + // activate simulation mode + d->useGps = false; + d->city = "Brisbane"; + emit cityChanged(); + this->refreshWeather(); +} + +void AppModel::hadError(bool tryAgain) +{ + qCDebug(requestsLog) << "hadError, will " << (tryAgain ? "" : "not ") << "rety"; + d->throttle.start(); + if (d->nErrors < 10) + ++d->nErrors; + d->minMsBeforeNewRequest = (d->nErrors + 1) * d->baseMsBeforeNewRequest; + if (tryAgain) + d->delayedCityRequestTimer.start(); +} + +void AppModel::handleGeoNetworkData(QObject *replyObj) +{ + QNetworkReply *networkReply = qobject_cast(replyObj); + if (!networkReply) { + hadError(false); // should retry? + return; + } + + if (!networkReply->error()) { + d->nErrors = 0; + if (!d->throttle.isValid()) + d->throttle.start(); + d->minMsBeforeNewRequest = d->baseMsBeforeNewRequest; + //convert coordinates to city name + QJsonDocument document = QJsonDocument::fromJson(networkReply->readAll()); + + QJsonObject jo = document.object(); + QJsonValue jv = jo.value(QStringLiteral("name")); + + const QString city = jv.toString(); + qCDebug(requestsLog) << "got city: " << city; + if (city != d->city) { + d->city = city; + emit cityChanged(); + refreshWeather(); + } + } else { + hadError(true); + } + networkReply->deleteLater(); +} + +void AppModel::refreshWeather() +{ + if (d->city.isEmpty()) { + qCDebug(requestsLog) << "refreshing weather skipped (no city)"; + return; + } + qCDebug(requestsLog) << "refreshing weather"; + QUrl url("http://api.openweathermap.org/data/2.5/weather"); + QUrlQuery query; + + query.addQueryItem("q", d->city); + query.addQueryItem("mode", "json"); + query.addQueryItem("APPID", d->app_ident); + url.setQuery(query); + + QNetworkReply *rep = d->nam->get(QNetworkRequest(url)); + // connect up the signal right away + d->weatherReplyMapper->setMapping(rep, rep); + connect(rep, SIGNAL(finished()), + d->weatherReplyMapper, SLOT(map())); +} + +static QString niceTemperatureString(double t) +{ + return QString::number(qRound(t-ZERO_KELVIN)) + QChar(0xB0); +} + +void AppModel::handleWeatherNetworkData(QObject *replyObj) +{ + qCDebug(requestsLog) << "got weather network data"; + QNetworkReply *networkReply = qobject_cast(replyObj); + if (!networkReply) + return; + + if (!networkReply->error()) { + foreach (WeatherData *inf, d->forecast) + delete inf; + d->forecast.clear(); + + QJsonDocument document = QJsonDocument::fromJson(networkReply->readAll()); + + if (document.isObject()) { + QJsonObject obj = document.object(); + QJsonObject tempObject; + QJsonValue val; + + if (obj.contains(QStringLiteral("weather"))) { + val = obj.value(QStringLiteral("weather")); + QJsonArray weatherArray = val.toArray(); + val = weatherArray.at(0); + tempObject = val.toObject(); + d->now.setWeatherDescription(tempObject.value(QStringLiteral("description")).toString()); + d->now.setWeatherIcon(tempObject.value("icon").toString()); + } + if (obj.contains(QStringLiteral("main"))) { + val = obj.value(QStringLiteral("main")); + tempObject = val.toObject(); + val = tempObject.value(QStringLiteral("temp")); + d->now.setTemperature(niceTemperatureString(val.toDouble())); + } + } + } + networkReply->deleteLater(); + + //retrieve the forecast + QUrl url("http://api.openweathermap.org/data/2.5/forecast/daily"); + QUrlQuery query; + + query.addQueryItem("q", d->city); + query.addQueryItem("mode", "json"); + query.addQueryItem("cnt", "5"); + query.addQueryItem("APPID", d->app_ident); + url.setQuery(query); + + QNetworkReply *rep = d->nam->get(QNetworkRequest(url)); + // connect up the signal right away + d->forecastReplyMapper->setMapping(rep, rep); + connect(rep, SIGNAL(finished()), d->forecastReplyMapper, SLOT(map())); +} + +void AppModel::handleForecastNetworkData(QObject *replyObj) +{ + qCDebug(requestsLog) << "got forecast"; + QNetworkReply *networkReply = qobject_cast(replyObj); + if (!networkReply) + return; + + if (!networkReply->error()) { + QJsonDocument document = QJsonDocument::fromJson(networkReply->readAll()); + + QJsonObject jo; + QJsonValue jv; + QJsonObject root = document.object(); + jv = root.value(QStringLiteral("list")); + if (!jv.isArray()) + qWarning() << "Invalid forecast object"; + QJsonArray ja = jv.toArray(); + //we need 4 days of forecast -> first entry is today + if (ja.count() != 5) + qWarning() << "Invalid forecast object"; + + QString data; + for (int i = 1; isetTemperature(data); + + //get date + jv = subtree.value(QStringLiteral("dt")); + QDateTime dt = QDateTime::fromMSecsSinceEpoch((qint64)jv.toDouble()*1000); + forecastEntry->setDayOfWeek(dt.date().toString(QStringLiteral("ddd"))); + + //get icon + QJsonArray weatherArray = subtree.value(QStringLiteral("weather")).toArray(); + jo = weatherArray.at(0).toObject(); + forecastEntry->setWeatherIcon(jo.value(QStringLiteral("icon")).toString()); + + //get description + forecastEntry->setWeatherDescription(jo.value(QStringLiteral("description")).toString()); + + d->forecast.append(forecastEntry); + } + + if (!(d->ready)) { + d->ready = true; + emit readyChanged(); + } + + emit weatherChanged(); + } + networkReply->deleteLater(); +} + +bool AppModel::hasValidCity() const +{ + return (!(d->city.isEmpty()) && d->city.size() > 1 && d->city != ""); +} + +bool AppModel::hasValidWeather() const +{ + return hasValidCity() && (!(d->now.weatherIcon().isEmpty()) && + (d->now.weatherIcon().size() > 1) && + d->now.weatherIcon() != ""); +} + +WeatherData *AppModel::weather() const +{ + return &(d->now); +} + +QQmlListProperty AppModel::forecast() const +{ + return *(d->fcProp); +} + +bool AppModel::ready() const +{ + return d->ready; +} + +bool AppModel::hasSource() const +{ + return (d->src != NULL); +} + +bool AppModel::useGps() const +{ + return d->useGps; +} + +void AppModel::setUseGps(bool value) +{ + d->useGps = value; + if (value) { + d->city = ""; + d->throttle.invalidate(); + emit cityChanged(); + emit weatherChanged(); + } + emit useGpsChanged(); +} + +QString AppModel::city() const +{ + return d->city; +} + +void AppModel::setCity(const QString &value) +{ + d->city = value; + emit cityChanged(); + refreshWeather(); +} diff --git a/examples/positioning/weatherinfo/appmodel.h b/examples/positioning/weatherinfo/appmodel.h new file mode 100644 index 0000000..024f314 --- /dev/null +++ b/examples/positioning/weatherinfo/appmodel.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef APPMODEL_H +#define APPMODEL_H + +#include +#include +#include +#include + +#include + +//! [0] +class WeatherData : public QObject { + Q_OBJECT + Q_PROPERTY(QString dayOfWeek + READ dayOfWeek WRITE setDayOfWeek + NOTIFY dataChanged) + Q_PROPERTY(QString weatherIcon + READ weatherIcon WRITE setWeatherIcon + NOTIFY dataChanged) + Q_PROPERTY(QString weatherDescription + READ weatherDescription WRITE setWeatherDescription + NOTIFY dataChanged) + Q_PROPERTY(QString temperature + READ temperature WRITE setTemperature + NOTIFY dataChanged) + +public: + explicit WeatherData(QObject *parent = 0); + WeatherData(const WeatherData &other); + + QString dayOfWeek() const; + QString weatherIcon() const; + QString weatherDescription() const; + QString temperature() const; + + void setDayOfWeek(const QString &value); + void setWeatherIcon(const QString &value); + void setWeatherDescription(const QString &value); + void setTemperature(const QString &value); + +signals: + void dataChanged(); +//! [0] +private: + QString m_dayOfWeek; + QString m_weather; + QString m_weatherDescription; + QString m_temperature; +//! [1] +}; +//! [1] + +Q_DECLARE_METATYPE(WeatherData) + +class AppModelPrivate; +//! [2] +class AppModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool ready + READ ready + NOTIFY readyChanged) + Q_PROPERTY(bool hasSource + READ hasSource + NOTIFY readyChanged) + Q_PROPERTY(bool hasValidCity + READ hasValidCity + NOTIFY cityChanged) + Q_PROPERTY(bool hasValidWeather + READ hasValidWeather + NOTIFY weatherChanged) + Q_PROPERTY(bool useGps + READ useGps WRITE setUseGps + NOTIFY useGpsChanged) + Q_PROPERTY(QString city + READ city WRITE setCity + NOTIFY cityChanged) + Q_PROPERTY(WeatherData *weather + READ weather + NOTIFY weatherChanged) + Q_PROPERTY(QQmlListProperty forecast + READ forecast + NOTIFY weatherChanged) + +public: + explicit AppModel(QObject *parent = 0); + ~AppModel(); + + bool ready() const; + bool hasSource() const; + bool useGps() const; + bool hasValidCity() const; + bool hasValidWeather() const; + void setUseGps(bool value); + void hadError(bool tryAgain); + + QString city() const; + void setCity(const QString &value); + + WeatherData *weather() const; + QQmlListProperty forecast() const; + +public slots: + Q_INVOKABLE void refreshWeather(); + +//! [2] +private slots: + void queryCity(); + void networkSessionOpened(); + void positionUpdated(QGeoPositionInfo gpsPos); + void positionError(QGeoPositionInfoSource::Error e); + // these would have QNetworkReply* params but for the signalmapper + void handleGeoNetworkData(QObject *networkReply); + void handleWeatherNetworkData(QObject *networkReply); + void handleForecastNetworkData(QObject *networkReply); + +//! [3] +signals: + void readyChanged(); + void useGpsChanged(); + void cityChanged(); + void weatherChanged(); + +//! [3] + +private: + AppModelPrivate *d; + +//! [4] +}; +//! [4] + +#endif // APPMODEL_H diff --git a/examples/positioning/weatherinfo/components/BigForecastIcon.qml b/examples/positioning/weatherinfo/components/BigForecastIcon.qml new file mode 100644 index 0000000..8630c17 --- /dev/null +++ b/examples/positioning/weatherinfo/components/BigForecastIcon.qml @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: current + + property string topText: "20*" + property string bottomText: "Mostly cloudy" + property string weatherIcon: "01d" + property real smallSide: (current.width < current.height ? current.width : current.height) + + Text { + text: current.topText + font.pointSize: 28 + anchors { + top: current.top + left: current.left + topMargin: 5 + leftMargin: 5 + } + } + + WeatherIcon { + weatherIcon: current.weatherIcon + useServerIcon: false + anchors.centerIn: parent + anchors.verticalCenterOffset: -15 + width: current.smallSide + height: current.smallSide + } + + Text { + text: current.bottomText + font.pointSize: 23 + wrapMode: Text.WordWrap + width: parent.width + horizontalAlignment: Text.AlignRight + anchors { + bottom: current.bottom + right: current.right + rightMargin: 5 + } + } +} diff --git a/examples/positioning/weatherinfo/components/ForecastIcon.qml b/examples/positioning/weatherinfo/components/ForecastIcon.qml new file mode 100644 index 0000000..f0d809c --- /dev/null +++ b/examples/positioning/weatherinfo/components/ForecastIcon.qml @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: top + + property string topText: "Mon" + property string weatherIcon: "01d" + property string bottomText: "22*/23*" + + Text { + id: dayText + horizontalAlignment: Text.AlignHCenter + width: top.width + text: top.topText + + anchors.top: parent.top + anchors.topMargin: top.height / 5 - dayText.paintedHeight + anchors.horizontalCenter: parent.horizontalCenter + } + + WeatherIcon { + id: icon + weatherIcon: top.weatherIcon + + property real side: { + var h = 3 * top.height / 5 + if (top.width < h) + top.width; + else + h; + } + + width: icon.side + height: icon.side + + anchors.centerIn: parent + } + + Text { + id: tempText + horizontalAlignment: Text.AlignHCenter + width: top.width + text: top.bottomText + + anchors.bottom: parent.bottom + anchors.bottomMargin: top.height / 5 - tempText.paintedHeight + anchors.horizontalCenter: parent.horizontalCenter + } +} diff --git a/examples/positioning/weatherinfo/components/WeatherIcon.qml b/examples/positioning/weatherinfo/components/WeatherIcon.qml new file mode 100644 index 0000000..3278cc3 --- /dev/null +++ b/examples/positioning/weatherinfo/components/WeatherIcon.qml @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: container + + property string weatherIcon: "01d" + + //server icons are too small. we keep using the local images + property bool useServerIcon: true + + Image { + id: img + source: { + if (useServerIcon) + "http://openweathermap.org/img/w/" + container.weatherIcon + ".png" + else { + switch (weatherIcon) { + case "01d": + case "01n": + "../icons/weather-sunny.png" + break; + case "02d": + case "02n": + "../icons/weather-sunny-very-few-clouds.png" + break; + case "03d": + case "03n": + "../icons/weather-few-clouds.png" + break; + case "04d": + case "04n": + "../icons/weather-overcast.png" + break; + case "09d": + case "09n": + "../icons/weather-showers.png" + break; + case "10d": + case "10n": + "../icons/weather-showers.png" + break; + case "11d": + case "11n": + "../icons/weather-thundershower.png" + break; + case "13d": + case "13n": + "../icons/weather-snow.png" + break; + case "50d": + case "50n": + "../icons/weather-fog.png" + break; + default: + "../icons/weather-unknown.png" + } + } + } + smooth: true + anchors.fill: parent + } +} diff --git a/examples/positioning/weatherinfo/doc/images/example-weatherinfo.png b/examples/positioning/weatherinfo/doc/images/example-weatherinfo.png new file mode 100644 index 0000000000000000000000000000000000000000..6557b57b2764a3c5e435755d05108a06fc1d563c GIT binary patch literal 82081 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

l;#XiE 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

q~)d8s!8}{10j9MX@ELWZG*C&ZZL! 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

g`s}QO5ynaZ!c)D4y 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 literal 0 HcmV?d00001 diff --git a/examples/positioning/weatherinfo/icons/weather-fog.png b/examples/positioning/weatherinfo/icons/weather-fog.png new file mode 100644 index 0000000000000000000000000000000000000000..9ffe9c4afcde071ce38d6846f7b2e2bee233cbaf GIT binary patch literal 43896 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�J}hmz``kpmts#GklXq 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 W$Cxu22_JOG2`p6Z{w#w{Rv)qrRC%Yn70e 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 z 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 T&UI|Szei3=S+YjlB<%kIn6v!l literal 0 HcmV?d00001 diff --git a/examples/positioning/weatherinfo/icons/weather-icy.png b/examples/positioning/weatherinfo/icons/weather-icy.png new file mode 100644 index 0000000000000000000000000000000000000000..18d203840b84401e6d7fefdb0846d09e0401c2f0 GIT binary patch literal 49362 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

K35|IsY+(I%c&*uc^9w5Vce(+&K_Koe(3XqglO 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

  • NnU*6Kv5~Hyu;EjK`hl33$K_I}E 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 literal 0 HcmV?d00001 diff --git a/examples/positioning/weatherinfo/icons/weather-snow.png b/examples/positioning/weatherinfo/icons/weather-snow.png new file mode 100644 index 0000000000000000000000000000000000000000..e7426e3cddc7d6352a6a2483eb3bcc060d860c8a GIT binary patch literal 70939 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 VyzTpd{lCsvMr6R@QFW__#k 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%0DYr^=+;A+oR!^FXBhMu4`BG6 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 literal 0 HcmV?d00001 diff --git a/examples/positioning/weatherinfo/icons/weather-sunny-very-few-clouds.png b/examples/positioning/weatherinfo/icons/weather-sunny-very-few-clouds.png new file mode 100644 index 0000000000000000000000000000000000000000..133bcb29f734312d7a3e686acc0ec90fd1274210 GIT binary patch literal 65731 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 literal 0 HcmV?d00001 diff --git a/examples/positioning/weatherinfo/icons/weather-sunny.png b/examples/positioning/weatherinfo/icons/weather-sunny.png new file mode 100644 index 0000000000000000000000000000000000000000..0fac921d7bd2d314d127bba8ea93e8aa01de0845 GIT binary patch literal 59084 zcmb4qcRZV4^l-$kt!mGrC~EIrYFDXRtxc$Ji`W!NP*}bH+@rq-0)a?0)Ky-BKw#i27({>ve4Kj!xdJ|L z+@5N@A^`sU2yDZEYeJ~Hi5m#?Irr`#Yozk98yKW>e`f5i?`-GpW#tM1d3kv~ad3L) zW@80~JaKllPurKJ2Y~M10lji?e-A)FtlZy1T={L@L#$k7UW+6Ew;AtlD|xuTb#?{4 z^s#bcRd#;=-T~su$_;gcc-T7g=$fdj0&w(qaLTU03=Ymttgjp#As|gFCl4!EA68*O zL2;Vxza9V->;GWn;c5c`y>)koN(%_wq4hsg**M$&Lri%}qaOeTX{ac@@=o7v_Dbh4 z^7wlzXTW6VX=gw7{^i7D`w}qa@qfOP2-_~E;KU=}4K=|>vVqYoFj+zI-fn~tslx0q zmUemc&HIvXduE&*!_ z+zsrL8#e$!Ism~wJ{973IYSSgrO4f3R~0QI&>Pg`Q?0nLo5&L(oTdv9V@IdcaPsgg zzwV!nBw#G^-x!1S#2Us$pPw(zLtLH?$ zM8?!Zu2BG{oc|UfU_YX`)wsL%`F9;Gb5AQk|2~J#83Y)^{WpeFr)|Y&3tZCu? zTuvBZI5EKR1QSIEH>+Ueh=VIwElHflP+KJn05$!03sHX-yUP%7Kn1zF%Ubv`AZIR(!Huj zLvpysr~xT~JgW2=f6D5^f4h?I#o_j4@k4hpyL|-jlh^5+^k>IOte0ArF0L&s*t}E5 z>hl`9dI?*>ci44s7nCeI*JX%C^(G>#Ecq2(?b#Zt zkgwyman&95^%5LH?;Z}~b~bf;T+XNWT_(1=q&<>?>Yzy`YvNS6)e)eQ;NJ{SixbSy z(jvd@*e=I&J&udA2aoq_Mr#3*@7+q2@PQ1o4WF|eaezp0`OL6$#Gd8nK|J$*BY&)8dWtn%Y z4RBiMZZXjw{)Qy*QU8fB_(Viq9(Jm*g4=7ch6}c%EWmnyn*tzZb4P$fj7I<1Vg@!Z zQA6TU?rmTEo2_5@-dHcQ3c33d)b0dzXim@gUiugAD#_3_9W{;@KkyZg)%Qy?l7w=*H^Pn;DWF z)utiU7biDZ^jVlB?&@wH(SP&gRX3DBUHYq%%?XhAD6H#m>TNv_=(fe!JcIKW9s)U% zHyHe&q%|BcRsnb0_?&_Zg0ok2IwspEiIcnU zx>7T)X#wopr+;A4zNG`;B#u75Ujbq)yX%DPut^2${n`Fz@t1*E0a|Ezz&u9Zna6y( zA zIW}tti!-afVc?bLjm22TuTc6muLYnZ?;jmRX-51wpC;!7h64O?)MX0Y(0N(DYN_ef zzK;0^*SFdv{3E`{SWfTP2xJp#fMY!c%7wOw7Pfl`#;nL z%uX;vmOiSabV>CxZt-mY;DgufTtB~Ti2rZFXKn$6X_zvf~>x=bo{uZJWke_h){K3?E@&?Xl2&L zT;m8Nxgf%kwT~-UY`rtHW{LGo@6m%h6}Y#$E3pYavBt&u3*aS;EP36?vW_LVK53nH zywB+k(x!tp+j|u3lS>XCGy-}vOtJ4->1 zye3rf>vP`})Bvu749>PFAV!co160`e|19Wc-ge@UEvnf?aOnJx8oj;mfG|>Vc63Xw zO2&hJ$$G!BsU+UWkP5Q$k8|8Z$fakBP2ieCPfS&kuFQY2$@d6KZ({xb4*mVP%mm)! zTp$hjwaDs-kpTiL+&h1o|M(HatNEE!IZp92?>0CgGss>bd09GBYPbK9HwfBf|9a&t zJJAaWjz)E{EoG;gel(k!`VZN9DW`Pb#VE!pfc3@RI?OqK4#{kB{pfnz*tiX==3;n& zy~NUel*z-iH3ljlh0{E9h?$-AOcF1XbEs?-x~~UJR)2nt9cX$$MFih?Z|YB>?%GtH z^AXBabuY|9HL1oDMqP0;0#BdK5>Eak@uQ5!zRZZLRPNTtS|IS!q)gyw3e(7~X)|Xi z+0H{Pziqs~2M{wIFiS) z>C{`R_-v}U{hH>SsdTZ@zVce^K820;&xBE`q5SqFp>*Nb*5)+Dvn8XM2bGxc@6L|T z0)R-2DHN`qAXP;4bAxUAKApk&2)sWZ{mB_e6d^|Ti;=-iUlo=*S_M4>p?58<-D(GY z8}jR7xK$vvFBe`(KDU$$A;RwJ_PhW|oaPHy>CvmofmXxciL)MWjet#@1X)~#BNv{e z89Gny1@*n^~)vo}^o*ZcZ4N$jPCJy-&Nql&_eoUSnw>YxQ&%j*W;SJsMUoXG)kY#{r?bDwJ zTXY{*P*uR#=Q3RSpOs!K1+~0}JkonyYG$>Jxeb5H+i0mn^GPb_JhM<7FRBrLfE|!U5(y8j z!~|B<>k+GQ_T5vcm^{NQ}d^2imVIEV$cVzKXxe@FKhskCA)~&uKxRXbuJc*-~K% zK~ldMo?V&6Unx0Bl6U40BJlli%y^W;xBKeAkd~e&qDJ7BC zFu=D}&uq-+5hX+No)!fgbp3-Zh*jSuHc2~hP5=F6cXEmNhGD5MD-1;qv2y{qdA{w; z$`_I7P{n4?KAkJOPG-Oi`z**rZb8k_cNK?B`9p1wtN&qK8cRlCz-3H>!rfI(x1d@eykHV7N9<_j`MdZ%Yp7A5~cjI^!L4Gbe*3j5I=PGh^s3v zJj9+fX87YFeZo`5m(E!lZiHFlF)kpn>0>ivuApRnIV^4k!kNQ7DhFGBE3~o{B+5(B z4rr45{JH?;-bd9i)p*qt&OH>VvluNA_=1wktb{^=y-5@(j*47q8EEYb3b zqpQX@Uazn%^D7H~@6o!+nKYQPiaocb3Y2|^dVjNJ?Df3xSpn!#MB|e^cg+b2^_jUm z`Ak8mR(^ret)d+z$~46z2c935(cnp zQ;Srg1HW{7TMLonZ-EOs#jW#kE@SHwwOy?r*ea~wxX%9i0U0PS(j&q~%RzTWJc#6-C z(ti6>lsTWFiB1;`NDvO%K5om}LK~|6pH9;HMJS@{=YA;V+z-{k(iR-bA7|w}I=PPdkdLM5$%7)DfUAABcrF4cJrN>JFkPH<@SxV~tT`4X_swCHi{K2TEm%8M zC)M2?_4AP_gLNUrCCiZR;%uHV_iN4_<_YH!(T*_?my92wLm!JQHd69Io)h^rnVrN` zBgyX2)VVoLNK`+$;KyIY^m9s|2T38(B4O2JR*=x%48hLS;a8!%&dpLihi*P|FPTN+ z1j6SZ<-Ig7pNVs2h@**Y!o93q<)bnt9rTEzu&$tl#zDQ()5}`#FLXq*l7!pl z_*T*zKP1wckTEKl$5IUJ{Xlb316YG~Dh>%Po9fi%IG~}49tTT+7{s1bB~U}fsWG(6 zySgsZ`umj(HlB3x^C|D0yy$$rx^2Birkg{>770FlcyvYP^9M11wa*uda!<;u<_M~) zH>Ee{>*iCnB~0#OjU*%!Cyt#3jMf4U%EaL#iM?I=ZcYuJ6IGbHj8r~jxmF&k9MhIi z^R3S~2M@`iJ7a2W)xc5UYxW5xT)M}|JUBJM7^J$kKTu9(()tYbC-Xl)H^urhXwRr=5b#A zZm=#^IYlomNB-V+G;tGo>kAx&C1!eiIzQ$15v>^L5n+LGJ)NY0A*)Rr?g`Sx>Y99) zG0>H$mDh^4!^^RF_V09M(Pan_e8*Ewde%!$(yq~Ozj$pJ35s-uzG4`tST5`+7ZBnr z93CQT4&{aYSv(a<%m+-yCm==k&kyrs)BAhjX}n)R!p$&DFeQ(Z_eW}igORwo_MPKW z2!C1aD3HyFAV}WPBvcJaE0yBRq&cY_$|kAR`phb0pUND*J$|Iuo%}|4((yVeKdVSI znjZ8ytg7%3LR`>!Z;fBFjObS;D8)u7Xts$MDidmT%}QKjOF+96TKdlEmwD@6gCYH0 z7V^@xv9qlKE56P;wSNZRn?Z$tu!F*<*F=3orI#^?4$X{iB0J|8IwqdwJOQM4%uJUH z3*Zigz1#9bQ~3;T_ow)LgEj(J3W|7jaCW;x7&O!pCDurnL0kijeEzd4#rnl`jCFvWU~S42ys#ptPO51C>W#L#pdA{HqUb525F=H ztWoNI&?u05Yd?Ot-`&1QE7a2&Q9TRYH? zgl{2H{#qM9`N`_0?lQut(Y<#%m3&TMH;NKIdNyEe2=&Tk&3-*WiABMOKIQ+z|GR#ykz zO!e4vLFQivi?E^J#grA7eAqXruSf8i{}h`>2siUM^QH4!PXwjUzEpM;XG}qDTy{O3 zG4x@_3{)6c?_(Bw7-7WBI&?P-CuY|_9e)*NC%8N#LHq)O{n_aQKU`alV(kJFdoMP( z2`i5l8TT>Vu~@nbQm0#{rTt)R$S^zfYt2W5s<(luQw@#r8?nK1AgxnLA1qq@*#AL2 zOyozXR4vU&D>uF&^)H(8jZ?)90^|=$u{?Vy${%dbc({_VB2>HX7x%99_O+J3{_CeC z2p+^6Q~w&Jg)DCOZqww&@aDfPZQI3vPK7&`!KBHQc2I_Wq}kFgq66X$5p*QfVM;c$ zUh~{RoH3czm2m4PlSr88*lqqt;Uo%u{~;J>V+Q)FvF?k-Ok8b^@t#pYGr52}5_|-| zs>jFBj-j#+50#*l?ve2=3EuT@D9g~$*sR~_+d4@>jnfhOW~A)2DqJ@ua1 zLOo19?4BXMHk}BXd>=Wl3+sv`@%H!~`QYu|WV1zF4kYnlW9ER%{u~JYSL!$WietZASej@#4)!BQ_I@W~Z7I$y239vesMrA4ChgNjmQz^v2`um5IC|$TGI(<={oJVY zI2&2Z#Y0lH%~x+cvKf$}3?9lPG9zj-231DIpv@kVV3`ga-cJtj65=(2zlAf&DoV+?`PjBRvxZ+v7?*Y{)6t{*nIB`OM*?DI~N^-#K;_ciAFtn9`32| zV*X=C%3UNSx0pMhTId+3aBNul&esmt{xO1R19swvuIcRZ5-jMO3L{Y!^gk}cO_}&} z-td=P z7-dT0p5NEQrdXF%Q#OxR#NcoX;qYQpCoOb6!5>z}3#ekSKTxysZI!sqLR4JXZpe-s05byPW$IdorF+V7S=k-k0t8 zf8WCJ+C7mJBFevIzSvTtjk4h9>m@QAo`@B{A?-^ZYvL@7BC54@^5V8oonvX-8D3;*y)SZV z!{H?#&2y{!`)f7(rB?jhW%6>Cb&MbJmvu`o!dfzC>gByzbEz+@&pz7I4Wg;hDX%8Tc0uN-*uKjrjRkeGP69a~-G%y%R}@Z_ z@kDPq-&x4V)eKk0?P@HbTbij3c)N|J@W}5=l2tET#D=}vr)x`=A6TMx4%Q1iMs-Fz zp=7#r$&gf?)72YZLUta-2u^65?6*DRVxKH)(WNr;bruR);|BW>p*$|3j@Tey+Vd|> zoO1jHbQ?UTRc$S>*%z-J--@qL5vgGn=$Fg|^}*<>%d@p^>XFPpq67ObNtZO192yIZ z?yHF@H5eQ7K<`8Cv`A{+ETAzfjOuv5GNaZE1I1cDP_dA1M7p1-`GCuhyP<_8e=FPr3$Bw|g&Vytj_N7{e2}-CxNtj)jP!l-1AwjJ&=5nF!Z?vi3mm ziEevBW>-Sy;TSPlN0Bo0dreyws{ri)|GCX9?5hRdh-6ng{w z-{TM|(|IixSRKH;x1q0xF@5`6H#%Ae`B74UBh*W`Uamr3R)FaOPBz&t@5h(MQq~a* znq1<`VOc4EeYgo&G%L+p+jN>ogn9(_Rwz3j^Qmf=r-k?&M zd3thf#m>s}Q=y%Jpchbl?c2S>svw8!*xd>(XY`|;tmGQ_ran-C%I98x{%(le=94~z zd6HDE+RDQpN4O^@`|S%!#@3) zwF=D9AJ!=U@~G}D4sDR{l3V#LiT6V(nq*7N$o6z?VIJyLH!gr0kPx&d38qu?##Jq$ zpKy3dk6u3MHoP?YL=fD3?@7J0W#4dMl`r2IF$=l!$bSq!(|BQ`xvIfB70`RXS6NCN zyQHh=K>PUnIQx~pA1pG~Fj4#{Q`sJ-0SkwZ%%4VK1#Bz!d+$z6F{1&tMhJgV^^h5< zYwqBMR0_)wmhRJe@C#5&whb?NCY-uHhs6e`s}U38)k+s8dP1kn;Vq@$ml> zx$F4)(x^WLrwrL!<4dj&8xg_5V{-OrFI_eqBSz6Jmqs}{u59z;zj1h5dB}de*!g`_ z!Nb*8)X|4(n701@Mfm*K?C8uN4V(W>UwaJ{)yDRW!Sg1=)dkAOx+IGoHT%Yoh8v)vfi52eec{R-AGQ^a!LZnlk`K*pos0#Y>KuW zyV2sUsuurqkWNJ$_QVPg`rXmPlf()e8mL635HxFkOy&&rb?VjMCmmH4iK#@Sn9!~3riG#tpx1(^YD-2Vne7ZQUq@_)B zS|-2$^VUY;Qowz5xrkFwu%d@mvOmjY%fybKSvjY?NTgwvJ@$fLV+591r>o>9*(kHo zHVOV~-O3Ld*rHB;+7m>Xr7jC6E7bVb%1vpquLkJz86(AzvjMyTrN8z@inDR%u+i9u zdxd}Lpb_s3gDz=H)?WV#tApK!$=dQp+!B=k{LZ}B6SWqksstspfZ{9IOXQ!@CRjX^ zo?vIc#0qrH?oXiLRWd19h5{H zGQLQ+wqiRpUkc*zu}=tJ;#?|1PVQzd@KlophrMY!H6l4)d@H)a{;ut~$!iKc;n!&n z77qQ(=@-p_)$KF?W_oq^I5S1+UeV2V*!fd^&^iskF%sAae&c;>&tNOxsSg4>b-VBW z&LiODI*5Fzdg&YSGYar;$q0KL*MGUi0Y2rp2rfAV zhp~rQcl~wSG`;}SaX#MmW;w-4L5jyLv%|MhZ@~I*f?2R&(cjD%5`Xd8HUIvpv`b9gDk4$EIy8upVJm15aSCfhUnJrTUN@wAkvG9zy5 zl;o%8O?-EIw2{S6>?Ot!SC#~_w%=9*zu9PI!?qMYZpnZ`$(}lBmd^Q+Ufeq*0jwd) z)+=P?&6mamg+$tZeI>r$q2Mm9xYdn7#~$vh?{8t*Ga|ZDG*I)7gv=uGE8Z!s@|iLY zmpqWTqzv@B1$P+btM7XxDL!$1m&}$9tEJr4V;XUhW z85Akhk&KD9T*1TvjSEF#wV-ODA4?=h<^ge`mgP7CjNQavZbUms6Rxw}xfjUEr|s%C zZ&UmC9ed3LCji$dymW)9@O#SgWa6*Yblf|&pRtP6jKS_t&Ztys@9!d9v#5xZt)SPI z-U5sZG1Dme{e7b)_(nNnqP}CfxH{ zjh6m8bZK2aYPCPL&m0@GO#GqeJKGY$2E%8dJi|l%q>W*3QGK*_z$SRg(j-?w#K(47 z*tRWRebP0#inx*}<#i!;PK(G>h#h%|7jl^?8M|zGANJsU|38oLx4g2rXtqO^&r)(r z)JA0@y^TlJKME}O#CxlKZ}C*46r9%tYH@qqk!Zj6zq0SC>N8ioYJ)U1{y+wM{%d{s zN40ZQ!Ct^=v9(iI-_;S@d1tlVyadt$(Ra3U z(1_qfWD=q-W+$kacxqZNxYd$pB9J}ar{8MGwU__6HLT#F2RG)MuzujE)8E;E>Dznz z{V$XkKbCFy5?=sb990?)g9aaOw5M3F!L1-Pmh0eqi$8=wV^q}` z*{+uMpiJPJK;S?{_Bd9JY6urS9n+NT@{)gSH`I^fz*;z{1 zNdNBq+Y{648;v76nXYyh|<@c?)cw zon+TI3r>Tup(5gBL+K5dVNcmraH8H%<-yW`d=?R?!gI)8ym&eF!!NDdOI`9)5^KIR zrcmq%I7c+}t9JeD0;b!D%SMD12>o!-%!h2<9~kBz2WMf)1l6EbY=8X{M0uCbbf3Dc z_IUGPl0WQQNuv4edPM34Zmn~`#0MQBILw)0p$12HO^DIU)^Lc0RL*V*G9C6^yWC2N zIcDc@KZUY;o@k$q{v}%elgup9IY|^+Z43{e@|IXGjP6Rn`>&#=4%x}~`Kmos+6Bkm z@gT%jH&bw4Oo!1SMmpCdGjw&6_0r;LC6{62&ASyWQ|&DmZV4F(7m{*~lZNyQIv(!* z&0-Hjvr0<0?7Uahdei)fWkyt|;x{Rp`moi3lg0M|5KQ#3fg3!F*>zNZz6gx5;gT{g z(|crmr1mAr>JT!JnYG$-D$2-+T3@TfYIR1P%8M)5VB{B)cJVMu2DIX|43iysHZp4)5=pm z;`&6*^6eaTm?W|eF+3yJED$rPoWniuKYg!1@kL+_-!BktIMLtUwg_2OPaj?(dX>t8 zi`?7q=~yiktkiaY3(UVBvg2EEk7!*)&5vCl<(r%TGyEaM&W{=G888vwq;_&=mXbX= zZnfg1QuXTKuQK8I`X9jQ86>CeU@x9yKSB}R;|G2z7d~+OC96jNt*oOj@YLVE0zl35 z-o^c;C?SX6LrS%SZU@+vfNiLtK{n~i)5JU&@LZO-mQWHLlCXn<#I$^?v}+Fmh^%V96SC3>M?)wKEn7AJT`dZ zZ9^T0@B8pFsI#id_HKsr7tQ{;?|h(Q2M2Am8wW%oFHcw-kwmA2c6d=UoWB7%EcK!n z#U7$uYhWADl@jfXWxiqf^}f&aY^bc`>QNv`Y6lOsPUrPQi&mF$Gm&pyWTjc&Di$C`d#rx>n2@l>CksU6 z#*dPRk>MG%z7ok-o+Ia!{w1uitt`0t2YQiHN!btNcq_N>am_0~)cSwaFkLMa1V+y|}N{lECf$UWRcxWK96em$0T02QLZ?RGmIA9Abs%X1r1-_`Hf8giuU!K7+{$BQ$hPGBwK_!-T|%2U z8%zDrA=?(ga7U03f@FfRd=oLQQAjZ9lc`NW5`RI=uyl;Q;6wh@jBC^xdC z**H^!X892J@n}HlZOAey1g(5$8@YZIwk%S zP!sMk5;%(#2KNbAhp>e9kUq7r)(K$pblAE!f%kv(IZvoi2BP4o){$O&%3z&jY^@W3 z;8Zn73`Wxl4o|V%D*UVW(KbGS^AHW{*3iZ5t6xKk=Pag-oM4D>ear!V3$dtz=y0`C z?Y!0~HXwZ=j2>)js}eShUg<%Zjg8g#nvGwZ80IXOj8ZAdEGFOKh(ZObK*EvnyVkvY z=TTtZtlx%35DMY81K~QdldD*07CIc|?7BOJMuuZ=8L;M&m9mxGvZBr%$3_T)DHaulm<;;tUK4QAq})2y7$%|c%%(DMZj zrcoHBJw)IT9;?n)}vJNE#nk8>ZDV>UP z#*4>L1AP)>x?T^&p1|z8gLZkJ2LlQj-^LSd0%Zu41$;H(7h*Y|8~#xEES9qgK8r$V zT5ytG+QiMd#3@WPGQaC$$!{V$nG>I*>E?U0r?lh+MEhk?20<(`Qy;rXRHKao{ z79R3;A7-m$k4al8kpexRq)YMo_!ok-za;<-M#zx)3|;J?**DNd`ZxRKCvFNIvrhB! zLuHZrd}yzwvH)BXquTe*omoe@S5=NvTxP`cyggndt!@M%=EsQ`olo|v+q!`UPFHbk zTGd3Ph0zjD0`or$YM9F%Dzx~o+uQ>M!OJmeNvKaWDyhR7;y_)utA+p z)KQn2w0OJL@1fdln>XTEP0vl)&0X$$7`Jygd4z3kli@92xtnv;KX|~{PK*YJmB=eU>zDgHhuly`53gfoA4rvgQc8vPRtY-Ftne}evyC*EwnCj_ zajwP-72nO=dweVEtayJRFkBS;*fv&WW*C7!vRb~7_V8hQy#B-vrChHtkLT21H0_Bc z0YoN?&NbO8;$?6C8VA9JR|vyQnU)x^%%mi(oVYK_#IF_;GDBLL5C3N41W^&^CvQ!k z*SPpAK<J^{fccJFGFN{YojqZpDT5?{6{H?UibI znZuhPS1p2-(yWh}k;f9KEvKMdnTN7iUuaH5>W61fxf~k4CHwAq%wp`C7SvLVfpkja zJvz@P#Uy=NVu*Zn+@wIuWk>4+yp_`_pw(d&6|Xi zBy?Dqn(^=fDW;9|fTV?5lP;FWZs)7=myQGHM!DB|3eCu9z**Hlk54=>@k%vdMk}*> zzA2%x6jfF141u^=nn==?ukqjbXS0~UnA2~o`Q?YmyyoH{{3MJ46r}?y{y-j~VNB13 z^f_V8dREEx0n#0C6Lh#@r9G~$V2k6eb=#+oIRShxOxWo_mu~s`OyOJ_%3FuX@2O<4%25 z?87#tI~n%~=t1nvpTxhNFfNk!ess#OrZci5-o2(9Vt@1IIWPL5IdsXUMD)`~w@on` z#Vr)F@09>yHElFSIYmWNA>z_8Hy6I?K|4c4RuF{|n%&KGgiD(reASj6bL?>C#GU;$ z7qdAQ+YKrs0h4ymM@ppfxApty0(xKgA{rlQxsGWcK9qQJVRF;*g1yh8Qy+cVus~z} zDAa$bjJ04v(6bMI;W6dQ#W=UY3qQ)#T0a!>e)8Y_rcIVaS7pV`-nVZk{~#zY1Xx4G z9@`WAAv|Yye}Wl~Xd+B77$?LKXQ|CG2yuXK!V_O8uQ+C>KFX_Yv?F&rR1j*!2%O4= zx8ArTevrvE3?z@BUPHytQ@tLlF~7Mj`s2!Yhz%q`6^W{!%F9|-*`jV}XeU*if3G`V zJDPeokl|^0kHM6e_CxoS9a1QxZ7QsquFAAYx3Q7~fE2$Ko3fQICb-oAN*bTLOoK0k z9iXCM;C7n}vEIbMWF^jlAX;Me+0|qge6mlmg>nw3^1`@Szm&F-;P%*D5-mvVT=06! zAdXfp=^I*m0Pn=ne7grgTf>;Cj@92e_f$FTQYbGKnT)rE+! zsGR&+Ot=_Lim&{Dl+c`|dzw<&)ATt^7GREW`<}npVpwtm#FRyJR*`-I?`a-c$bD?w zW>8pkHDqbcht>yM^Llw1TUY0*>Pyy?+?!iy(^bj;l2z-=tD!m5m9@iblj+NZm$PwQ zq(=5I#W}j_7|vB5teVi8!N7F(M*Q3)$tuHTY>FGcDahytI$v0h#uTYG1W@<_b^3O8 zat(FEhchCt7EwFVA8?+cikh$zeV4NyU_Uw)F_fR%Og>3{Ls(hisA|@udlD5Up01pH z^F~OQFS}GGtw2q%~&&EY8L3c2(DN{1MHiIZ9j5D=<+F&yn-)i z5hE-C@h)!@oPVt4HeqR;prXCOE3()wkh9)LrjzTqhc8x|KyAmS62d6(TbUoV=C3W< zA^V{H`(9S&&OC~T0zi-yTQjN%e%Z3xw@5Cu(=G%u=sCnPmR;k&=H=W_@NNehhij5& z1}Q4QQA}l_-jZ-I1bY_0jbFL2%&t?x)slJ0^z6u45Y~5lBbtMLCY#h+hK4M=?h-9Z z;q@&BW4M;0XPf_GDAq0LQm!|}*k``!!R0aoBvS~0p5gUU|EnGXV~PcYt}pw6N=7E) z+p%s6VV2rR6b)+*-;sq@=istjvfN_#ZUar9|5@}!cznf7#ZU|BPIc91>G*Ev*jz58 zZE3^bceE$awMBQ%R<*4-8nT39w9wzb@OwyvR-PNsw9m z;jtH+fWOaKOY>7l6bD?Xl#+VBEd&na;6|6H3;vLJlX36+>|8>xQRI!(g|OXZ$#0Y! zy&>8FLhU;6dRO8`cwAqAL?}iTX&>bXX(8T^dA)wnewZB>%1f`5Ie2=Care9Wt}Xr_ zXuvWm`6|bAURW~7US^IoyUaoM*Mxe`P=l%&LQY4z!% zP3Ez{0p!)u$3&6r6Qsusg5Z_4j!g;_P-Kf1 z#4|@|HpoOx*O63h&SD~BRH@+K-?ybq1v7jt*}CEA9ww;Y9_lECw6NAIixD>W`Fn_2 zX=g05^;N3?4qY3FBh_%W`~Mj2m!;lHl%mbfj|YdN)mnl6vP3{@fq8V?FW+DODova) znmGiYqn{Syao@Go!TfI&ckcbok5ob`k1_PY&(&*3*bDcP_56&xjgH0o@$MJkJa!!U zN)EbK@zAcK=#VXu}G}Isai1w7>sBhS6>=3bC7rl5_6c zzb$+{tsA6o;C^Ku9s=IO&fQcyY<6ZB6JZ2b=5PF%d?++hHfTb#t+rRw?p*1+x zSU`kCAr7w#Zip%WQ*L+lK+o8Oe@~k*ox6!YrnFlON;X3&N;QQlV|OFw!~GYcjd@cs zby7~MhijUoW2&5Knh`8)8+bq8C%s z_Gy^mla|nKzY|J}OL!u1%po*sO${l%;a)7Vu+Xk5k9U>TxjRqHsle%*b8P?Wk-<@@ zZ4s4Uy7dc@dyicW|4O;sgnP##6BG^Re@1~GUVZjtb}`gc4g;9z$ z5ZDlv&u;jUsn52I3AHD6+oZiHF!m-R;klK{JjO4!8B)}#rx$Scxt~nAfkX+0!UJEa z$`|%26u;#~Q389d_BDhmm(`GzvKgx_5e&p&ZE7LW_nuZdu%p6$Qhq6_<99$3kw2R| zU|U9sT+rL~FzM#}On#y`?6TwSaot0q=!GmE@)*?jU=_V;aURGj)0P6{ zk;K%+OXH*Ur!dQw_=eiL7Dy1u5*5)T_N{tzp8|s}rst}!bsC;O#5T~25$1f;IS~_*XY|W53W7?`mJN7 zftW_ir3dupPyS|UqBM(71T_59Xyuowh;A*a5?6aoXRkj37;vzMJltVn)cK@mhX-sh zWwF%`Giy_DMrf~ik^O9b1uS2mFC7=;XcD?agDW6Dcj zrShfH3watLL_S`i9>l3@UPnP*s->xG`Kr0zGq6kJjB+* zC>TGA9CGX;xQNi_tFE8RqXt%cq*B{8w_i0Avd_F@|5*P%kfIXp)DhFK4M8iAk)FPo zvYsm!pkzQWh4UvVk85^6j~BPtX88T{OETH$X}EDwTH6(H4<)&s`pLCpXrwsNLAy$U^=D{0ra1&+#&dY(>4SXB#J` zp3eR|f!jbAPuP1)Ikv3mY3)4O#b@fo)<~&S<$-K`I=X=1M0>M7g+q}_S*0yCdOQ!t zif+Xy(`aH*N1RSP+>ix@## zK5TKRuBW~ti8c+p3rdj#n*o-pUzfF025qhwA)3J9!}oV5I!NYrEa*N8jJd)6wQv|# zhXX81wJLMa4lZj4q&~SAOu=`*&#|y9{0Ek~eF~;r-=OajV1GmvXRHje3L4PwuI3^r z6u+9E6^Oes%RC^LkNsIHt1xB$QT_eAU0hiHwwNfTZ79mBc99TUX4?qeRvBz>V^RJ% zd$A%NpJQlN;@R*`8M|Lf-z)*D5;hwk1dkDh0A02(@f6}xfGRM;k0P6kBa~K zvC1*m>2~$CE@u5^fwDDwPw1ZDeiQS$1&`KCG+WY5Z^;NnZJW%0rnl_l4~c{5m63ta z#a!^^it3q|DuCpFvWgcXQ5OS114;2G&DVt|-5ydUnIb?1no06hTcsxljH}#@Y!^ut z1H;CFUr)_zcX8ZyolX7UvjDpu&18fdiwIXdCzhS_#KLGNd|4tF;6TQ#Tr(MR3Uds2 zUP?=%IIRamYd0JUmQ23Xt}9}qdFW7!j@?!kZlma9wS+f`qeW`JxDsOLA|InL635}) zLusLhyipNph#|pog`(OE5}veP-{s@CCi(`cWpM0yN36wDf~ZqU=FomP{V^T>D5w3(yu_ySGo+O>V%NChhlRom3bT0&sF! z-I*RsRJ6HjgeWE)3&TWQLKN#|%`1#%BE>mW`=39*ChW7Yj*9pWn`pBCv=aLtmumh7 zd&w99Eq;e0wLui(`@x8<4N|uC0ctv{r?+zy-_#mthGjl3tg8pVdn4$TABjHD^~hXPi{&Ajc=z8f+w+Os+Qva0Y#c#nSseV#dr6ee!afX}4e8m>vE)Pzhide}8SVHYbBB@}Yi6e}uExZ)pb%Q-9KAjGv53x7_V*;fH zbE!TRriZpP6cGo0&Cz~L#24HhB&EB%Vc&gz@ArOx z!MW$m%yrGonVI_?6~@wbO@mfS8FB#8200aQi+cLm9c%H9px-**6UCDB)pOU{EYHKq zmBRB&1!DixR?*iBxOsA9sB*)o3l-#7ic8q64krQP>>!S!*r17PHku%c-cnY22m@*9 zA$z#$6~=3yG1Sq|60JY@z6O}M4=dueYai>zM8D21IsQ1y{CT;84A_k~!1@e$FM6H5 zjPB9<85x`CBWN@P z_oT}Rv}}nqrG-!O^1v$qi#oD{=R@1i;k{ogzhb^!nK(N{|LRPNboS75KDCNDGKRYlVS+U*Uax! zsuH&XCjE(B9$&^v+IY2|T*uFqzW(*nuo$P(QE@riH9fv5i9ROub)!$}dEDY7v!;YO zM9!OzE~T}P45)QNa%^EKCY4iPGo<(rn*wCEKVH;r;4TcMU&uH~W4!9f5aAKxS;D#l{kf6-o?hS zuNMrQl&)i_(Uk8xzAT8f+x`oe-WU_|r3rdIn%+X=iBV%6icIe*3XApFCPH>EniZ!K zBUe|HLUYW!2V9@ZTd9wzAf}6RI|#P9t0U}J_q7sZM;e)P+)ET&1ZmN4eYE%{=#?yF z_|_(F;p;K4zt%q~_eoc@6U!Eu(Zk^N`Y0ba+bOO<(^Z&oI`vk;qFV#a`z!EA@GDCm z9S4e$I1IB}(^cb~b^H`%^oONJDFXu(YGxEaF1$rC#z>}Ae)8)?Bd`Be&99^p%ErDG zS*F<<*86axTKgSac$+g;vg(9@%ui5;6?JrD8&L_@a!V|liE8_MWfE~-Gs%^DuGtr8 z%U8f63z>^B{h)*BJ)t;uy}iJiB~MKr@+rKK+sZGqPd<(hqWc!16H<`(i?tC0*+n2J zXA~t#M(j0$Ha@SJ1i^M>Gqr2PdvDEwypF5$MXCE;89N0-#K#(x%0wGw6j^Rlyr-s) z#K1WzxWJ2~6swr?FV0VcOO>|VR?~TII`2W#DcbC$Q}J2T-F3}a!T$LE3Nk-PyQB_x zD4Q90up-5%vu6OY;EDH>ifL|=UyOiwV)z(&rG=$IN!`ZYHj^PmugPy^ic?eOBPjqX zY)EURJ8gDTK1+Ur{ahE@)zjJ`dgSgpHxB&R>4O!g6Jmu${}+CbTzCN&{G5f!dN&U5 z*p@tt?+W}9E=8{(wXP$Tq6NP4j{Oz;*r~7P2T{3a16vFxbe~7|(k2a$6k@AQj7MnH$jMX|RBuQdhe}un{V=c3gEsRSGxBahJHUDRS2%)_XAuEB)?imgEW#I#PC!2 z@6#e-Towy0WgfYkMuqSMBkoP=d8(xS(GZrd0w#lBU8;w)Nr-Qz99xmKfTGeD=GgjT zoG{oR^!;6Y(?hT8!&;~$R2K3VUeI!s)gi0X0o(V)^f#`X0u@G2+rTOwl&#i0vMaJb zHKRrEAAQ_?pCzq%^QY_LMjR!?_bJ*3go5kRH_sDjJSp2th2qzg_~O?ga<361VY%LJ zLEe#qB60S~ed7+QLz!P4IjO6uYIO{%oB`ciZK3%$BfBe-UsF4|iFA(Zegk_e(amFE z6!g$RVwM`*W!hwa`8jKlsbH^X0VjBnJ&{Ez%C>h!3hEl2@%eQ$1}iWm~W(-1UX z8M6;XHp6LF2(yT)1j`8U59&1K;Xox1JrXO`6pjygGQi7VRVhOG<5!r$0Fo{)AUL|e z?uPff_>Gzd?a8}RM1}5&mDSdrvHRG>)ju!McbT1H`@4?;7wL+b-~PB6BJ-9Y3jtUS z4Odz2b~73=xs|E%E3KD|cf;jh0#|x|YDlTvFDqJL-!INjKBBJtbEqR4e*q&AeAMky zH)hxJ8x(6kke)MXVd+g18J=efma9^kWKmj)- z1sM}a&AcJ|MZFbmU5-V31^V0`!0TgKlvd{9I$-7HdlCM6rb^kf17Cd~JAY7WaGP=S zmuCcX5GCprUOyfh6@%PR<6Pn=G^uEc`qI0nu9Z`YwD!NtIP1^xL_Y_-ohY~@iL@$3 z%zW)-&UwT4+P!BKPQoaaj4BZNb_$)AIl7#=Z*wQ=R8DRG+4b4Aq7(TAn_@2VNb!g@ zNPiGGB!@FPTCCTazC2%wU;7nB5$qXKV6cqFuVn&w=KfxKCu%|XV~!koJT}p@i2PW0 zZ2b))ZV-p|-dVu1^g!~XzTD4Qbo1(K8UMadSYI#esW%;|KrHrl`RIa|&#^_aUt)gE zq`m|S-onLBxv-xgd^M(H-6W;>$`X;9P75mMLY+YGe8?FG<2cm25uv*D!rG>jXuJpZ z9`K_AVvr@NQg@I~$r$u!6zcF?OGvParsv;)Wbmxe-hQJ4=Sre0J8xX9`4+x{9Lj=i zQL(?1IuRf{_pg6z8tiK5(A^5xD-c66?KCJ=zk#yk{5lnC34z{6yzUoP2QfF8qvt<5 zqx=q9RSLpxQ+e5(Pdn!9gAv7cHqV6_sGJUdKR^Xxah+JVp46j8IU4p}B+8lY~ zfR-Rz^sa^1*-DOI(*iyf-TZYo*Hv1%41!FBODLpwvT6|3W$qvE_3XEM1(~TWLz8l@ zoL-XYBhV-GxC83jbZ{BR+F^$N*tBQ(C_w4^;~Pa{70{!KS}=)_gg$WGJQVo}f~%cK zRddcRN%T!bV3~}Gxjp^1+i!88v~3qOs2SZr(~LAS0aVo6GblpoG&a@ zFK$4cn*toepAcq!Yc6z2j_r~OM$*R5K6+L%4XN!Umr}Si+%S87h!Zx3IITq{6R#<} zCh>=Uc*t$67IZmSiIXN|CLvtO4kcXB1s27uMJsGDLxUt^_u zBS(ETf6KCbEPXwGfosJT-~LXH#WYm6=X?6lM4#Ocxk`)mZzjVL0zLr@Z06wq<*w?}yce)Wn8e83So%>R1Qq)F@x zwsj*J*t2typFvw1^!QVAgG7i6X_TN<$(w9qIfl4@c)lxdFwa6b8t#oAq>EDp=wUHe zBJY#*CRGKv(oV&%1S!tYDl#l^BTm^ zsNBYLJBwe&Ten`~fnJpf|`uD6jrA?ln81^InZ7l6&ms@xJ>Uev7Gt>Gf3& zw9)fH{qLb{8%eJ(zy7vWOJH)()PhcE1Ea z+PxMO7xqdCvF<`SAFTaXGsqO&+5m%8&oK4 zLa`3*{^e6}^@E0hC)|VxrJlm-C1}!5%muNdvkfAfB4^uZFC)>#m>R*Yt=Ud+d>Y?C ztB9JowAXS4KC3-^SEluGZ#g3jI+yM-yW11~q(`42Y)MG;ltw9Z73O`m z^2Dm!-A2xQ{QY_Xsh2IEG>2A5t=k%y+E~gis(V6vv8v<9WdR}To6WOzM4jPJjk4w1 z_(Jq(&--qvcFyfcXb~kfYYzmj4{`ETIttm6qMZM_87NnmV&1#$3%8KXnO={v{Fa4N zGb^|Bm5h9*^O03NP*_ZNs`7eNnAC@2(f#XHGsT>BpigqT&rfCyJR*`_0HfhD6w6KN zIDTPghx?=I#dBd7i4YZ14=Des8%2WLHh<|sdTV3@ZAjN_qjY_4VHNFi`uF9)fFZ?s z)l!9ZCXJrcuIZO@6k@aWvQ`ThNu0;|F9&n$EwbzY52gTu-Mc@iZoW>ZMqZJfeOVnIA9!OK(PcH}*hj04y*WUwxp}Ns16;r-}IEa#H+jiL&kO)yArQ;`P0K#FL9XCPaKZDhAHiv%h{NTZ9 z5X^utCklGez$mOO`_Jm8by0Tv9M3^4bY0WgHJrFVywU77PgsgwsAr4|qE3y>|Lh%^ zLA}-JMZP79<9rJ$1b9E=?$&HDJl6>f8{PC|UkEx54X_xKC#hYQxT%4wfm$Ohcayql zAukB)S2|(yd42k=qFHNP_p(u8^9ktL(yvtCaO@LW*J$rpI7L*pLr7-!nc%Bl*XHYX ztgjn;Ht6=4B?JvN$R?1hnfr8xZ(tuQ!u4+_heM%l_tA3S+0FaSm$7S~D!l3K(V7$W{VRJInNbo>l} z_Tks>-Z{3t9b>R|r6y9KLB$SYwb*%!%l~&uCy+mM{NPKuP)@6yR0U~Qy_)7%tG`^o zxs(QOWMijJlzW@ryne_EKO#bX!rg2ZN|nmqW$)?h&Nf$h@@jZR+ZIsUe-)2(G6>Si zT494BM2CT`F6+nkB<%>pNLf+5BX^O>h58IP0hl}AY63&7>K%>;2?4}D)bR@Ni5(Pf z^{zpr{+QI1RM32rpl->)p9{HUuY~t)^9hNwAnUgV&w$o~00JJ6%a*)fpQOx3?h!P6 z6e>{M;7nQn)qlXj@yHuhC&As9xCr?tOEzIyd+qOQUFuCw8~Z)sPePvpw0H&}C~ayh z_R~B}VRf_+kZjZ}0-1#yF#G)0@O+pRL-C>J*|g{oW6_sGfvJ7wq&bg zYQ~i$vy}gV>?wa?_`2rZJ6_Kdkkq%%Pea|4uaymQII9(oe_r-lB&T-?lWQ{DD&Bn? z_HLkQmf5y|@;o{!orXbJ0xc#RxtOcqKyCMf_UEq;CbkVsdU8qbG(oJLW{*FNLlj;q zRIN5kK*sVXWowPKPau~qg&$TjIsXK9IsRQ&kV5)8CNj*w0hrM~&Hco%cI;7nM01(* zlm9+Z7^>bvH7*=ylo#i_wC^n*E1a7+RO-mmMGs<=DWDK4^ZAW~SfB#!-;t%i*ms{Q z++3qXcy>3m*PfC4E=M!f5rwn=}-1kRfl zFQjF%nZ1qXFH5|x)%Qs~{4L>5UP@&3MDf+b$J^qZn5q67K`mbc_tp8;?OWnc`DlXb zgehb_mp%*M$_WdfK1b`G|KwE09vzH0i1w;^Ipn$qf*D0XaUG6VkgTa_Pd~SQFNeHP z0P;d*yA*?Cs^tlYCd`RF+B$c8FmX(JEwaIz_UtR4g zN>iN8hJ(JkvxReB`uwdK^y=jBC`K$ABFcQNiCqLg?S&ZLEi%x6zIGhxiQ_-TN^PWU zUT0glAm&g7Y@Krpk&J{A7`5KIV$9IETzvH82n}GcOgD8BY$N%rz*j58mdaxTkC5Vq zeZi<+$n0@Tn!QMzdDS9h%78lUm8_=;Um-DR#beuwj#|}P1B+H@-8bXc9eN7Poe#(* zkec4Y`_;920knCwDxBZwk%804)jCaC=iey3^cZL96_?K_N@6}_T{_Y@HZwl@K0l*) zDhA}E0AJp`&^fQg3X1~s_}%Lw$i+eTU|{OK&XA|U5O7lRr5&An?4zQ`+wW5+ zW?M8F$04?KKQ$v*V9th&Y{BEz*%C&KuiZwcGhaD*pRbb--Cy}#d7?;Nf)(*Xt)H&# zYyuFM0;mB`XE2TU#wLnSH~t;(Py6%da_uiAKpYA18=315rb#siIy_5@4~38o|A+!n zJBaP(*WBO0%N2M{0dX_5+e{=xD*93+*XCq~ zi0vQ>rh%>9E8fz|e4Snn`#x$V9JqYQ#{Yiv9EJu~0pQifdF z;q;9Q@;M;qTgdt4T|y4pDbH!^)O%+q9)#+;>xC!ub>g+J$(nZO(VRe4zXvjW(Iv|F z_GHc$e&k6)%eVvcy!{oav}{|Ef)O+i?%mwA4_N1}yJH)|;kP;9A^7=bQqWgc?}H-r z6+z+R%TP{&Ua&asMvG3IYW-{&j0qIJqd-u=NGbb!N)^Ji{_UAPq5o=H4CF)8qS$4o zdqG@L9@ms`fcYXHRo(4N_HVW-h^ja5Nq=*))9v2`IrtL>vWskn{S-<81}JlBs6wA# zoofF5_gKmCM>5t@I5*cfE8f5G8cE-0Eg3KDN((_N zw06y%kC!JEB?pG z_dcdgV9d}&yBTA|sUfMuo7qldVM_AheZ=nr!6>-_x;_!C32pqVW&0aziN*P3$0iQ% zvnoESt?t8)j4H}Z3`m%!D8JFF7HYgTWhL^91Lc|4<#sZ+PEPl}Sj+y%03ag{COZk< zK+hz8?!Gf`prZfvhomZ0^~D={uSb(Z)^x1-$iUF&?6h!+E^JMDKaKcK5++uo(_XHQ z511)-b`;h;gaPFCbiSRKfEh~DCSf80M-u7x&Ufj~7G|2*-@_Jt5Q0TQ4HNY~CnY4L zVXVRs4?OAOl&M#NXg*lz*|l}({J@pF{SoOTsk{e!N_CZ$J+-V3Y<%^6Voqbzyk6zNjO^yP?|>XTSFtu;SE(ZF55>-kfC?h?Ld-F zd%2|bH#&tgyqL+t&1iYl3Nx#FvkZw*?%#EH&H_;^<$+5vX6F27=?Hz)pxDRHgDQH} zH#OI%GfrsI$B2cn`H0HoH?yj+4QV$WPVJ>yC@ zP(XJIUDR(+N$X=eyCxr9W#B~0Y#k$m`7OH||LD+>5`a3Ny|@>-B|{Ui6U1=p`TvlW z!ODCPHzD3}L2;HpbelqMRdL?L$A^Ri)!J{v+W^I%k`q{z_0P}&#dm<>JSXzD^~*A6Vm!<9=%8y41-BjO)kGpbc9{xVr_K zVa}c0p02F83*;J|8!gnFPFOgb>i>EHR^31V*0tLg@QPe`xR<8NovC0#U|8whM{tgk z=bwOB$+HuXgw$kD#Cw2R)YxOQ<+y>jl2UUQ@Q%RLQ~QHz-*qeEGZBC{ht$l)^y{*R zH}J=@kMIDudr(0nx)nF3lapSeZ9r17kaG4m6Tbi({f8S90nd+#bd;?1-z^LyT~Mje#wWuA!yDY&iHKeBC;i7Dvw~lWWS@4-D84()x>Ykj6;hhixRDBr|uc=m160ssWhYlN$e$ ze%ncqaxe@nHnhBkQc!|0O@OrrWX9_l3FJ7zf`q)A7M}o2*Pu`0PXvw!cc0pCe)$1r zOQeiY$=Dn&(e>=uW^G%!#NUVtMRB0;E~WwWhx1iLzHF0EK6pMKsC*uN6FNs>7s|Yx zo_az^_mz(T^!%7J%xxkSiU~knAA)XXOB=*e4I|SiHPUpGvuu%lroLY!v8RWM=ig<9 z^8ci5YpCcQTMlCdJb#&OC_}D1s0P%oxk@%mlK$Hu6vh`Tz=1mA%7K(IloFDrj30PH zv0(H6`p*kqa8zq~gqvsU;}#niRn9uR5b_4%h9{>;(6__dyKG!mx9 z$#i~>)~BU`D4DK`zzcr?fQbfuL4_%O)J^y>5tJaU3TE#90}6nuUYLMYFC@{^k{JxP ziA>^d&F4pD*a13z+h_At+5V{AVnr7L34a3E&A7sf#a~ZFsD=bzPJK}FC6-H)mXPwu z^dap+)>J((DT|cimcHhAw?I>00>05yVF8T+ALHj_p6@jQ4TS)CRFa~3LMU)Sm!^KKG9KMFx6BZhC+q}RRBW-weM3wWBD8n^y z_u9TR=wPH=PF)SS{i!H2pZM>D*(~VWEe||XVX-I^HSTg%58tB{26Gjkn+YiqqI;4dC&hLD zI6g!JSd)E;p@wwcP-V-NwVY2q*gR2u-bnnh>OHI2`HGLmf_Tx7=U*37BTW z&+<7mKC|>CFzR!RYV^kaD(?4X;ho15|EA;N)8O;k(ES%4BMnQ53CyOk*uQ2R8C8|i zF>B#cS6Vg3RMC1SKU=g`b~1hDz9%30vv0U)b(Wyy(p+AX+M6_>w@bA7;{D~*+w0xK z)pP9AhQK1gM}mD!hnhf0-)iK>2{;KY?vFn)Jd5N&UXi#Yums~FyispNx*h}SA?rgh zzema)_{9CnVysuasjBDW((&JPG+`e*?Gqq)6|F*@!AthD~Vk>8(#?uZxvlb%sY*#hZ}ET%Ic?^PG}7$b^b+)xczyV+nc=J zZJ#>&f*B98#1T}I7q3=d%>G?8V8Voxw7hT7MYU!lwP!xR>{&cAuRFr<^>o*b_f8u&8a zCuKu{y-3oQ8u-pYTSK1!O(8fv?=@rm>T>5!?gHTr0`>0h)lA8|9EK;kU{jV&!M_CJ zQ*ki;vf*nVfZpv#|GdXJ?*xok(m4$1c7e8=f@J{tb#6p^rIV0T2Y$KG+DMu6i%HZ9 zN7)>gqKC*W>Yuo0vD|>X$1lC!g0JH&rRk3C9T;E8Z@3p|Cp;I@lz{p;N)*Av(5Uza zTo}$~ZR5&p2ZqV#*0AONJmPK|z{J1UOrDDHt%HP*=pb#SnSl^#O|sH+#*U0afNVgE zfsVQf5zy8_#mG+<^z$_l@ft6$;h6HtRpp?^c{;tQc>9*g)$fouz6Ro{KYp6g>QWGa zj@YgkzkTJM$#~B*$7T-kwMhDd>nXt~_oAK44&A%E{00DsG-ZqkN&Sjd%DI%nLmxz( zNE3X?k^sH9aN2!EWf=0{(K}yA_IlP)K{Ra+F-0L z6UU8oC33kDfyI^Lp-C1ABU);9KI{bvVdF{e0p7OqM@45Eg`xL zJ#Lt>!-yvASC2}Qvw42nG7BihA2jW?NGMwj);zEZIheT=!xtD)dDL%9ef%OpsGAz> zdqm^$?jm#GsWbOe&MxQO;;8>x_5lqFnro{JniRcizANi{@cB{yN^)U9w(zt9lQnp+ zI$;^h1vj&%Yoc*=NMTJ`yd8@@yT4=~9dc<8XZ)=!bk|PlPPtC02=F`m*fM{=k3le^ zeP}#mpK|S4>h!|^jIaZhBrFH-E`f%-dH0gn4yCVVZbeFzvIzk}lp)wkp5*(M6&9?3 zm>hfhbMPlQhg|R{6(R_S0%}}PTj6>cErA~tq{6C+oTY>>GoXfVdG*6o0nG)Nc%)}2 zFXKZ}9}m_Gt{uG*Y>nfCZ~{8(w>l0cb`o_stuGUb-_#0lL9hYM&2M(ggOZz#Hg|%( zstNF6#KSgbS1YP>mp^ic4S91GyhVqihenl~wiuR{B4XRXAf$|PJ=UiM#9|q^=be7V z4zhm!*f0|k^V8?M8@M5trrPm1>359?V^D-I+S$n-05q9RkShwIKCc{MflGq7ZUalt zXr37u=!7-+t2Ht(OlPUwK{LE|q4n&jME=M*{)C2@8$2|xZ4@0jKt?C2=9gC+#(@M( z5pPX7h%e6q1*(03S!87LnSC+%Wj6z<5pO-h<_4!FdG8FAhneJXc1C9_%+BpOTJ`F9 zO3BWwMN^!<8kx|7oJ@6xW>J81$f^Ua5@|Z~Jg_QME%Y_J>sNa?chR_LR?(jM8%+;jh}7M zrAJWy5qs&>WKcD`GE07)5Dyi(k@CTS<5}DU2uH76^LO1yybz>pNrK?RGT1%wT)}~n ze$|98x6v?(ao(HXY}r7Jk}=7sRiZJ+hdYss_GkjVUswX9_)WW(=CwdD6of=gcIE{@CMh|S-zA`sBEuj z(M+I=O*pm2kwo!Jo&|$jZ>IN6kJpqfJ*?D^CB#{?yJ@2SuS zM~Na-*seSlY!PSb2kem$FDvb4S;}+NoWwBIPeh@zWDN^XM@1BmBgLkStZ4A7tt4{f{DC57h@)rNI zgnrI=yubItc{WLd80T7p!HnfQ`br)CMfk3yTZ1<@;5_zBl>K0Ad0OxdCX$LzpmHZq z2_{l_0dTQHsqLK_96+tIV;K?QT+dZ*atHqU6QeOI-(XFr(%d485ml!tdb zGF*^5K>w|(YsLZb_nY|3D5Lv6j-m%VxtrYaX50sy0O4nEUn!EIB}9GX=rj$${5@a< zM;ySl&OOGS4B#vg7i7V!E-?$?Tq(q?m}|mZAudbqoSYJxPa`$GLMOJG4h5)?vxQ#C zeW}5IE2=Y!+h_O81$xNaI#y1>iYG}W9=%Du@JtJM=0-xU+;zUJM<@Q^-zZ^F zxloi@@Qii%1>+L-lCQX(te-t5j3~Z5#I;QWQyAZs7AU;9OS`T1HfE2OvvK2yxMal_ z-)-gn+>i@8BBkQ0!lk4Q^G-McMXv0mrJRxU0}%+~MFg2xw0BTb+sTx`C}7Yrfr5pg zJezZ=TvDh~sgFj#A7YkalYb$oY?fc(z$sDc^~rjIko`D(=(AQ9*3gPAt;%ZBnT6Iq zA8wW#!WT^jK9^Y{5$D;P5hks--;jOKkI~@=U&Pt{6jO&qShwc4*(_(x4)K5{p-j^R z4}S)u0dp4#a$CWz66hhMiR~wlk9iRW5B>W=NDY^UQHoknu2-@F?lr`&rRe&_;2SdF zEB6=Nwu@&k3P1k`fwJh^c0F| zkyF=}3V=IR+7@yEwhdpON;XfxsBCxfd4)9+(4FLhNHGeQzf3$aJ)`M6r?`iXbcP_) zOA$q3-STIitiwoLURV^V?cC>dB{?qXZ?XY2nYqE1v~1XAfuBjCZdRC^Gm%4*&+Y@N@g0|frl8gq;)KKa%nt`%3Zip|0JsByX z7P&HCO29Mj0C4e`e-C8><=S+eI&DpNFO-ri+0SBJm0Lojk@|gXE;D*;e~sm$>DOI3 zP)s4b2o@tYrE@wR!^n_9jX@1(16`_6wgKr+qzg+&Ov77cvLzM zcc@eVO+6liB6yD%tR4y6>V}icotPoZh;*I7OMWCGD^kb)cyr%QwoM$g`h5_8i!~majHZ0w@MVSJW$>Cu8$a4zIcTyNo*56ZmN2Y(S-QKY zIyLI|Qt5~k;Eje}L;qbU%EH9TKd&^iUDqZV%Ou3u!HnwSQtjF&gI&V_xv&Y~?_#?5 z^3)rpV4t8s-|B<1BOxkp!Y{-h=d->VD&OH1Bkqqm<3}=avZMZ6lq2Q{`>!FZA+noj zLdr=kY;$G%zP#T(bDynz{qPhNL#T)?ZswK2-{F_mN{%56my`|!M?UrOma`kR2jz)( zHk$*piT`T43$^s*QxnvclvCg#0Jh9CRK3|yAybvy2#RrRjE++3Y8x058MJ5#-Ob0ngHVP-tD7_#Lm zal=&Vt^P_7tkPetIVaN<$OIHF!`7UwunGdcEn~aKH`pLZUQOy$zNZfeX%Db7T>mJF zhpYYObFYv#r0GUd%M#V@PA#ER)o$+97=ylzi`t6gVvc*GeKS=72tM#%FI8pWX?{c_74hkS(`YwwE0CE_`lkT}wb@wD@z#&L(sa>qfg7kdzZgd6xV zO6^X1!qW+4wlnn5|E0r+h%3LQq=J=A!M>A(U{A}CNA8Qh$Hs(pxrOFO0g2~Wg*!7J z;OTD}92${l8mJzCCJbrF>g}-NBQM`tX0*3*C+%o^V}4|zEt&FDefcljAD{6PLv-N_ z=d+kAQ~;>=ZjDLUd?Bx8h@ipP?D57mA8+iXU12tOU>w zjGB6jc~0=F(|FP|r?UMl3PVbRA9@$RZ_82>ZZj+a_W5a1|DzQ5-t#eMUld@QI(``I zKHgj8_$TcPg(z~aXJ8!I@T@hx|^y@4Zmo-}irv7NY#1oIrO68N#;?X?9PGLj)1$?Ak3qa^&B~e!V$gd+KWO zST()FBXDOB<=OI_8;HHVTJ3@`2+@n$60%TS4C ztfhSPH}iI&hKhGl>uo8mTGpD5r|8}0xuY`cGgt5mD2uv$ZfGr{^0bG-kmGtOT{mrr z`|*X#(MpJ$8dD}kk*0TRK#S7@yyB{UmQ}4f9%?r&C1Sjr;Q4PMr%6fX`D9-41RJ*i$E^F^G5k@X8RXG+{!1o%re-Na&+LHo+Z!Z?PeQDHM~6 zb5vo!8}#d4D5rT(`0~3rc``BtYVOrPI^L=Emi}p3KyN(6FP|tLk$fog98ls%)fd6* zH!nkaUWl}RYqDb>3SdUP2x|;>JM-Y4;BxuKX04ih4`B0V!K{q23L(&sNNKH>g z2n^oa3_+d$x$r)TM~BDWs(bu0Ba#Ic58LwgqWI7x?TG_@Ue9c}I7r#Q>Eih|`Q6`M+*Ebx?k^*)r5b3O|*ksay1!nEL>o*3(-zB#BW7yDf9sd9zTJb{s-8&_p0* z<+&7=afb*qUM#1LN=Ty}p=l&ew}2Sdc&LW>&%7S(E^WVK=9?Y2cvUDVtWhWlk&0vd zfj!_s#K{fC`&JS%8%Uaj(kl)KD4Ij9uz?$&g>{YQLb!X8w1N+Nd@HrS#I5k%^?R4# z3EC<&KzwfDp>a;1Uu#mt_o=KFK)^#sn~kZ-4&KE*#RAsCgZOBF#*~>tf?Z+)iAhu* z%UaNqB*}(Wv}Ox#96*E~{p9vkCq8b{z*?j0-TBvY(wbfyn=3ei8ZF{ESaLH)Cg?10 z8_dxFutP+J9(vOmMuOuXJ}KPUH9jdKiMt0KxPgDrv%JlhDg;bN+&5did@UL)ezdjODB*&eFR!;`!|UH?W3gzkR`a!I3IC z|Kji7a<94=yVLp%2vFW%*H~1_!QAr!INljOynW4~T|7%H=+k2F`rNAg6dsNFF<~$$%pupU&&g1|0s({_? z%rJFwlA-!~mOJ1JnFH1%n+rmDGnF~&tYEnpzATI8tXt5*5*%qv#*2~b3*Mvd<>Xii zKBSr{4e{0zBIUYn)CI(RC6ao487w5fE64_210d!2 zQ=hgZYQWIP=OsyMw`CtCVp-I9kcj9YeR9}mYb&Cbq6{&wO~u+B#?90*Xv>Z4Njoxu z{bcSVgF7rCS5G;)2$hT&8N-XfJiqr9qGJKR9Y3++|F`{l6SiEWr%zg#Sj>g4CT2vT z5DB1N%kIIUlZvf`0}l7tNQ)QeN7)ZgNKjJmg$qbO4w+k)6T$UMBjGEgj&RC<12#h| zE*eEYnj-CRy+zT2xE)+TWLFFJWAb>hwhY{{+gEHaF^Wh9)Abw_aGCStc?Ans)UA^{ z$b6;U9V;|VmgOGEG}Ae4DhZGb7!S0U2suPx4mGY@NU^;SqQ2*aP$S8%4P^{8cAlw- zh_70FLKc}(@u%dT?g^LYSOQv;%+FU7cT8t5lqNVOu$TNv-H!8GDnw8j9th0wCMEbf zacttGAe?#V&}1etz*;s|Y=^|5O-d=ulH~mAp4LJ6+dv^;Wr)NMd(Ds$)6-&e-OlF=NaLn! z7u&tmlWSN0r31W=z8A(f%%zqNw9mqAEyj=6B~EVW z-nWZG%brq)3U0J4ZJ@n6xSBxv+xf++cS6z(t_e*y++dU`TIE;EI1Pt2>vpv>3cU00 zFabo}@iEAE;?Y4NETn?P=m2B{-(8g;3@e1_+^`9BReJ8;6+3QbpH_GfxqL#Po4#(d z{@nZP=V~k8U$toDh^t83c%#@=&cJjw5%IR~E58|>L6A1}I;mBgMZzGZ__qliKDaG| zT^j?9GCivTmIz@>!~+0Rs*w9p0Nvo+7p6En~bCHE|6Eq)JVYo(_fagV89u&CP zOcT+qAl?|Z=bZM5+dNW0jwE_EwEjrrE(b4JLa-b4ZUb zW~7Fyj7Mrhdbku;5cDTpf#`jNh1pxpGYZU!P=J^CoXZoRv^X}47{Itl=utGvH4+f} zA0U-}(w2t_NKSc|zWvqgP#k`6Gj6?tDucQFl1An)+udN2FT4qk#XtQ!ZB%KO;N~|Y zN$~y-Px7xZFX{z>U1nQ-o>qkY0_z)qnm8(8Sna0}_W&aJV|>bMrREn;=~28-k7~~N zqu0^P&+`EnfdNi$dt4OjN0`%1?8R<4d#<1W;!BCM(sY;!#@QIg|F($W`w`D=sF4L5 zl59wp3l%1Jt#MINLvC_|V#^cv)cfxdmn4TS?{o`aOvpM{UBI(^5;Vk76S%lcex+@5 z6FF&YrvBFVJ^lonGTP9loJdE%_XCKx+LHoJjqvoMlC=u;{TKuDV<{X|;~6DTp`5x( zv$Iy8>mum_wbMm-)Ta~RuR=f6GL1FwjGl>B30r)G9NYsF&KgM%2&~WQxNon!noG z<#{*Kig5lbe90532apK0E2xwh`n(8sy>#C^PL|R?8V=87o zN^KSwm&U`{%S8Nh^l-o{X$a${BLt&H`kE<$)Z&gkTi!A7yYr-p;^{k6;eYvJy*=8x zHnV@Nov5(^X!-ZYkgyt{$wstfo#^7OKdK(!%y_+3&wY0hOndaw7w6I#V}i4TG+_yK z*jPgW)Phya%Px#V>HR`V#oKNFp2RBz0PJG4#{?Yi4BMej$XaErevJhDZkEDgA0FJs z)wC;GdT!9IIa6S*r7~y+_HpBw6`rVTQu8cvMO6PxuV%=8HTrVxCF(OrjC03U;Lq1F z&P9|G!7M_-LR9vqkF+kV_>mJUxszsWy&_oGo#ZbG&%X~HGFUQ8Uq^p<*qS`4o{J;R zlXjx}C-5Nf+}F9dovYu7Cln6QJgciQR$lXjt*rh3B1oj40sWoH@aopj^~M@)cu+6p z(CBA~b(Hv*WcvQIU90!T`*|AMcv*x1vkL&ae?m5~s)HD=SBo#zAr(k-gP1@w@_T(} zS~r*|5>P?-oiSJClYk>YUqm~D;#+zo)UOhmIsw<1ifWcfb&Yv($cM|8*X_mqbeBRI z950D8LL<{Uqo>_PbtQcCb_u0mSEl$scfWkBB@^SHpsqb}l)ZsOewOUIxk7Q_4U zh>}1X)rJlKH%e)ZKBCK&k(3&MirZ%HT=i9~RV)d_#3H))N9lyIkfzea$KUpF6R=YO zilWfkxCoy&Zr|~m&)+9VN;O;SezcJfDBQyMEu=Ifz}zBeH5Fi~TTtvEX=!7oEqhYL zfI5}Ysmiju7nAWGb$5+KrqLT^+9zxB9a|Yvs^z~6gD+5M`PC^-cCXSiq8_Pjij5G9 zGY_7n7wnTH>3b*SGd-8Wc%?*QSOs9VP zNX#z6V+umeo=f8zUCfHDto2JTlY3?G)I0aSdHBjER_%6c|0Xx?na{~$)(k^?Ad&K- z9eqKWKvIV_yTDO_#CI+I$P8y(APc#j-3_7C5ToMJ|Il<5eob&+e{{EWcb7;=mxOc( z3P`G?geWm$qgy~ykp^i7(h{RvO6l(I+F)$&e4o$nFSz%fKIfkEJ)m7o;PyxcXI))V zE?$d;={+LpYTQj9De1$=<;WGN{lnbxACIy*0gpa{PRe#ecAuWZPzo{w_u)yGY)Y8A zP;AkZzPl-OJ(l*)lkD@m8b|6FIw0=jL7=G_Ui?7cJO2x+061*7*>~$!{`Oul+@nsSsFr|EGMCaB@134p*(_1)60YAJJD3VOhvT0& zwW%53j`7+^Xn8~=tj+i@+}KQaOKw@TqhnU6`s=YDgvX}{KxemJR*AKW-7%DsIQk|` zfmLG=hl{lnwQj8>#og9ogz(|^e1rS>O;T0&cH5p&N!^~D@Ab3bN}@l6U{A8nerk02 zP+s_ruTmvMZV5qo*$cG;yH0ne#{o>5p9zKIV|e=Em~t{n>2Va1NKmwKAE%Q;DHSQ; zmeGII|E7rvCznOpjPz(+oRojo6>PtJ+?k?m;J^{2BV}K`R6m+zHURwl^_-o4e30C@fCZPAkSG9@$4bwH2(kJ(TFw;=N@iFXWVN#@^Dy(dI=$( z6jDtM4d3wa_AG|QV1Hrhv%*mM@}3DR_{rOt^;V2(Yjq~VBZh0zt~>rvDa^-x5kP`b z$zmQ%W#^Y@Bp=hqH6=}i7=0b+=9ul`bO+UVndsw6I;}Ac#N8+0h^#AP_d>(LRLCC0vyL5fNY&cvN2 zrRJjaOkg+M%}cO1ibIke%%{`nE@)jC%!HN-hIxh)?2Y^p*cmADlH6016Moojw$sOo z?zkZ@ief}Bu{t7tDU7`0SKl5!vRA$$6|C(!axJC<-o^UYWOQ0B-f`*WX=PyPUjXILHDwQrE#R=A5z z*rcB$tcVU5GXTHP(t1P&CZqI!I)`2C!W82!)ejJ5-x>x?Ke=1vGS9t5t zL1HlwEKG}GD2`8wq2B9%^a8!^N|zcdvYvWCS*<-drLJq{k7!t6XlMFOc@p%aY2P|M zU+a252hY(%t09eJd6WK{aUA6BVA>GIX$CXUR4*h(lNN18qRg{O8LoHRnP0OlqLuvCls%#krz^AxO z_{55?Mi!EwtEdq~-9v*dI21XPQ>^{EGOcrAEBTKpS!_j-Fm1lP0 z8;7vnKwpzd=ESYAZ!It~< zK~t4eL&@iK2IVR#u>jlpSLD@#=P>}w4d-xWWm&4S``CDPpk;7Z4^g$q5!Zfw^UI5- zdF+n`0|TVvVEymFrPOxN<`_uC%T7QI{z@9fk78zsZcjf4nIx}oq9&pNXXJ`vc$}Ivmj^7?j;ty=ZGdA=PCoSZt2dt?$IA@sTooVtZ%RT+!nlyp4!o*P(sK1 zrw_>nZK&O5R$9Z>vh9B4{3`yQB;%UH^EMArLFHE!1$pf$T$Qi8jJ4MMn}P~pV?dj+ zN+{X5QJ2WfMX<}z((|8Mclw~0LG4eIB9z#W+iP`+?P^>?U>Kk6^&-hngj2~^VVPWX zwOLu5qQ;k4@H|5cI5CeEHZq%sKS4!087-~cAU+=vi(&b#O2ZZsWY0(o$J=66KZ*3) zkCvXN-z=UVqjB8utGcz$o)@in3m;B;9$Yv|lBE2FZRf_~@I}{M+6I=bom20_DzCyf z*v%>W&z5>9=48V}&gGv+HUqc`79Am gvT@w*0M|;(`oV}RQWLRkszv!Y`Z3zP` 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 +#include +#include +#include +#include +#include + +//! [0] +#include "appmodel.h" + +int main(int argc, char *argv[]) +{ + QLoggingCategory::setFilterRules("wapp.*.debug=false"); + QGuiApplication application(argc, argv); + + qmlRegisterType("WeatherInfo", 1, 0, "WeatherData"); + qmlRegisterType("WeatherInfo", 1, 0, "AppModel"); + +//! [0] + qRegisterMetaType(); +//! [1] + const QString mainQmlApp = QStringLiteral("qrc:///weatherinfo.qml"); + QQuickView view; + view.setSource(QUrl(mainQmlApp)); + view.setResizeMode(QQuickView::SizeRootObjectToView); + + QObject::connect(view.engine(), SIGNAL(quit()), qApp, SLOT(quit())); + view.setGeometry(QRect(100, 100, 360, 640)); + view.show(); + return application.exec(); +} +//! [1] diff --git a/examples/positioning/weatherinfo/weatherinfo.pro b/examples/positioning/weatherinfo/weatherinfo.pro new file mode 100644 index 0000000..e2b2102 --- /dev/null +++ b/examples/positioning/weatherinfo/weatherinfo.pro @@ -0,0 +1,21 @@ +TEMPLATE = app +TARGET = weatherinfo + +QT += core network positioning qml quick + +SOURCES += main.cpp \ + appmodel.cpp + +OTHER_FILES += weatherinfo.qml \ + components/WeatherIcon.qml \ + components/ForecastIcon.qml \ + components/BigForecastIcon.qml \ + icons/* + + +RESOURCES += weatherinfo.qrc + +HEADERS += appmodel.h + +target.path = $$[QT_INSTALL_EXAMPLES]/positioning/weatherinfo +INSTALLS += target diff --git a/examples/positioning/weatherinfo/weatherinfo.qml b/examples/positioning/weatherinfo/weatherinfo.qml new file mode 100644 index 0000000..b0b2a8e --- /dev/null +++ b/examples/positioning/weatherinfo/weatherinfo.qml @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import "components" +//! [0] +import WeatherInfo 1.0 + +Item { + id: window +//! [0] + width: 360 + height: 640 + + state: "loading" + + states: [ + State { + name: "loading" + PropertyChanges { target: main; opacity: 0 } + PropertyChanges { target: wait; opacity: 1 } + }, + State { + name: "ready" + PropertyChanges { target: main; opacity: 1 } + PropertyChanges { target: wait; opacity: 0 } + } + ] +//! [1] + AppModel { + id: model + onReadyChanged: { + if (model.ready) + window.state = "ready" + else + window.state = "loading" + } + } +//! [1] + Item { + id: wait + anchors.fill: parent + + Text { + text: "Loading weather data..." + anchors.centerIn: parent + font.pointSize: 18 + } + } + + Item { + id: main + anchors.fill: parent + + Column { + spacing: 6 + + anchors { + fill: parent + topMargin: 6; bottomMargin: 6; leftMargin: 6; rightMargin: 6 + } + + Rectangle { + width: parent.width + height: 25 + color: "lightgrey" + + Text { + text: (model.hasValidCity ? model.city : "Unknown location") + (model.useGps ? " (GPS)" : "") + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (model.useGps) { + model.useGps = false + model.city = "Brisbane" + } else { + switch (model.city) { + case "Brisbane": + model.city = "Oslo" + break + case "Oslo": + model.city = "Helsinki" + break + case "Helsinki": + model.city = "New York" + break + case "New York": + model.useGps = true + break + } + } + } + } + } + +//! [3] + BigForecastIcon { + id: current + + width: main.width -12 + height: 2 * (main.height - 25 - 12) / 3 + + weatherIcon: (model.hasValidWeather + ? model.weather.weatherIcon + : "01d") +//! [3] + topText: (model.hasValidWeather + ? model.weather.temperature + : "??") + bottomText: (model.hasValidWeather + ? model.weather.weatherDescription + : "No weather data") + + MouseArea { + anchors.fill: parent + onClicked: { + model.refreshWeather() + } + } +//! [4] + } +//! [4] + + Row { + id: iconRow + spacing: 6 + + width: main.width - 12 + height: (main.height - 25 - 24) / 3 + + property real iconWidth: iconRow.width / 4 - 10 + property real iconHeight: iconRow.height + + ForecastIcon { + id: forecast1 + width: iconRow.iconWidth + height: iconRow.iconHeight + + topText: (model.hasValidWeather ? + model.forecast[0].dayOfWeek : "??") + bottomText: (model.hasValidWeather ? + model.forecast[0].temperature : "??/??") + weatherIcon: (model.hasValidWeather ? + model.forecast[0].weatherIcon : "01d") + } + ForecastIcon { + id: forecast2 + width: iconRow.iconWidth + height: iconRow.iconHeight + + topText: (model.hasValidWeather ? + model.forecast[1].dayOfWeek : "??") + bottomText: (model.hasValidWeather ? + model.forecast[1].temperature : "??/??") + weatherIcon: (model.hasValidWeather ? + model.forecast[1].weatherIcon : "01d") + } + ForecastIcon { + id: forecast3 + width: iconRow.iconWidth + height: iconRow.iconHeight + + topText: (model.hasValidWeather ? + model.forecast[2].dayOfWeek : "??") + bottomText: (model.hasValidWeather ? + model.forecast[2].temperature : "??/??") + weatherIcon: (model.hasValidWeather ? + model.forecast[2].weatherIcon : "01d") + } + ForecastIcon { + id: forecast4 + width: iconRow.iconWidth + height: iconRow.iconHeight + + topText: (model.hasValidWeather ? + model.forecast[3].dayOfWeek : "??") + bottomText: (model.hasValidWeather ? + model.forecast[3].temperature : "??/??") + weatherIcon: (model.hasValidWeather ? + model.forecast[3].weatherIcon : "01d") + } + + } + } + } +//! [2] +} +//! [2] diff --git a/examples/positioning/weatherinfo/weatherinfo.qrc b/examples/positioning/weatherinfo/weatherinfo.qrc new file mode 100644 index 0000000..7b79dbe --- /dev/null +++ b/examples/positioning/weatherinfo/weatherinfo.qrc @@ -0,0 +1,20 @@ + + + weatherinfo.qml + components/BigForecastIcon.qml + components/ForecastIcon.qml + components/WeatherIcon.qml + icons/weather-few-clouds.png + icons/weather-fog.png + icons/weather-haze.png + icons/weather-icy.png + icons/weather-overcast.png + icons/weather-showers.png + icons/weather-sleet.png + icons/weather-snow.png + icons/weather-storm.png + icons/weather-sunny-very-few-clouds.png + icons/weather-sunny.png + icons/weather-thundershower.png + + diff --git a/include/QtLocation/5.7.1/QtLocation/private/qabstractgeotilecache_p.h b/include/QtLocation/5.7.1/QtLocation/private/qabstractgeotilecache_p.h new file mode 100644 index 0000000..c483a2f --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qabstractgeotilecache_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qabstractgeotilecache_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qcache3q_p.h b/include/QtLocation/5.7.1/QtLocation/private/qcache3q_p.h new file mode 100644 index 0000000..2f3ebc0 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qcache3q_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qcache3q_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeocameracapabilities_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeocameracapabilities_p.h new file mode 100644 index 0000000..c237979 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeocameracapabilities_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeocameracapabilities_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeocameradata_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeocameradata_p.h new file mode 100644 index 0000000..0639753 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeocameradata_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeocameradata_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeocameratiles_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeocameratiles_p.h new file mode 100644 index 0000000..80f6aaf --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeocameratiles_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeocameratiles_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeocodereply_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeocodereply_p.h new file mode 100644 index 0000000..2524050 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeocodereply_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeocodereply_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeocodingmanager_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeocodingmanager_p.h new file mode 100644 index 0000000..043ed36 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeocodingmanager_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeocodingmanager_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeocodingmanagerengine_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeocodingmanagerengine_p.h new file mode 100644 index 0000000..5ce24ee --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeocodingmanagerengine_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeocodingmanagerengine_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeofiletilecache_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeofiletilecache_p.h new file mode 100644 index 0000000..9fbe35a --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeofiletilecache_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeofiletilecache_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeomaneuver_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeomaneuver_p.h new file mode 100644 index 0000000..cd5008e --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeomaneuver_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeomaneuver_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeomap_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeomap_p.h new file mode 100644 index 0000000..b7327e4 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeomap_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeomap_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeomap_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeomap_p_p.h new file mode 100644 index 0000000..3407c69 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeomap_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeomap_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanager_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanager_p.h new file mode 100644 index 0000000..9b03ed7 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanager_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeomappingmanager_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanager_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanager_p_p.h new file mode 100644 index 0000000..a9d965a --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanager_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeomappingmanager_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanagerengine_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanagerengine_p.h new file mode 100644 index 0000000..a1dd77b --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanagerengine_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeomappingmanagerengine_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanagerengine_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanagerengine_p_p.h new file mode 100644 index 0000000..7d99d28 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeomappingmanagerengine_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeomappingmanagerengine_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeomaptype_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeomaptype_p.h new file mode 100644 index 0000000..d6f8348 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeomaptype_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeomaptype_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeomaptype_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeomaptype_p_p.h new file mode 100644 index 0000000..93fb3ba --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeomaptype_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeomaptype_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeoroute_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeoroute_p.h new file mode 100644 index 0000000..aed5df1 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeoroute_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeoroute_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparser_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparser_p.h new file mode 100644 index 0000000..75a676d --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparser_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeorouteparser_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparser_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparser_p_p.h new file mode 100644 index 0000000..a40608f --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparser_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeorouteparser_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparserosrmv4_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparserosrmv4_p.h new file mode 100644 index 0000000..0882302 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparserosrmv4_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeorouteparserosrmv4_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparserosrmv5_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparserosrmv5_p.h new file mode 100644 index 0000000..d16d23f --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeorouteparserosrmv5_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeorouteparserosrmv5_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeoroutereply_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeoroutereply_p.h new file mode 100644 index 0000000..73e5640 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeoroutereply_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeoroutereply_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeorouterequest_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeorouterequest_p.h new file mode 100644 index 0000000..00f7941 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeorouterequest_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeorouterequest_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeoroutesegment_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeoroutesegment_p.h new file mode 100644 index 0000000..77cc84a --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeoroutesegment_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeoroutesegment_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeoroutingmanager_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeoroutingmanager_p.h new file mode 100644 index 0000000..1824922 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeoroutingmanager_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeoroutingmanager_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeoroutingmanagerengine_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeoroutingmanagerengine_p.h new file mode 100644 index 0000000..6f11de9 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeoroutingmanagerengine_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeoroutingmanagerengine_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeoserviceprovider_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeoserviceprovider_p.h new file mode 100644 index 0000000..88f9999 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeoserviceprovider_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeoserviceprovider_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmap_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmap_p.h new file mode 100644 index 0000000..718b0a3 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmap_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotiledmap_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmap_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmap_p_p.h new file mode 100644 index 0000000..10bf8bb --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmap_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotiledmap_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmappingmanagerengine_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmappingmanagerengine_p.h new file mode 100644 index 0000000..8cda0fe --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmappingmanagerengine_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotiledmappingmanagerengine_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmappingmanagerengine_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmappingmanagerengine_p_p.h new file mode 100644 index 0000000..1ad7d38 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmappingmanagerengine_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotiledmappingmanagerengine_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapreply_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapreply_p.h new file mode 100644 index 0000000..90e0c73 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapreply_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotiledmapreply_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapreply_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapreply_p_p.h new file mode 100644 index 0000000..6563bf2 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapreply_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotiledmapreply_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapscene_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapscene_p.h new file mode 100644 index 0000000..edf39c9 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotiledmapscene_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotiledmapscene_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotilefetcher_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotilefetcher_p.h new file mode 100644 index 0000000..5e921b5 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotilefetcher_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotilefetcher_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotilefetcher_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotilefetcher_p_p.h new file mode 100644 index 0000000..c183b71 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotilefetcher_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotilefetcher_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotilerequestmanager_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotilerequestmanager_p.h new file mode 100644 index 0000000..8872823 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotilerequestmanager_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotilerequestmanager_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotilespec_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotilespec_p.h new file mode 100644 index 0000000..7737cc7 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotilespec_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotilespec_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qgeotilespec_p_p.h b/include/QtLocation/5.7.1/QtLocation/private/qgeotilespec_p_p.h new file mode 100644 index 0000000..8f318b6 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qgeotilespec_p_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/maps/qgeotilespec_p_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplace_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplace_p.h new file mode 100644 index 0000000..1aabde7 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplace_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplace_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplaceattribute_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplaceattribute_p.h new file mode 100644 index 0000000..ce8bd7c --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplaceattribute_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplaceattribute_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplacecategory_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplacecategory_p.h new file mode 100644 index 0000000..ed94a62 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplacecategory_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplacecategory_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplacecontactdetail_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplacecontactdetail_p.h new file mode 100644 index 0000000..c8833e9 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplacecontactdetail_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplacecontactdetail_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplacecontent_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplacecontent_p.h new file mode 100644 index 0000000..4ccd7c8 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplacecontent_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplacecontent_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplacecontentrequest_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplacecontentrequest_p.h new file mode 100644 index 0000000..64ca72d --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplacecontentrequest_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplacecontentrequest_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplaceeditorial_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplaceeditorial_p.h new file mode 100644 index 0000000..c4caf2e --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplaceeditorial_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplaceeditorial_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplaceicon_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplaceicon_p.h new file mode 100644 index 0000000..37a951d --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplaceicon_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplaceicon_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplaceimage_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplaceimage_p.h new file mode 100644 index 0000000..9de1983 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplaceimage_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplaceimage_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplacemanagerengine_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplacemanagerengine_p.h new file mode 100644 index 0000000..2e1be79 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplacemanagerengine_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplacemanagerengine_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplaceproposedsearchresult_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplaceproposedsearchresult_p.h new file mode 100644 index 0000000..b9e81b0 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplaceproposedsearchresult_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplaceproposedsearchresult_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplaceratings_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplaceratings_p.h new file mode 100644 index 0000000..3904253 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplaceratings_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplaceratings_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplacereply_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplacereply_p.h new file mode 100644 index 0000000..fb0e273 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplacereply_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplacereply_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplaceresult_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplaceresult_p.h new file mode 100644 index 0000000..94132fe --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplaceresult_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplaceresult_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplacereview_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplacereview_p.h new file mode 100644 index 0000000..94552a3 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplacereview_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplacereview_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplacesearchresult_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplacesearchresult_p.h new file mode 100644 index 0000000..1b7404e --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplacesearchresult_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplacesearchresult_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplacesupplier_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplacesupplier_p.h new file mode 100644 index 0000000..718cc74 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplacesupplier_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplacesupplier_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/qplaceuser_p.h b/include/QtLocation/5.7.1/QtLocation/private/qplaceuser_p.h new file mode 100644 index 0000000..0dc4089 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/qplaceuser_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/qplaceuser_p.h" diff --git a/include/QtLocation/5.7.1/QtLocation/private/unsupportedreplies_p.h b/include/QtLocation/5.7.1/QtLocation/private/unsupportedreplies_p.h new file mode 100644 index 0000000..396fcf9 --- /dev/null +++ b/include/QtLocation/5.7.1/QtLocation/private/unsupportedreplies_p.h @@ -0,0 +1 @@ +#include "../../../../../src/location/places/unsupportedreplies_p.h" diff --git a/include/QtLocation/QGeoCodeReply b/include/QtLocation/QGeoCodeReply new file mode 100644 index 0000000..14193f8 --- /dev/null +++ b/include/QtLocation/QGeoCodeReply @@ -0,0 +1 @@ +#include "qgeocodereply.h" diff --git a/include/QtLocation/QGeoCodingManager b/include/QtLocation/QGeoCodingManager new file mode 100644 index 0000000..ed7d721 --- /dev/null +++ b/include/QtLocation/QGeoCodingManager @@ -0,0 +1 @@ +#include "qgeocodingmanager.h" diff --git a/include/QtLocation/QGeoCodingManagerEngine b/include/QtLocation/QGeoCodingManagerEngine new file mode 100644 index 0000000..69303d2 --- /dev/null +++ b/include/QtLocation/QGeoCodingManagerEngine @@ -0,0 +1 @@ +#include "qgeocodingmanagerengine.h" diff --git a/include/QtLocation/QGeoManeuver b/include/QtLocation/QGeoManeuver new file mode 100644 index 0000000..a3ff6a5 --- /dev/null +++ b/include/QtLocation/QGeoManeuver @@ -0,0 +1 @@ +#include "qgeomaneuver.h" diff --git a/include/QtLocation/QGeoRoute b/include/QtLocation/QGeoRoute new file mode 100644 index 0000000..fd6690a --- /dev/null +++ b/include/QtLocation/QGeoRoute @@ -0,0 +1 @@ +#include "qgeoroute.h" diff --git a/include/QtLocation/QGeoRouteReply b/include/QtLocation/QGeoRouteReply new file mode 100644 index 0000000..6109d67 --- /dev/null +++ b/include/QtLocation/QGeoRouteReply @@ -0,0 +1 @@ +#include "qgeoroutereply.h" diff --git a/include/QtLocation/QGeoRouteRequest b/include/QtLocation/QGeoRouteRequest new file mode 100644 index 0000000..221969b --- /dev/null +++ b/include/QtLocation/QGeoRouteRequest @@ -0,0 +1 @@ +#include "qgeorouterequest.h" diff --git a/include/QtLocation/QGeoRouteSegment b/include/QtLocation/QGeoRouteSegment new file mode 100644 index 0000000..74590d6 --- /dev/null +++ b/include/QtLocation/QGeoRouteSegment @@ -0,0 +1 @@ +#include "qgeoroutesegment.h" diff --git a/include/QtLocation/QGeoRoutingManager b/include/QtLocation/QGeoRoutingManager new file mode 100644 index 0000000..76e5224 --- /dev/null +++ b/include/QtLocation/QGeoRoutingManager @@ -0,0 +1 @@ +#include "qgeoroutingmanager.h" diff --git a/include/QtLocation/QGeoRoutingManagerEngine b/include/QtLocation/QGeoRoutingManagerEngine new file mode 100644 index 0000000..300a668 --- /dev/null +++ b/include/QtLocation/QGeoRoutingManagerEngine @@ -0,0 +1 @@ +#include "qgeoroutingmanagerengine.h" diff --git a/include/QtLocation/QGeoServiceProvider b/include/QtLocation/QGeoServiceProvider new file mode 100644 index 0000000..d5b38bc --- /dev/null +++ b/include/QtLocation/QGeoServiceProvider @@ -0,0 +1 @@ +#include "qgeoserviceprovider.h" diff --git a/include/QtLocation/QGeoServiceProviderFactory b/include/QtLocation/QGeoServiceProviderFactory new file mode 100644 index 0000000..9bee8fb --- /dev/null +++ b/include/QtLocation/QGeoServiceProviderFactory @@ -0,0 +1 @@ +#include "qgeoserviceproviderfactory.h" diff --git a/include/QtLocation/QLocation b/include/QtLocation/QLocation new file mode 100644 index 0000000..e433f1e --- /dev/null +++ b/include/QtLocation/QLocation @@ -0,0 +1 @@ +#include "qlocation.h" diff --git a/include/QtLocation/QPlace b/include/QtLocation/QPlace new file mode 100644 index 0000000..51b4bac --- /dev/null +++ b/include/QtLocation/QPlace @@ -0,0 +1 @@ +#include "qplace.h" diff --git a/include/QtLocation/QPlaceAttribute b/include/QtLocation/QPlaceAttribute new file mode 100644 index 0000000..05d36df --- /dev/null +++ b/include/QtLocation/QPlaceAttribute @@ -0,0 +1 @@ +#include "qplaceattribute.h" diff --git a/include/QtLocation/QPlaceCategory b/include/QtLocation/QPlaceCategory new file mode 100644 index 0000000..959738a --- /dev/null +++ b/include/QtLocation/QPlaceCategory @@ -0,0 +1 @@ +#include "qplacecategory.h" diff --git a/include/QtLocation/QPlaceContactDetail b/include/QtLocation/QPlaceContactDetail new file mode 100644 index 0000000..4ff2d2d --- /dev/null +++ b/include/QtLocation/QPlaceContactDetail @@ -0,0 +1 @@ +#include "qplacecontactdetail.h" diff --git a/include/QtLocation/QPlaceContent b/include/QtLocation/QPlaceContent new file mode 100644 index 0000000..7ebb145 --- /dev/null +++ b/include/QtLocation/QPlaceContent @@ -0,0 +1 @@ +#include "qplacecontent.h" diff --git a/include/QtLocation/QPlaceContentReply b/include/QtLocation/QPlaceContentReply new file mode 100644 index 0000000..67fc8a8 --- /dev/null +++ b/include/QtLocation/QPlaceContentReply @@ -0,0 +1 @@ +#include "qplacecontentreply.h" diff --git a/include/QtLocation/QPlaceContentRequest b/include/QtLocation/QPlaceContentRequest new file mode 100644 index 0000000..43459f4 --- /dev/null +++ b/include/QtLocation/QPlaceContentRequest @@ -0,0 +1 @@ +#include "qplacecontentrequest.h" diff --git a/include/QtLocation/QPlaceDetailsReply b/include/QtLocation/QPlaceDetailsReply new file mode 100644 index 0000000..1455310 --- /dev/null +++ b/include/QtLocation/QPlaceDetailsReply @@ -0,0 +1 @@ +#include "qplacedetailsreply.h" diff --git a/include/QtLocation/QPlaceEditorial b/include/QtLocation/QPlaceEditorial new file mode 100644 index 0000000..d1a232a --- /dev/null +++ b/include/QtLocation/QPlaceEditorial @@ -0,0 +1 @@ +#include "qplaceeditorial.h" diff --git a/include/QtLocation/QPlaceIcon b/include/QtLocation/QPlaceIcon new file mode 100644 index 0000000..fae7a4e --- /dev/null +++ b/include/QtLocation/QPlaceIcon @@ -0,0 +1 @@ +#include "qplaceicon.h" diff --git a/include/QtLocation/QPlaceIdReply b/include/QtLocation/QPlaceIdReply new file mode 100644 index 0000000..8709b65 --- /dev/null +++ b/include/QtLocation/QPlaceIdReply @@ -0,0 +1 @@ +#include "qplaceidreply.h" diff --git a/include/QtLocation/QPlaceImage b/include/QtLocation/QPlaceImage new file mode 100644 index 0000000..606c306 --- /dev/null +++ b/include/QtLocation/QPlaceImage @@ -0,0 +1 @@ +#include "qplaceimage.h" diff --git a/include/QtLocation/QPlaceManager b/include/QtLocation/QPlaceManager new file mode 100644 index 0000000..7973c48 --- /dev/null +++ b/include/QtLocation/QPlaceManager @@ -0,0 +1 @@ +#include "qplacemanager.h" diff --git a/include/QtLocation/QPlaceManagerEngine b/include/QtLocation/QPlaceManagerEngine new file mode 100644 index 0000000..27f07fc --- /dev/null +++ b/include/QtLocation/QPlaceManagerEngine @@ -0,0 +1 @@ +#include "qplacemanagerengine.h" diff --git a/include/QtLocation/QPlaceMatchReply b/include/QtLocation/QPlaceMatchReply new file mode 100644 index 0000000..37ffdf2 --- /dev/null +++ b/include/QtLocation/QPlaceMatchReply @@ -0,0 +1 @@ +#include "qplacematchreply.h" diff --git a/include/QtLocation/QPlaceMatchRequest b/include/QtLocation/QPlaceMatchRequest new file mode 100644 index 0000000..ceaeb96 --- /dev/null +++ b/include/QtLocation/QPlaceMatchRequest @@ -0,0 +1 @@ +#include "qplacematchrequest.h" diff --git a/include/QtLocation/QPlaceProposedSearchResult b/include/QtLocation/QPlaceProposedSearchResult new file mode 100644 index 0000000..c43f2a6 --- /dev/null +++ b/include/QtLocation/QPlaceProposedSearchResult @@ -0,0 +1 @@ +#include "qplaceproposedsearchresult.h" diff --git a/include/QtLocation/QPlaceRatings b/include/QtLocation/QPlaceRatings new file mode 100644 index 0000000..4b7f63c --- /dev/null +++ b/include/QtLocation/QPlaceRatings @@ -0,0 +1 @@ +#include "qplaceratings.h" diff --git a/include/QtLocation/QPlaceReply b/include/QtLocation/QPlaceReply new file mode 100644 index 0000000..f0654b8 --- /dev/null +++ b/include/QtLocation/QPlaceReply @@ -0,0 +1 @@ +#include "qplacereply.h" diff --git a/include/QtLocation/QPlaceResult b/include/QtLocation/QPlaceResult new file mode 100644 index 0000000..4f5c8db --- /dev/null +++ b/include/QtLocation/QPlaceResult @@ -0,0 +1 @@ +#include "qplaceresult.h" diff --git a/include/QtLocation/QPlaceReview b/include/QtLocation/QPlaceReview new file mode 100644 index 0000000..83cccec --- /dev/null +++ b/include/QtLocation/QPlaceReview @@ -0,0 +1 @@ +#include "qplacereview.h" diff --git a/include/QtLocation/QPlaceSearchReply b/include/QtLocation/QPlaceSearchReply new file mode 100644 index 0000000..395a61a --- /dev/null +++ b/include/QtLocation/QPlaceSearchReply @@ -0,0 +1 @@ +#include "qplacesearchreply.h" diff --git a/include/QtLocation/QPlaceSearchRequest b/include/QtLocation/QPlaceSearchRequest new file mode 100644 index 0000000..b247dab --- /dev/null +++ b/include/QtLocation/QPlaceSearchRequest @@ -0,0 +1 @@ +#include "qplacesearchrequest.h" diff --git a/include/QtLocation/QPlaceSearchResult b/include/QtLocation/QPlaceSearchResult new file mode 100644 index 0000000..2298271 --- /dev/null +++ b/include/QtLocation/QPlaceSearchResult @@ -0,0 +1 @@ +#include "qplacesearchresult.h" diff --git a/include/QtLocation/QPlaceSearchSuggestionReply b/include/QtLocation/QPlaceSearchSuggestionReply new file mode 100644 index 0000000..e7a4f91 --- /dev/null +++ b/include/QtLocation/QPlaceSearchSuggestionReply @@ -0,0 +1 @@ +#include "qplacesearchsuggestionreply.h" diff --git a/include/QtLocation/QPlaceSupplier b/include/QtLocation/QPlaceSupplier new file mode 100644 index 0000000..465b46d --- /dev/null +++ b/include/QtLocation/QPlaceSupplier @@ -0,0 +1 @@ +#include "qplacesupplier.h" diff --git a/include/QtLocation/QPlaceUser b/include/QtLocation/QPlaceUser new file mode 100644 index 0000000..5acba29 --- /dev/null +++ b/include/QtLocation/QPlaceUser @@ -0,0 +1 @@ +#include "qplaceuser.h" diff --git a/include/QtLocation/QtLocation b/include/QtLocation/QtLocation new file mode 100644 index 0000000..897c1aa --- /dev/null +++ b/include/QtLocation/QtLocation @@ -0,0 +1,47 @@ +#ifndef QT_QTLOCATION_MODULE_H +#define QT_QTLOCATION_MODULE_H +#include +#include "qlocation.h" +#include "qlocationglobal.h" +#include "qgeocodereply.h" +#include "qgeocodingmanager.h" +#include "qgeocodingmanagerengine.h" +#include "qgeomaneuver.h" +#include "qgeoroute.h" +#include "qgeoroutereply.h" +#include "qgeorouterequest.h" +#include "qgeoroutesegment.h" +#include "qgeoroutingmanager.h" +#include "qgeoroutingmanagerengine.h" +#include "qgeoserviceprovider.h" +#include "qgeoserviceproviderfactory.h" +#include "placemacro.h" +#include "qplace.h" +#include "qplaceattribute.h" +#include "qplacecategory.h" +#include "qplacecontactdetail.h" +#include "qplacecontent.h" +#include "qplacecontentreply.h" +#include "qplacecontentrequest.h" +#include "qplacedetailsreply.h" +#include "qplaceeditorial.h" +#include "qplaceicon.h" +#include "qplaceidreply.h" +#include "qplaceimage.h" +#include "qplacemanager.h" +#include "qplacemanagerengine.h" +#include "qplacematchreply.h" +#include "qplacematchrequest.h" +#include "qplaceproposedsearchresult.h" +#include "qplaceratings.h" +#include "qplacereply.h" +#include "qplaceresult.h" +#include "qplacereview.h" +#include "qplacesearchreply.h" +#include "qplacesearchrequest.h" +#include "qplacesearchresult.h" +#include "qplacesearchsuggestionreply.h" +#include "qplacesupplier.h" +#include "qplaceuser.h" +#include "qtlocationversion.h" +#endif diff --git a/include/QtLocation/QtLocationVersion b/include/QtLocation/QtLocationVersion new file mode 100644 index 0000000..8da2499 --- /dev/null +++ b/include/QtLocation/QtLocationVersion @@ -0,0 +1 @@ +#include "qtlocationversion.h" diff --git a/include/QtLocation/headers.pri b/include/QtLocation/headers.pri new file mode 100644 index 0000000..6ec9927 --- /dev/null +++ b/include/QtLocation/headers.pri @@ -0,0 +1,6 @@ +SYNCQT.HEADER_FILES = qlocation.h qlocationglobal.h maps/qgeocodereply.h maps/qgeocodingmanager.h maps/qgeocodingmanagerengine.h maps/qgeomaneuver.h maps/qgeoroute.h maps/qgeoroutereply.h maps/qgeorouterequest.h maps/qgeoroutesegment.h maps/qgeoroutingmanager.h maps/qgeoroutingmanagerengine.h maps/qgeoserviceprovider.h maps/qgeoserviceproviderfactory.h places/placemacro.h places/qplace.h places/qplaceattribute.h places/qplacecategory.h places/qplacecontactdetail.h places/qplacecontent.h places/qplacecontentreply.h places/qplacecontentrequest.h places/qplacedetailsreply.h places/qplaceeditorial.h places/qplaceicon.h places/qplaceidreply.h places/qplaceimage.h places/qplacemanager.h places/qplacemanagerengine.h places/qplacematchreply.h places/qplacematchrequest.h places/qplaceproposedsearchresult.h places/qplaceratings.h places/qplacereply.h places/qplaceresult.h places/qplacereview.h places/qplacesearchreply.h places/qplacesearchrequest.h places/qplacesearchresult.h places/qplacesearchsuggestionreply.h places/qplacesupplier.h places/qplaceuser.h ../../include/QtLocation/qtlocationversion.h ../../include/QtLocation/QtLocation +SYNCQT.HEADER_CLASSES = ../../include/QtLocation/QLocation ../../include/QtLocation/QGeoCodeReply ../../include/QtLocation/QGeoCodingManager ../../include/QtLocation/QGeoCodingManagerEngine ../../include/QtLocation/QGeoManeuver ../../include/QtLocation/QGeoRoute ../../include/QtLocation/QGeoRouteReply ../../include/QtLocation/QGeoRouteRequest ../../include/QtLocation/QGeoRouteSegment ../../include/QtLocation/QGeoRoutingManager ../../include/QtLocation/QGeoRoutingManagerEngine ../../include/QtLocation/QGeoServiceProvider ../../include/QtLocation/QGeoServiceProviderFactory ../../include/QtLocation/QPlace ../../include/QtLocation/QPlaceAttribute ../../include/QtLocation/QPlaceCategory ../../include/QtLocation/QPlaceContactDetail ../../include/QtLocation/QPlaceContent ../../include/QtLocation/QPlaceContentReply ../../include/QtLocation/QPlaceContentRequest ../../include/QtLocation/QPlaceDetailsReply ../../include/QtLocation/QPlaceEditorial ../../include/QtLocation/QPlaceIcon ../../include/QtLocation/QPlaceIdReply ../../include/QtLocation/QPlaceImage ../../include/QtLocation/QPlaceManager ../../include/QtLocation/QPlaceManagerEngine ../../include/QtLocation/QPlaceMatchReply ../../include/QtLocation/QPlaceMatchRequest ../../include/QtLocation/QPlaceProposedSearchResult ../../include/QtLocation/QPlaceRatings ../../include/QtLocation/QPlaceReply ../../include/QtLocation/QPlaceResult ../../include/QtLocation/QPlaceReview ../../include/QtLocation/QPlaceSearchReply ../../include/QtLocation/QPlaceSearchRequest ../../include/QtLocation/QPlaceSearchResult ../../include/QtLocation/QPlaceSearchSuggestionReply ../../include/QtLocation/QPlaceSupplier ../../include/QtLocation/QPlaceUser ../../include/QtLocation/QtLocationVersion +SYNCQT.PRIVATE_HEADER_FILES = maps/qabstractgeotilecache_p.h maps/qcache3q_p.h maps/qgeocameracapabilities_p.h maps/qgeocameradata_p.h maps/qgeocameratiles_p.h maps/qgeocodereply_p.h maps/qgeocodingmanager_p.h maps/qgeocodingmanagerengine_p.h maps/qgeofiletilecache_p.h maps/qgeomaneuver_p.h maps/qgeomap_p.h maps/qgeomap_p_p.h maps/qgeomappingmanager_p.h maps/qgeomappingmanager_p_p.h maps/qgeomappingmanagerengine_p.h maps/qgeomappingmanagerengine_p_p.h maps/qgeomaptype_p.h maps/qgeomaptype_p_p.h maps/qgeoroute_p.h maps/qgeorouteparser_p.h maps/qgeorouteparser_p_p.h maps/qgeorouteparserosrmv4_p.h maps/qgeorouteparserosrmv5_p.h maps/qgeoroutereply_p.h maps/qgeorouterequest_p.h maps/qgeoroutesegment_p.h maps/qgeoroutingmanager_p.h maps/qgeoroutingmanagerengine_p.h maps/qgeoserviceprovider_p.h maps/qgeotiledmap_p.h maps/qgeotiledmap_p_p.h maps/qgeotiledmappingmanagerengine_p.h maps/qgeotiledmappingmanagerengine_p_p.h maps/qgeotiledmapreply_p.h maps/qgeotiledmapreply_p_p.h maps/qgeotiledmapscene_p.h maps/qgeotilefetcher_p.h maps/qgeotilefetcher_p_p.h maps/qgeotilerequestmanager_p.h maps/qgeotilespec_p.h maps/qgeotilespec_p_p.h places/qplace_p.h places/qplaceattribute_p.h places/qplacecategory_p.h places/qplacecontactdetail_p.h places/qplacecontent_p.h places/qplacecontentrequest_p.h places/qplaceeditorial_p.h places/qplaceicon_p.h places/qplaceimage_p.h places/qplacemanagerengine_p.h places/qplaceproposedsearchresult_p.h places/qplaceratings_p.h places/qplacereply_p.h places/qplaceresult_p.h places/qplacereview_p.h places/qplacesearchresult_p.h places/qplacesupplier_p.h places/qplaceuser_p.h places/unsupportedreplies_p.h +SYNCQT.QPA_HEADER_FILES = +SYNCQT.CLEAN_HEADER_FILES = qlocation.h qlocationglobal.h maps/qgeocodereply.h maps/qgeocodingmanager.h maps/qgeocodingmanagerengine.h maps/qgeomaneuver.h maps/qgeoroute.h maps/qgeoroutereply.h maps/qgeorouterequest.h maps/qgeoroutesegment.h maps/qgeoroutingmanager.h maps/qgeoroutingmanagerengine.h maps/qgeoserviceprovider.h maps/qgeoserviceproviderfactory.h places/placemacro.h places/qplace.h places/qplaceattribute.h places/qplacecategory.h places/qplacecontactdetail.h places/qplacecontent.h places/qplacecontentreply.h places/qplacecontentrequest.h places/qplacedetailsreply.h places/qplaceeditorial.h places/qplaceicon.h places/qplaceidreply.h places/qplaceimage.h places/qplacemanager.h places/qplacemanagerengine.h places/qplacematchreply.h places/qplacematchrequest.h places/qplaceproposedsearchresult.h places/qplaceratings.h places/qplacereply.h places/qplaceresult.h places/qplacereview.h places/qplacesearchreply.h places/qplacesearchrequest.h places/qplacesearchresult.h places/qplacesearchsuggestionreply.h places/qplacesupplier.h places/qplaceuser.h +SYNCQT.INJECTIONS = diff --git a/include/QtLocation/placemacro.h b/include/QtLocation/placemacro.h new file mode 100644 index 0000000..19404ab --- /dev/null +++ b/include/QtLocation/placemacro.h @@ -0,0 +1 @@ +#include "../../src/location/places/placemacro.h" diff --git a/include/QtLocation/qgeocodereply.h b/include/QtLocation/qgeocodereply.h new file mode 100644 index 0000000..ba6d712 --- /dev/null +++ b/include/QtLocation/qgeocodereply.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeocodereply.h" diff --git a/include/QtLocation/qgeocodingmanager.h b/include/QtLocation/qgeocodingmanager.h new file mode 100644 index 0000000..7ddf50f --- /dev/null +++ b/include/QtLocation/qgeocodingmanager.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeocodingmanager.h" diff --git a/include/QtLocation/qgeocodingmanagerengine.h b/include/QtLocation/qgeocodingmanagerengine.h new file mode 100644 index 0000000..18d06ef --- /dev/null +++ b/include/QtLocation/qgeocodingmanagerengine.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeocodingmanagerengine.h" diff --git a/include/QtLocation/qgeomaneuver.h b/include/QtLocation/qgeomaneuver.h new file mode 100644 index 0000000..72e145b --- /dev/null +++ b/include/QtLocation/qgeomaneuver.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeomaneuver.h" diff --git a/include/QtLocation/qgeoroute.h b/include/QtLocation/qgeoroute.h new file mode 100644 index 0000000..b0bdf34 --- /dev/null +++ b/include/QtLocation/qgeoroute.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeoroute.h" diff --git a/include/QtLocation/qgeoroutereply.h b/include/QtLocation/qgeoroutereply.h new file mode 100644 index 0000000..34d8e88 --- /dev/null +++ b/include/QtLocation/qgeoroutereply.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeoroutereply.h" diff --git a/include/QtLocation/qgeorouterequest.h b/include/QtLocation/qgeorouterequest.h new file mode 100644 index 0000000..33bf11d --- /dev/null +++ b/include/QtLocation/qgeorouterequest.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeorouterequest.h" diff --git a/include/QtLocation/qgeoroutesegment.h b/include/QtLocation/qgeoroutesegment.h new file mode 100644 index 0000000..7205732 --- /dev/null +++ b/include/QtLocation/qgeoroutesegment.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeoroutesegment.h" diff --git a/include/QtLocation/qgeoroutingmanager.h b/include/QtLocation/qgeoroutingmanager.h new file mode 100644 index 0000000..8ae46be --- /dev/null +++ b/include/QtLocation/qgeoroutingmanager.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeoroutingmanager.h" diff --git a/include/QtLocation/qgeoroutingmanagerengine.h b/include/QtLocation/qgeoroutingmanagerengine.h new file mode 100644 index 0000000..4b26f14 --- /dev/null +++ b/include/QtLocation/qgeoroutingmanagerengine.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeoroutingmanagerengine.h" diff --git a/include/QtLocation/qgeoserviceprovider.h b/include/QtLocation/qgeoserviceprovider.h new file mode 100644 index 0000000..4aca205 --- /dev/null +++ b/include/QtLocation/qgeoserviceprovider.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeoserviceprovider.h" diff --git a/include/QtLocation/qgeoserviceproviderfactory.h b/include/QtLocation/qgeoserviceproviderfactory.h new file mode 100644 index 0000000..39a5e4c --- /dev/null +++ b/include/QtLocation/qgeoserviceproviderfactory.h @@ -0,0 +1 @@ +#include "../../src/location/maps/qgeoserviceproviderfactory.h" diff --git a/include/QtLocation/qlocation.h b/include/QtLocation/qlocation.h new file mode 100644 index 0000000..4c266a1 --- /dev/null +++ b/include/QtLocation/qlocation.h @@ -0,0 +1 @@ +#include "../../src/location/qlocation.h" diff --git a/include/QtLocation/qlocationglobal.h b/include/QtLocation/qlocationglobal.h new file mode 100644 index 0000000..9959740 --- /dev/null +++ b/include/QtLocation/qlocationglobal.h @@ -0,0 +1 @@ +#include "../../src/location/qlocationglobal.h" diff --git a/include/QtLocation/qplace.h b/include/QtLocation/qplace.h new file mode 100644 index 0000000..7ce3cf4 --- /dev/null +++ b/include/QtLocation/qplace.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplace.h" diff --git a/include/QtLocation/qplaceattribute.h b/include/QtLocation/qplaceattribute.h new file mode 100644 index 0000000..9ade0c5 --- /dev/null +++ b/include/QtLocation/qplaceattribute.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplaceattribute.h" diff --git a/include/QtLocation/qplacecategory.h b/include/QtLocation/qplacecategory.h new file mode 100644 index 0000000..310d15c --- /dev/null +++ b/include/QtLocation/qplacecategory.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacecategory.h" diff --git a/include/QtLocation/qplacecontactdetail.h b/include/QtLocation/qplacecontactdetail.h new file mode 100644 index 0000000..179040c --- /dev/null +++ b/include/QtLocation/qplacecontactdetail.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacecontactdetail.h" diff --git a/include/QtLocation/qplacecontent.h b/include/QtLocation/qplacecontent.h new file mode 100644 index 0000000..675d5ee --- /dev/null +++ b/include/QtLocation/qplacecontent.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacecontent.h" diff --git a/include/QtLocation/qplacecontentreply.h b/include/QtLocation/qplacecontentreply.h new file mode 100644 index 0000000..3129a8f --- /dev/null +++ b/include/QtLocation/qplacecontentreply.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacecontentreply.h" diff --git a/include/QtLocation/qplacecontentrequest.h b/include/QtLocation/qplacecontentrequest.h new file mode 100644 index 0000000..1578507 --- /dev/null +++ b/include/QtLocation/qplacecontentrequest.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacecontentrequest.h" diff --git a/include/QtLocation/qplacedetailsreply.h b/include/QtLocation/qplacedetailsreply.h new file mode 100644 index 0000000..586e16a --- /dev/null +++ b/include/QtLocation/qplacedetailsreply.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacedetailsreply.h" diff --git a/include/QtLocation/qplaceeditorial.h b/include/QtLocation/qplaceeditorial.h new file mode 100644 index 0000000..9dac346 --- /dev/null +++ b/include/QtLocation/qplaceeditorial.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplaceeditorial.h" diff --git a/include/QtLocation/qplaceicon.h b/include/QtLocation/qplaceicon.h new file mode 100644 index 0000000..558ac04 --- /dev/null +++ b/include/QtLocation/qplaceicon.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplaceicon.h" diff --git a/include/QtLocation/qplaceidreply.h b/include/QtLocation/qplaceidreply.h new file mode 100644 index 0000000..8d19185 --- /dev/null +++ b/include/QtLocation/qplaceidreply.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplaceidreply.h" diff --git a/include/QtLocation/qplaceimage.h b/include/QtLocation/qplaceimage.h new file mode 100644 index 0000000..856deb1 --- /dev/null +++ b/include/QtLocation/qplaceimage.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplaceimage.h" diff --git a/include/QtLocation/qplacemanager.h b/include/QtLocation/qplacemanager.h new file mode 100644 index 0000000..689e043 --- /dev/null +++ b/include/QtLocation/qplacemanager.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacemanager.h" diff --git a/include/QtLocation/qplacemanagerengine.h b/include/QtLocation/qplacemanagerengine.h new file mode 100644 index 0000000..8966e6d --- /dev/null +++ b/include/QtLocation/qplacemanagerengine.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacemanagerengine.h" diff --git a/include/QtLocation/qplacematchreply.h b/include/QtLocation/qplacematchreply.h new file mode 100644 index 0000000..123bd8f --- /dev/null +++ b/include/QtLocation/qplacematchreply.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacematchreply.h" diff --git a/include/QtLocation/qplacematchrequest.h b/include/QtLocation/qplacematchrequest.h new file mode 100644 index 0000000..bca1b69 --- /dev/null +++ b/include/QtLocation/qplacematchrequest.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacematchrequest.h" diff --git a/include/QtLocation/qplaceproposedsearchresult.h b/include/QtLocation/qplaceproposedsearchresult.h new file mode 100644 index 0000000..9a609b4 --- /dev/null +++ b/include/QtLocation/qplaceproposedsearchresult.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplaceproposedsearchresult.h" diff --git a/include/QtLocation/qplaceratings.h b/include/QtLocation/qplaceratings.h new file mode 100644 index 0000000..dc6ad10 --- /dev/null +++ b/include/QtLocation/qplaceratings.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplaceratings.h" diff --git a/include/QtLocation/qplacereply.h b/include/QtLocation/qplacereply.h new file mode 100644 index 0000000..96a2fef --- /dev/null +++ b/include/QtLocation/qplacereply.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacereply.h" diff --git a/include/QtLocation/qplaceresult.h b/include/QtLocation/qplaceresult.h new file mode 100644 index 0000000..48e4bed --- /dev/null +++ b/include/QtLocation/qplaceresult.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplaceresult.h" diff --git a/include/QtLocation/qplacereview.h b/include/QtLocation/qplacereview.h new file mode 100644 index 0000000..19273d9 --- /dev/null +++ b/include/QtLocation/qplacereview.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacereview.h" diff --git a/include/QtLocation/qplacesearchreply.h b/include/QtLocation/qplacesearchreply.h new file mode 100644 index 0000000..5e45879 --- /dev/null +++ b/include/QtLocation/qplacesearchreply.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacesearchreply.h" diff --git a/include/QtLocation/qplacesearchrequest.h b/include/QtLocation/qplacesearchrequest.h new file mode 100644 index 0000000..794ac51 --- /dev/null +++ b/include/QtLocation/qplacesearchrequest.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacesearchrequest.h" diff --git a/include/QtLocation/qplacesearchresult.h b/include/QtLocation/qplacesearchresult.h new file mode 100644 index 0000000..102d1e8 --- /dev/null +++ b/include/QtLocation/qplacesearchresult.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacesearchresult.h" diff --git a/include/QtLocation/qplacesearchsuggestionreply.h b/include/QtLocation/qplacesearchsuggestionreply.h new file mode 100644 index 0000000..72cdc6a --- /dev/null +++ b/include/QtLocation/qplacesearchsuggestionreply.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacesearchsuggestionreply.h" diff --git a/include/QtLocation/qplacesupplier.h b/include/QtLocation/qplacesupplier.h new file mode 100644 index 0000000..071102d --- /dev/null +++ b/include/QtLocation/qplacesupplier.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplacesupplier.h" diff --git a/include/QtLocation/qplaceuser.h b/include/QtLocation/qplaceuser.h new file mode 100644 index 0000000..88d3b28 --- /dev/null +++ b/include/QtLocation/qplaceuser.h @@ -0,0 +1 @@ +#include "../../src/location/places/qplaceuser.h" diff --git a/include/QtLocation/qtlocationversion.h b/include/QtLocation/qtlocationversion.h new file mode 100644 index 0000000..8a87c9f --- /dev/null +++ b/include/QtLocation/qtlocationversion.h @@ -0,0 +1,9 @@ +/* This file was generated by syncqt. */ +#ifndef QT_QTLOCATION_VERSION_H +#define QT_QTLOCATION_VERSION_H + +#define QTLOCATION_VERSION_STR "5.7.1" + +#define QTLOCATION_VERSION 0x050701 + +#endif // QT_QTLOCATION_VERSION_H diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qdeclarativegeoaddress_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qdeclarativegeoaddress_p.h new file mode 100644 index 0000000..56562a7 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qdeclarativegeoaddress_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qdeclarativegeoaddress_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qdeclarativegeolocation_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qdeclarativegeolocation_p.h new file mode 100644 index 0000000..ce5bbf3 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qdeclarativegeolocation_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qdeclarativegeolocation_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qdoublevector2d_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qdoublevector2d_p.h new file mode 100644 index 0000000..a679f2f --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qdoublevector2d_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qdoublevector2d_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qdoublevector3d_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qdoublevector3d_p.h new file mode 100644 index 0000000..6b1a13c --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qdoublevector3d_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qdoublevector3d_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qgeoaddress_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qgeoaddress_p.h new file mode 100644 index 0000000..b494007 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qgeoaddress_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qgeoaddress_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qgeocircle_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qgeocircle_p.h new file mode 100644 index 0000000..80a2f05 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qgeocircle_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qgeocircle_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qgeocoordinate_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qgeocoordinate_p.h new file mode 100644 index 0000000..53b5c3b --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qgeocoordinate_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qgeocoordinate_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qgeolocation_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qgeolocation_p.h new file mode 100644 index 0000000..15bd5a1 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qgeolocation_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qgeolocation_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qgeopositioninfosource_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qgeopositioninfosource_p.h new file mode 100644 index 0000000..a43d355 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qgeopositioninfosource_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qgeopositioninfosource_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qgeoprojection_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qgeoprojection_p.h new file mode 100644 index 0000000..d055679 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qgeoprojection_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qgeoprojection_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qgeorectangle_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qgeorectangle_p.h new file mode 100644 index 0000000..e4d2ac1 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qgeorectangle_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qgeorectangle_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qgeoshape_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qgeoshape_p.h new file mode 100644 index 0000000..ef4a35e --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qgeoshape_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qgeoshape_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qlocationdata_simulator_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qlocationdata_simulator_p.h new file mode 100644 index 0000000..ed0b911 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qlocationdata_simulator_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qlocationdata_simulator_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qlocationutils_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qlocationutils_p.h new file mode 100644 index 0000000..0c3d9cd --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qlocationutils_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qlocationutils_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qnmeapositioninfosource_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qnmeapositioninfosource_p.h new file mode 100644 index 0000000..2fd5904 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qnmeapositioninfosource_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qnmeapositioninfosource_p.h" diff --git a/include/QtPositioning/5.7.1/QtPositioning/private/qpositioningglobal_p.h b/include/QtPositioning/5.7.1/QtPositioning/private/qpositioningglobal_p.h new file mode 100644 index 0000000..657c470 --- /dev/null +++ b/include/QtPositioning/5.7.1/QtPositioning/private/qpositioningglobal_p.h @@ -0,0 +1 @@ +#include "../../../../../src/positioning/qpositioningglobal_p.h" diff --git a/include/QtPositioning/QGeoAddress b/include/QtPositioning/QGeoAddress new file mode 100644 index 0000000..708bcab --- /dev/null +++ b/include/QtPositioning/QGeoAddress @@ -0,0 +1 @@ +#include "qgeoaddress.h" diff --git a/include/QtPositioning/QGeoAreaMonitorInfo b/include/QtPositioning/QGeoAreaMonitorInfo new file mode 100644 index 0000000..7e137e6 --- /dev/null +++ b/include/QtPositioning/QGeoAreaMonitorInfo @@ -0,0 +1 @@ +#include "qgeoareamonitorinfo.h" diff --git a/include/QtPositioning/QGeoAreaMonitorSource b/include/QtPositioning/QGeoAreaMonitorSource new file mode 100644 index 0000000..942ccf6 --- /dev/null +++ b/include/QtPositioning/QGeoAreaMonitorSource @@ -0,0 +1 @@ +#include "qgeoareamonitorsource.h" diff --git a/include/QtPositioning/QGeoCircle b/include/QtPositioning/QGeoCircle new file mode 100644 index 0000000..b77d827 --- /dev/null +++ b/include/QtPositioning/QGeoCircle @@ -0,0 +1 @@ +#include "qgeocircle.h" diff --git a/include/QtPositioning/QGeoCoordinate b/include/QtPositioning/QGeoCoordinate new file mode 100644 index 0000000..812fb2a --- /dev/null +++ b/include/QtPositioning/QGeoCoordinate @@ -0,0 +1 @@ +#include "qgeocoordinate.h" diff --git a/include/QtPositioning/QGeoLocation b/include/QtPositioning/QGeoLocation new file mode 100644 index 0000000..bd38c75 --- /dev/null +++ b/include/QtPositioning/QGeoLocation @@ -0,0 +1 @@ +#include "qgeolocation.h" diff --git a/include/QtPositioning/QGeoPositionInfo b/include/QtPositioning/QGeoPositionInfo new file mode 100644 index 0000000..d6feec9 --- /dev/null +++ b/include/QtPositioning/QGeoPositionInfo @@ -0,0 +1 @@ +#include "qgeopositioninfo.h" diff --git a/include/QtPositioning/QGeoPositionInfoSource b/include/QtPositioning/QGeoPositionInfoSource new file mode 100644 index 0000000..b7cc338 --- /dev/null +++ b/include/QtPositioning/QGeoPositionInfoSource @@ -0,0 +1 @@ +#include "qgeopositioninfosource.h" diff --git a/include/QtPositioning/QGeoPositionInfoSourceFactory b/include/QtPositioning/QGeoPositionInfoSourceFactory new file mode 100644 index 0000000..7448436 --- /dev/null +++ b/include/QtPositioning/QGeoPositionInfoSourceFactory @@ -0,0 +1 @@ +#include "qgeopositioninfosourcefactory.h" diff --git a/include/QtPositioning/QGeoRectangle b/include/QtPositioning/QGeoRectangle new file mode 100644 index 0000000..9a7d48f --- /dev/null +++ b/include/QtPositioning/QGeoRectangle @@ -0,0 +1 @@ +#include "qgeorectangle.h" diff --git a/include/QtPositioning/QGeoSatelliteInfo b/include/QtPositioning/QGeoSatelliteInfo new file mode 100644 index 0000000..a3fd47d --- /dev/null +++ b/include/QtPositioning/QGeoSatelliteInfo @@ -0,0 +1 @@ +#include "qgeosatelliteinfo.h" diff --git a/include/QtPositioning/QGeoSatelliteInfoSource b/include/QtPositioning/QGeoSatelliteInfoSource new file mode 100644 index 0000000..cc6ba9d --- /dev/null +++ b/include/QtPositioning/QGeoSatelliteInfoSource @@ -0,0 +1 @@ +#include "qgeosatelliteinfosource.h" diff --git a/include/QtPositioning/QGeoShape b/include/QtPositioning/QGeoShape new file mode 100644 index 0000000..d86ddc3 --- /dev/null +++ b/include/QtPositioning/QGeoShape @@ -0,0 +1 @@ +#include "qgeoshape.h" diff --git a/include/QtPositioning/QNmeaPositionInfoSource b/include/QtPositioning/QNmeaPositionInfoSource new file mode 100644 index 0000000..5c261c9 --- /dev/null +++ b/include/QtPositioning/QNmeaPositionInfoSource @@ -0,0 +1 @@ +#include "qnmeapositioninfosource.h" diff --git a/include/QtPositioning/QtPositioning b/include/QtPositioning/QtPositioning new file mode 100644 index 0000000..90f0720 --- /dev/null +++ b/include/QtPositioning/QtPositioning @@ -0,0 +1,20 @@ +#ifndef QT_QTPOSITIONING_MODULE_H +#define QT_QTPOSITIONING_MODULE_H +#include +#include "qgeoaddress.h" +#include "qgeoareamonitorinfo.h" +#include "qgeoareamonitorsource.h" +#include "qgeocircle.h" +#include "qgeocoordinate.h" +#include "qgeolocation.h" +#include "qgeopositioninfo.h" +#include "qgeopositioninfosource.h" +#include "qgeopositioninfosourcefactory.h" +#include "qgeorectangle.h" +#include "qgeosatelliteinfo.h" +#include "qgeosatelliteinfosource.h" +#include "qgeoshape.h" +#include "qnmeapositioninfosource.h" +#include "qpositioningglobal.h" +#include "qtpositioningversion.h" +#endif diff --git a/include/QtPositioning/QtPositioningVersion b/include/QtPositioning/QtPositioningVersion new file mode 100644 index 0000000..2f99398 --- /dev/null +++ b/include/QtPositioning/QtPositioningVersion @@ -0,0 +1 @@ +#include "qtpositioningversion.h" diff --git a/include/QtPositioning/headers.pri b/include/QtPositioning/headers.pri new file mode 100644 index 0000000..0faa108 --- /dev/null +++ b/include/QtPositioning/headers.pri @@ -0,0 +1,6 @@ +SYNCQT.HEADER_FILES = qgeoaddress.h qgeoareamonitorinfo.h qgeoareamonitorsource.h qgeocircle.h qgeocoordinate.h qgeolocation.h qgeopositioninfo.h qgeopositioninfosource.h qgeopositioninfosourcefactory.h qgeorectangle.h qgeosatelliteinfo.h qgeosatelliteinfosource.h qgeoshape.h qnmeapositioninfosource.h qpositioningglobal.h ../../include/QtPositioning/qtpositioningversion.h ../../include/QtPositioning/QtPositioning +SYNCQT.HEADER_CLASSES = ../../include/QtPositioning/QGeoAddress ../../include/QtPositioning/QGeoAreaMonitorInfo ../../include/QtPositioning/QGeoAreaMonitorSource ../../include/QtPositioning/QGeoCircle ../../include/QtPositioning/QGeoCoordinate ../../include/QtPositioning/QGeoLocation ../../include/QtPositioning/QGeoPositionInfo ../../include/QtPositioning/QGeoPositionInfoSource ../../include/QtPositioning/QGeoPositionInfoSourceFactory ../../include/QtPositioning/QGeoRectangle ../../include/QtPositioning/QGeoSatelliteInfo ../../include/QtPositioning/QGeoSatelliteInfoSource ../../include/QtPositioning/QGeoShape ../../include/QtPositioning/QNmeaPositionInfoSource ../../include/QtPositioning/QtPositioningVersion +SYNCQT.PRIVATE_HEADER_FILES = qdeclarativegeoaddress_p.h qdeclarativegeolocation_p.h qdoublevector2d_p.h qdoublevector3d_p.h qgeoaddress_p.h qgeocircle_p.h qgeocoordinate_p.h qgeolocation_p.h qgeopositioninfosource_p.h qgeoprojection_p.h qgeorectangle_p.h qgeoshape_p.h qlocationdata_simulator_p.h qlocationutils_p.h qnmeapositioninfosource_p.h qpositioningglobal_p.h +SYNCQT.QPA_HEADER_FILES = +SYNCQT.CLEAN_HEADER_FILES = qgeoaddress.h qgeoareamonitorinfo.h qgeoareamonitorsource.h qgeocircle.h qgeocoordinate.h qgeolocation.h qgeopositioninfo.h qgeopositioninfosource.h qgeopositioninfosourcefactory.h qgeorectangle.h qgeosatelliteinfo.h qgeosatelliteinfosource.h qgeoshape.h qnmeapositioninfosource.h qpositioningglobal.h +SYNCQT.INJECTIONS = diff --git a/include/QtPositioning/qgeoaddress.h b/include/QtPositioning/qgeoaddress.h new file mode 100644 index 0000000..b8e0a99 --- /dev/null +++ b/include/QtPositioning/qgeoaddress.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeoaddress.h" diff --git a/include/QtPositioning/qgeoareamonitorinfo.h b/include/QtPositioning/qgeoareamonitorinfo.h new file mode 100644 index 0000000..f9a334c --- /dev/null +++ b/include/QtPositioning/qgeoareamonitorinfo.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeoareamonitorinfo.h" diff --git a/include/QtPositioning/qgeoareamonitorsource.h b/include/QtPositioning/qgeoareamonitorsource.h new file mode 100644 index 0000000..4555de8 --- /dev/null +++ b/include/QtPositioning/qgeoareamonitorsource.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeoareamonitorsource.h" diff --git a/include/QtPositioning/qgeocircle.h b/include/QtPositioning/qgeocircle.h new file mode 100644 index 0000000..f598c2c --- /dev/null +++ b/include/QtPositioning/qgeocircle.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeocircle.h" diff --git a/include/QtPositioning/qgeocoordinate.h b/include/QtPositioning/qgeocoordinate.h new file mode 100644 index 0000000..93b16d9 --- /dev/null +++ b/include/QtPositioning/qgeocoordinate.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeocoordinate.h" diff --git a/include/QtPositioning/qgeolocation.h b/include/QtPositioning/qgeolocation.h new file mode 100644 index 0000000..59429ea --- /dev/null +++ b/include/QtPositioning/qgeolocation.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeolocation.h" diff --git a/include/QtPositioning/qgeopositioninfo.h b/include/QtPositioning/qgeopositioninfo.h new file mode 100644 index 0000000..213fbb2 --- /dev/null +++ b/include/QtPositioning/qgeopositioninfo.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeopositioninfo.h" diff --git a/include/QtPositioning/qgeopositioninfosource.h b/include/QtPositioning/qgeopositioninfosource.h new file mode 100644 index 0000000..8f7082d --- /dev/null +++ b/include/QtPositioning/qgeopositioninfosource.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeopositioninfosource.h" diff --git a/include/QtPositioning/qgeopositioninfosourcefactory.h b/include/QtPositioning/qgeopositioninfosourcefactory.h new file mode 100644 index 0000000..e59bdd9 --- /dev/null +++ b/include/QtPositioning/qgeopositioninfosourcefactory.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeopositioninfosourcefactory.h" diff --git a/include/QtPositioning/qgeorectangle.h b/include/QtPositioning/qgeorectangle.h new file mode 100644 index 0000000..f4a6526 --- /dev/null +++ b/include/QtPositioning/qgeorectangle.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeorectangle.h" diff --git a/include/QtPositioning/qgeosatelliteinfo.h b/include/QtPositioning/qgeosatelliteinfo.h new file mode 100644 index 0000000..1c1d631 --- /dev/null +++ b/include/QtPositioning/qgeosatelliteinfo.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeosatelliteinfo.h" diff --git a/include/QtPositioning/qgeosatelliteinfosource.h b/include/QtPositioning/qgeosatelliteinfosource.h new file mode 100644 index 0000000..a3115b1 --- /dev/null +++ b/include/QtPositioning/qgeosatelliteinfosource.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeosatelliteinfosource.h" diff --git a/include/QtPositioning/qgeoshape.h b/include/QtPositioning/qgeoshape.h new file mode 100644 index 0000000..6e7d4db --- /dev/null +++ b/include/QtPositioning/qgeoshape.h @@ -0,0 +1 @@ +#include "../../src/positioning/qgeoshape.h" diff --git a/include/QtPositioning/qnmeapositioninfosource.h b/include/QtPositioning/qnmeapositioninfosource.h new file mode 100644 index 0000000..6f5d63d --- /dev/null +++ b/include/QtPositioning/qnmeapositioninfosource.h @@ -0,0 +1 @@ +#include "../../src/positioning/qnmeapositioninfosource.h" diff --git a/include/QtPositioning/qpositioningglobal.h b/include/QtPositioning/qpositioningglobal.h new file mode 100644 index 0000000..a291214 --- /dev/null +++ b/include/QtPositioning/qpositioningglobal.h @@ -0,0 +1 @@ +#include "../../src/positioning/qpositioningglobal.h" diff --git a/include/QtPositioning/qtpositioningversion.h b/include/QtPositioning/qtpositioningversion.h new file mode 100644 index 0000000..0a35884 --- /dev/null +++ b/include/QtPositioning/qtpositioningversion.h @@ -0,0 +1,9 @@ +/* This file was generated by syncqt. */ +#ifndef QT_QTPOSITIONING_VERSION_H +#define QT_QTPOSITIONING_VERSION_H + +#define QTPOSITIONING_VERSION_STR "5.7.1" + +#define QTPOSITIONING_VERSION 0x050701 + +#endif // QT_QTPOSITIONING_VERSION_H diff --git a/qtlocation.pro b/qtlocation.pro new file mode 100644 index 0000000..3104465 --- /dev/null +++ b/qtlocation.pro @@ -0,0 +1,4 @@ +load(configure) +qtCompileTest(gypsy) + +load(qt_parts) diff --git a/src/3rdparty/3rdparty.pro b/src/3rdparty/3rdparty.pro new file mode 100644 index 0000000..72996c7 --- /dev/null +++ b/src/3rdparty/3rdparty.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS += poly2tri +SUBDIRS += clipper +SUBDIRS += clip2tri diff --git a/src/3rdparty/clip2tri/LICENSE b/src/3rdparty/clip2tri/LICENSE new file mode 100644 index 0000000..9d99b88 --- /dev/null +++ b/src/3rdparty/clip2tri/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Bitfighter developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/3rdparty/clip2tri/clip2tri.cpp b/src/3rdparty/clip2tri/clip2tri.cpp new file mode 100644 index 0000000..86870fc --- /dev/null +++ b/src/3rdparty/clip2tri/clip2tri.cpp @@ -0,0 +1,312 @@ +/* + * Authors: kaen, raptor, sam686, watusimoto + * + * Originally from the bitfighter source code + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Bitfighter developers + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "clip2tri.h" +#include "../poly2tri/poly2tri.h" + +#include + + +using namespace p2t; + +namespace c2t +{ + + +static const F32 CLIPPER_SCALE_FACT = 1000.0f; +static const F32 CLIPPER_SCALE_FACT_INVERSE = 0.001f; + + +///////////////////////////////// + +Point::Point() +{ + x = 0; + y = 0; +} + +Point::Point(const Point& pt) +{ + x = pt.x; + y = pt.y; +} + + +///////////////////////////////// + +clip2tri::clip2tri() +{ + // Do nothing! +} + +clip2tri::~clip2tri() +{ + // Do nothing! +} + + +void clip2tri::triangulate(const vector > &inputPolygons, vector &outputTriangles, + const vector &boundingPolygon) +{ + // Use clipper to clean. This upscales the floating point input + PolyTree solution; + mergePolysToPolyTree(inputPolygons, solution); + + Path bounds = upscaleClipperPoints(boundingPolygon); + + // This will downscale the Clipper output and use poly2tri to triangulate + triangulateComplex(outputTriangles, bounds, solution); +} + + +Path clip2tri::upscaleClipperPoints(const vector &inputPolygon) +{ + Path outputPolygon; + outputPolygon.resize(inputPolygon.size()); + + for(S32 i = 0; i < inputPolygon.size(); i++) + outputPolygon[i] = IntPoint(S64(inputPolygon[i].x * CLIPPER_SCALE_FACT), S64(inputPolygon[i].y * CLIPPER_SCALE_FACT)); + + return outputPolygon; +} + + +Paths clip2tri::upscaleClipperPoints(const vector > &inputPolygons) +{ + Paths outputPolygons; + + outputPolygons.resize(inputPolygons.size()); + + for(S32 i = 0; i < inputPolygons.size(); i++) + { + outputPolygons[i].resize(inputPolygons[i].size()); + + for(S32 j = 0; j < inputPolygons[i].size(); j++) + outputPolygons[i][j] = IntPoint(S64(inputPolygons[i][j].x * CLIPPER_SCALE_FACT), S64(inputPolygons[i][j].y * CLIPPER_SCALE_FACT)); + } + + return outputPolygons; +} + + +vector > clip2tri::downscaleClipperPoints(const Paths &inputPolygons) +{ + vector > outputPolygons; + + outputPolygons.resize(inputPolygons.size()); + + for(U32 i = 0; i < inputPolygons.size(); i++) + { + outputPolygons[i].resize(inputPolygons[i].size()); + + for(U32 j = 0; j < inputPolygons[i].size(); j++) + outputPolygons[i][j] = Point(F32(inputPolygons[i][j].X) * CLIPPER_SCALE_FACT_INVERSE, F32(inputPolygons[i][j].Y) * CLIPPER_SCALE_FACT_INVERSE); + } + + return outputPolygons; +} + + +// Use Clipper to merge inputPolygons, placing the result in a Polytree +// NOTE: this does NOT downscale the Clipper points. You must do this afterwards +// +// Here you add all your non-navigatable objects (e.g. walls, barriers, etc.) +bool clip2tri::mergePolysToPolyTree(const vector > &inputPolygons, PolyTree &solution) +{ + Paths input = upscaleClipperPoints(inputPolygons); + + // Fire up clipper and union! + Clipper clipper; + clipper.StrictlySimple(true); + + try // there is a "throw" in AddPolygon + { + clipper.AddPaths(input, ptSubject, true); + } + catch(...) + { + printf("clipper.AddPaths, something went wrong\n"); + } + + return clipper.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} + + +// Delete all poly2tri points from a vector and clear the vector +static void deleteAndClear(vector &vec) +{ + for(U32 i = 0; i < vec.size(); i++) + delete vec[i]; + + vec.clear(); +} + + +// Shrink large polygons by reducing each coordinate by 1 in the +// general direction of the last point as we wind around +// +// This normally wouldn't work in every case, but our upscaled-by-1000 polygons +// have little chance to create new duplicate points with this method. +// +// For information on why this was needed, see: +// +// https://code.google.com/p/poly2tri/issues/detail?id=90 +// +static void edgeShrink(Path &path) +{ + U32 prev = path.size() - 1; + for(U32 i = 0; i < path.size(); i++) + { + // Adjust coordinate by 1 depending on the direction + path[i].X - path[prev].X > 0 ? path[i].X-- : path[i].X++; + path[i].Y - path[prev].Y > 0 ? path[i].Y-- : path[i].Y++; + + prev = i; + } +} + + +// This uses poly2tri to triangulate. poly2tri isn't very robust so clipper needs to do +// the cleaning of points before getting here. +// +// A tree structure of polygons is required for doing complex polygons-within-polygons. +// For reference discussion on how this started to be developed, see here: +// +// https://code.google.com/p/poly2tri/issues/detail?id=74 +// +// For assistance with a special case crash, see this utility: +// http://javascript.poly2tri.googlecode.com/hg/index.html +// +// FIXME: what is ignoreFills and ignoreHoles for? kaen? +bool clip2tri::triangulateComplex(vector &outputTriangles, const Path &outline, + const PolyTree &polyTree, bool ignoreFills, bool ignoreHoles) +{ + // Keep track of memory for all the poly2tri objects we create + vector cdtRegistry; + vector > holesRegistry; + vector > polylinesRegistry; + + + // Let's be tricky and add our outline to the root node (it should have none), it'll be + // our first Clipper hole + PolyNode *rootNode = NULL; + + PolyNode tempNode; + if(polyTree.Total() == 0) // Polytree is empty with no root node, e.g. on an empty level + rootNode = &tempNode; + else + rootNode = polyTree.GetFirst()->Parent; + + rootNode->Contour = outline; + + // Now traverse our polyline nodes and triangulate them with only their children holes + PolyNode *currentNode = rootNode; + while(currentNode != NULL) + { + // A Clipper hole is actually what we want to build zones for; they become our bounding + // polylines. poly2tri holes are therefore the inverse + if((!ignoreHoles && currentNode->IsHole()) || + (!ignoreFills && !currentNode->IsHole())) + { + // Build up this polyline in poly2tri's format (downscale Clipper points) + vector polyline; + for(U32 j = 0; j < currentNode->Contour.size(); j++) + polyline.push_back(new p2t::Point(F64(currentNode->Contour[j].X), F64(currentNode->Contour[j].Y))); + + polylinesRegistry.push_back(polyline); // Memory + + // Set our polyline in poly2tri + p2t::CDT* cdt = new p2t::CDT(polyline); + cdtRegistry.push_back(cdt); + + for(U32 j = 0; j < currentNode->Childs.size(); j++) + { + PolyNode *childNode = currentNode->Childs[j]; + + // Slightly modify the polygon to guarantee no duplicate points + edgeShrink(childNode->Contour); + + vector hole; + for(U32 k = 0; k < childNode->Contour.size(); k++) + hole.push_back(new p2t::Point(F64(childNode->Contour[k].X), F64(childNode->Contour[k].Y))); + + holesRegistry.push_back(hole); // Memory + + // Add the holes for this polyline + cdt->AddHole(hole); + } + + cdt->Triangulate(); + + // Add current output triangles to our total + vector currentOutput = cdt->GetTriangles(); + + // Copy our data to TNL::Point and to our output Vector + p2t::Triangle *currentTriangle; + for(U32 j = 0; j < currentOutput.size(); j++) + { + currentTriangle = currentOutput[j]; + outputTriangles.push_back(Point(currentTriangle->GetPoint(0)->x * CLIPPER_SCALE_FACT_INVERSE, currentTriangle->GetPoint(0)->y * CLIPPER_SCALE_FACT_INVERSE)); + outputTriangles.push_back(Point(currentTriangle->GetPoint(1)->x * CLIPPER_SCALE_FACT_INVERSE, currentTriangle->GetPoint(1)->y * CLIPPER_SCALE_FACT_INVERSE)); + outputTriangles.push_back(Point(currentTriangle->GetPoint(2)->x * CLIPPER_SCALE_FACT_INVERSE, currentTriangle->GetPoint(2)->y * CLIPPER_SCALE_FACT_INVERSE)); + } + } + + currentNode = currentNode->GetNext(); + } + + + // Clean up memory used with poly2tri + // + // Clean-up workers + for(S32 i = 0; i < cdtRegistry.size(); i++) + delete cdtRegistry[i]; + + // Free the polylines + for(S32 i = 0; i < polylinesRegistry.size(); i++) + { + vector polyline = polylinesRegistry[i]; + deleteAndClear(polyline); + } + + // Free the holes + for(S32 i = 0; i < holesRegistry.size(); i++) + { + vector hole = holesRegistry[i]; + deleteAndClear(hole); + } + + // Make sure we have output data + if(outputTriangles.size() == 0) + return false; + + return true; +} + + +} /* namespace c2t */ diff --git a/src/3rdparty/clip2tri/clip2tri.h b/src/3rdparty/clip2tri/clip2tri.h new file mode 100644 index 0000000..982c804 --- /dev/null +++ b/src/3rdparty/clip2tri/clip2tri.h @@ -0,0 +1,86 @@ +/* + * Authors: kaen, raptor, sam686, watusimoto + * + * Originally from the bitfighter source code + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Bitfighter developers + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CLIP2TRI_H_ +#define CLIP2TRI_H_ + +#include +#include "../clipper/clipper.h" + +using namespace std; +using namespace ClipperLib; + +namespace c2t +{ + +typedef signed int S32; +typedef signed long long S64; +typedef unsigned int U32; +typedef float F32; +typedef double F64; + + +struct Point +{ + F32 x; + F32 y; + + Point(); + Point(const Point &pt); + + template + Point(T in_x, U in_y) { x = static_cast(in_x); y = static_cast(in_y); } +}; + + +class clip2tri +{ +private: + // + Path upscaleClipperPoints(const vector &inputPolygon); + + // These operate on a vector of polygons + Paths upscaleClipperPoints(const vector > &inputPolygons); + vector > downscaleClipperPoints(const Paths &inputPolygons); + + bool mergePolysToPolyTree(const vector > &inputPolygons, PolyTree &solution); + + bool triangulateComplex(vector &outputTriangles, const Path &outline, + const PolyTree &polyTree, bool ignoreFills = true, bool ignoreHoles = false); + +public: + clip2tri(); + virtual ~clip2tri(); + + void triangulate(const vector > &inputPolygons, vector &outputTriangles, + const vector &boundingPolygon); +}; + +} /* namespace c2t */ + +#endif /* CLIP2TRI_H_ */ diff --git a/src/3rdparty/clip2tri/clip2tri.pro b/src/3rdparty/clip2tri/clip2tri.pro new file mode 100644 index 0000000..50901c0 --- /dev/null +++ b/src/3rdparty/clip2tri/clip2tri.pro @@ -0,0 +1,18 @@ +TARGET = clip2tri + +CONFIG += staticlib exceptions + +load(qt_helper_lib) + +# workaround for QTBUG-31586 +contains(QT_CONFIG, c++11): CONFIG += c++11 + +*-g++* { + QMAKE_CXXFLAGS += -O3 -ftree-vectorize -ffast-math -funsafe-math-optimizations -Wno-error=return-type +} + +HEADERS += clip2tri.h +SOURCES += clip2tri.cpp + +LIBS_PRIVATE += -L$$MODULE_BASE_OUTDIR/lib -lpoly2tri$$qtPlatformTargetSuffix() -lclipper$$qtPlatformTargetSuffix() + diff --git a/src/3rdparty/clip2tri_legal.qdoc b/src/3rdparty/clip2tri_legal.qdoc new file mode 100644 index 0000000..65a5fa7 --- /dev/null +++ b/src/3rdparty/clip2tri_legal.qdoc @@ -0,0 +1,32 @@ +/*! +\page legal-clip2tri.html +\title Clip2Tri Polygon Triangulation Library +\ingroup licensing + +\legalese +\code +Clip2Tri Copyright (c) 2014 Bitfighter developers +https://github.com/raptor/clip2tri + +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +\endcode +\endlegalese +*/ diff --git a/src/3rdparty/clipper/clipper.cpp b/src/3rdparty/clipper/clipper.cpp new file mode 100644 index 0000000..7a27bc4 --- /dev/null +++ b/src/3rdparty/clipper/clipper.cpp @@ -0,0 +1,4605 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.1.3a * +* Date : 22 January 2014 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2014 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +#include "clipper.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +#ifdef use_int32 + static cInt const loRange = 46340; + static cInt const hiRange = 46340; +#else + static cInt const loRange = 0x3FFFFFFF; + static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; + typedef unsigned long long ulong64; +#endif + +static double const pi = 3.141592653589793238; +static double const two_pi = pi *2; +static double const def_arc_tolerance = 0.25; + +enum Direction { dRightToLeft, dLeftToRight }; + +static int const Unassigned = -1; //edge not currently 'owning' a solution +static int const Skip = -2; //edge that would otherwise close a path + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) + +struct TEdge { + IntPoint Bot; + IntPoint Curr; + IntPoint Top; + IntPoint Delta; + double Dx; + PolyType PolyTyp; + EdgeSide Side; + int WindDelta; //1 or -1 depending on winding direction + int WindCnt; + int WindCnt2; //winding count of the opposite polytype + int OutIdx; + TEdge *Next; + TEdge *Prev; + TEdge *NextInLML; + TEdge *NextInAEL; + TEdge *PrevInAEL; + TEdge *NextInSEL; + TEdge *PrevInSEL; +}; + +struct IntersectNode { + TEdge *Edge1; + TEdge *Edge2; + IntPoint Pt; +}; + +struct LocalMinima { + cInt Y; + TEdge *LeftBound; + TEdge *RightBound; + LocalMinima *Next; +}; + +struct OutPt; + +struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *PolyNd; + OutPt *Pts; + OutPt *BottomPt; +}; + +struct OutPt { + int Idx; + IntPoint Pt; + OutPt *Next; + OutPt *Prev; +}; + +struct Join { + OutPt *OutPt1; + OutPt *OutPt2; + IntPoint OffPt; +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +inline cInt Round(double val) +{ + if ((val < 0)) return static_cast(val - 0.5); + else return static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +inline cInt Abs(cInt val) +{ + return val < 0 ? -val : val; +} + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const +{ + return (int)AllNodes.size(); +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false) +{ +} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const +{ + return (int)Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = (unsigned)Childs.size(); + Childs.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() const +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsOpen() const +{ + return m_IsOpen; +} +//------------------------------------------------------------------------------ + +#ifndef use_int32 + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((cInt)9223372036854775807); //ie 2^63 -1 +// Int128 val2((cInt)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 +{ + public: + + cUInt lo; + cInt hi; + + Int128(cInt _lo = 0) + { + lo = (cUInt)_lo; + if (_lo < 0) hi = -1; else hi = 0; + } + + + Int128(const Int128 &val): lo(val.lo), hi(val.hi){} + + Int128(const cInt& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + + Int128& operator = (const cInt &val) + { + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; + return *this; + } + + bool operator == (const Int128 &val) const + {return (hi == val.hi && lo == val.lo);} + + bool operator != (const Int128 &val) const + { return !(*this == val);} + + bool operator > (const Int128 &val) const + { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator < (const Int128 &val) const + { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator >= (const Int128 &val) const + { return !(*this < val);} + + bool operator <= (const Int128 &val) const + { return !(*this > val);} + + Int128& operator += (const Int128 &rhs) + { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) hi++; + return *this; + } + + Int128 operator + (const Int128 &rhs) const + { + Int128 result(*this); + result+= rhs; + return result; + } + + Int128& operator -= (const Int128 &rhs) + { + *this += -rhs; + return *this; + } + + Int128 operator - (const Int128 &rhs) const + { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const //unary negation + { + if (lo == 0) + return Int128(-hi,0); + else + return Int128(~hi,~lo +1); + } + + Int128 operator/ (const Int128 &rhs) const + { + if (rhs.lo == 0 && rhs.hi == 0) + throw "Int128 operator/: divide by zero"; + + bool negate = (rhs.hi < 0) != (hi < 0); + Int128 dividend = *this; + Int128 divisor = rhs; + if (dividend.hi < 0) dividend = -dividend; + if (divisor.hi < 0) divisor = -divisor; + + if (divisor < dividend) + { + Int128 result = Int128(0); + Int128 cntr = Int128(1); + while (divisor.hi >= 0 && !(divisor > dividend)) + { + divisor.hi <<= 1; + if ((cInt)divisor.lo < 0) divisor.hi++; + divisor.lo <<= 1; + + cntr.hi <<= 1; + if ((cInt)cntr.lo < 0) cntr.hi++; + cntr.lo <<= 1; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi = (ulong64)divisor.hi >> 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + + while (cntr.hi != 0 || cntr.lo != 0) + { + if (!(dividend < divisor)) + { + dividend -= divisor; + result.hi |= cntr.hi; + result.lo |= cntr.lo; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi >>= 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + } + if (negate) result = -result; + return result; + } + else if (rhs.hi == this->hi && rhs.lo == this->lo) + return Int128(negate ? -1: 1); + else + return Int128(0); + } + + double AsDouble() const + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + cUInt lo_ = ~lo + 1; + if (lo_ == 0) return (double)hi * shift64; + else return -(double)(lo_ + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } + +}; +//------------------------------------------------------------------------------ + +Int128 Int128Mul (cInt lhs, cInt rhs) +{ + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = cInt(a + (c >> 32)); + tmp.lo = cInt(c << 32); + tmp.lo += cInt(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +}; +#endif + +//------------------------------------------------------------------------------ +// Miscellaneous global functions +//------------------------------------------------------------------------------ + +bool Orientation(const Path &poly) +{ + return Area(poly) >= 0; +} +//------------------------------------------------------------------------------ + +double Area(const Path &poly) +{ + int size = (int)poly.size(); + if (size < 3) return 0; + + double a = 0; + for (int i = 0, j = size -1; i < size; ++i) + { + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; + } + return -a * 0.5; +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec) +{ + OutPt *op = outRec.Pts; + if (!op) return 0; + double a = 0; + do { + a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); + op = op->Next; + } while (op != outRec.Pts); + return a * 0.5; +} +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &Pt, OutPt *pp) +{ + OutPt *pp2 = pp; + do + { + if (pp2->Pt == Pt) return true; + pp2 = pp2->Next; + } + while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +int PointInPolygon (const IntPoint &pt, const Path &path) +{ + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf + int result = 0; + size_t cnt = path.size(); + if (cnt < 3) return 0; + IntPoint ip = path[0]; + for(size_t i = 1; i <= cnt; ++i) + { + IntPoint ipNext = (i == cnt ? path[0] : path[i]); + if (ipNext.Y == pt.Y) + { + if ((ipNext.X == pt.X) || (ip.Y == pt.Y && + ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; + } + if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) + { + if (ip.X >= pt.X) + { + if (ipNext.X > pt.X) result = 1 - result; + else + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } else + { + if (ipNext.X > pt.X) + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } + } + ip = ipNext; + } + return result; +} +//------------------------------------------------------------------------------ + +int PointInPolygon (const IntPoint &pt, OutPt *op) +{ + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf + int result = 0; + OutPt* startOp = op; + for(;;) + { + if (op->Next->Pt.Y == pt.Y) + { + if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && + ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; + } + if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) + { + if (op->Pt.X >= pt.X) + { + if (op->Next->Pt.X > pt.X) result = 1 - result; + else + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } else + { + if (op->Next->Pt.X > pt.X) + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } + } + op = op->Next; + if (startOp == op) break; + } + return result; +} +//------------------------------------------------------------------------------ + +bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) +{ + OutPt* op = OutPt1; + do + { + int res = PointInPolygon(op->Pt, OutPt2); + if (res >= 0) return res != 0; + op = op->Next; + } + while (op != OutPt1); + return true; +} +//---------------------------------------------------------------------- + +bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(e1.Delta.Y, e2.Delta.X) == Int128Mul(e1.Delta.X, e2.Delta.Y); + else +#endif + return e1.Delta.Y * e2.Delta.X == e1.Delta.X * e2.Delta.Y; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); +} +//------------------------------------------------------------------------------ + +inline bool IsHorizontal(TEdge &e) +{ + return e.Delta.Y == 0; +} +//------------------------------------------------------------------------------ + +inline double GetDx(const IntPoint pt1, const IntPoint pt2) +{ + return (pt1.Y == pt2.Y) ? + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +inline void SetDx(TEdge &e) +{ + e.Delta.X = (e.Top.X - e.Bot.X); + e.Delta.Y = (e.Top.Y - e.Bot.Y); + + if (e.Delta.Y == 0) e.Dx = HORIZONTAL; + else e.Dx = (double)(e.Delta.X) / e.Delta.Y; +} +//--------------------------------------------------------------------------- + +inline void SwapSides(TEdge &Edge1, TEdge &Edge2) +{ + EdgeSide Side = Edge1.Side; + Edge1.Side = Edge2.Side; + Edge2.Side = Side; +} +//------------------------------------------------------------------------------ + +inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) +{ + int OutIdx = Edge1.OutIdx; + Edge1.OutIdx = Edge2.OutIdx; + Edge2.OutIdx = OutIdx; +} +//------------------------------------------------------------------------------ + +inline cInt TopX(TEdge &edge, const cInt currentY) +{ + return ( currentY == edge.Top.Y ) ? + edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); +} +//------------------------------------------------------------------------------ + +bool IntersectPoint(TEdge &Edge1, TEdge &Edge2, + IntPoint &ip, bool UseFullInt64Range) +{ +#ifdef use_xyz + ip.Z = 0; +#endif + double b1, b2; + //nb: with very large coordinate values, it's possible for SlopesEqual() to + //return false but for the edge.Dx value be equal due to double precision rounding. + if (SlopesEqual(Edge1, Edge2, UseFullInt64Range) || Edge1.Dx == Edge2.Dx) + { + if (Edge2.Bot.Y > Edge1.Bot.Y) ip = Edge2.Bot; + else ip = Edge1.Bot; + return false; + } + else if (Edge1.Delta.X == 0) + { + ip.X = Edge1.Bot.X; + if (IsHorizontal(Edge2)) + ip.Y = Edge2.Bot.Y; + else + { + b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); + ip.Y = Round(ip.X / Edge2.Dx + b2); + } + } + else if (Edge2.Delta.X == 0) + { + ip.X = Edge2.Bot.X; + if (IsHorizontal(Edge1)) + ip.Y = Edge1.Bot.Y; + else + { + b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); + ip.Y = Round(ip.X / Edge1.Dx + b1); + } + } + else + { + b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; + b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); + ip.Y = Round(q); + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = Round(Edge1.Dx * q + b1); + else + ip.X = Round(Edge2.Dx * q + b2); + } + + if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) + { + if (Edge1.Top.Y > Edge2.Top.Y) + ip.Y = Edge1.Top.Y; + else + ip.Y = Edge2.Top.Y; + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = TopX(Edge1, ip.Y); + else + ip.X = TopX(Edge2, ip.Y); + } + return true; +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) +{ + if (!pp) return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->Next; + pp1->Next = pp1->Prev; + pp1->Prev = pp2; + pp1 = pp2; + } while( pp1 != pp ); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt*& pp) +{ + if (pp == 0) return; + pp->Prev->Next = 0; + while( pp ) + { + OutPt *tmpPp = pp; + pp = pp->Next; + delete tmpPp; + } +} +//------------------------------------------------------------------------------ + +inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) +{ + std::memset(e, 0, sizeof(TEdge)); + e->Next = eNext; + e->Prev = ePrev; + e->Curr = Pt; + e->OutIdx = Unassigned; +} +//------------------------------------------------------------------------------ + +void InitEdge2(TEdge& e, PolyType Pt) +{ + if (e.Curr.Y >= e.Next->Curr.Y) + { + e.Bot = e.Curr; + e.Top = e.Next->Curr; + } else + { + e.Top = e.Curr; + e.Bot = e.Next->Curr; + } + SetDx(e); + e.PolyTyp = Pt; +} +//------------------------------------------------------------------------------ + +TEdge* RemoveEdge(TEdge* e) +{ + //removes e from double_linked_list (but without removing from memory) + e->Prev->Next = e->Next; + e->Next->Prev = e->Prev; + TEdge* result = e->Next; + e->Prev = 0; //flag as removed (see ClipperBase.Clear) + return result; +} +//------------------------------------------------------------------------------ + +inline void ReverseHorizontal(TEdge &e) +{ + //swap horizontal edges' Top and Bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + cInt tmp = e.Top.X; + e.Top.X = e.Bot.X; + e.Bot.X = tmp; +#ifdef use_xyz + tmp = e.Top.Z; + e.Top.Z = e.Bot.Z; + e.Bot.Z = tmp; +#endif +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) +{ + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) +{ + //precondition: segments are Collinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) + { + if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; + return pt1.X < pt2.X; + } else + { + if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) +{ + OutPt *p = btmPt1->Prev; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; + double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + p = btmPt1->Next; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; + double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + + p = btmPt2->Prev; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; + double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + p = btmPt2->Next; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; + double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt* GetBottomPt(OutPt *pp) +{ + OutPt* dups = 0; + OutPt* p = pp->Next; + while (p != pp) + { + if (p->Pt.Y > pp->Pt.Y) + { + pp = p; + dups = 0; + } + else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) + { + if (p->Pt.X < pp->Pt.X) + { + dups = 0; + pp = p; + } else + { + if (p->Next != pp && p->Prev != pp) dups = p; + } + } + p = p->Next; + } + if (dups) + { + //there appears to be at least 2 vertices at BottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups->Next; + while (dups->Pt != pp->Pt) dups = dups->Next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool FindSegment(OutPt* &pp, bool UseFullInt64Range, + IntPoint &pt1, IntPoint &pt2) +{ + //OutPt1 & OutPt2 => the overlap segment (if the function returns true) + if (!pp) return false; + OutPt* pp2 = pp; + IntPoint pt1a = pt1, pt2a = pt2; + do + { + if (SlopesEqual(pt1a, pt2a, pp->Pt, pp->Prev->Pt, UseFullInt64Range) && + SlopesEqual(pt1a, pt2a, pp->Pt, UseFullInt64Range) && + GetOverlapSegment(pt1a, pt2a, pp->Pt, pp->Prev->Pt, pt1, pt2)) + return true; + pp = pp->Next; + } + while (pp != pp2); + return false; +} +//------------------------------------------------------------------------------ + +bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, + const IntPoint pt2, const IntPoint pt3) +{ + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) + return false; + else if (pt1.X != pt3.X) + return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else + return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); +} +//------------------------------------------------------------------------------ + +OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint Pt) +{ + if (p1 == p2) throw "JoinError"; + OutPt* result = new OutPt; + result->Pt = Pt; + if (p2 == p1->Next) + { + p1->Next = result; + p2->Prev = result; + result->Next = p2; + result->Prev = p1; + } else + { + p2->Next = result; + p1->Prev = result; + result->Next = p1; + result->Prev = p2; + } + return result; +} +//------------------------------------------------------------------------------ + +bool HorzSegmentsOverlap(const IntPoint& pt1a, const IntPoint& pt1b, + const IntPoint& pt2a, const IntPoint& pt2b) +{ + //precondition: both segments are horizontal + if ((pt1a.X > pt2a.X) == (pt1a.X < pt2b.X)) return true; + else if ((pt1b.X > pt2a.X) == (pt1b.X < pt2b.X)) return true; + else if ((pt2a.X > pt1a.X) == (pt2a.X < pt1b.X)) return true; + else if ((pt2b.X > pt1a.X) == (pt2b.X < pt1b.X)) return true; + else if ((pt1a.X == pt2a.X) && (pt1b.X == pt2b.X)) return true; + else if ((pt1a.X == pt2b.X) && (pt1b.X == pt2a.X)) return true; + else return false; +} + + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + +ClipperBase::ClipperBase() //constructor +{ + m_MinimaList = 0; + m_CurrentLM = 0; + m_UseFullRange = false; +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() //destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void RangeTest(const IntPoint& Pt, bool& useFullRange) +{ + if (useFullRange) + { + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw "Coordinate outside allowed range"; + } + else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) + { + useFullRange = true; + RangeTest(Pt, useFullRange); + } +} +//------------------------------------------------------------------------------ + +TEdge* FindNextLocMin(TEdge* E) +{ + for (;;) + { + while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; + if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; + while (IsHorizontal(*E->Prev)) E = E->Prev; + TEdge* E2 = E; + while (IsHorizontal(*E)) E = E->Next; + if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. + if (E2->Prev->Bot.X < E->Bot.X) E = E2; + break; + } + return E; +} +//------------------------------------------------------------------------------ + +TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) +{ + TEdge *EStart = E, *Result = E; + TEdge *Horz = 0; + cInt StartX; + if (IsHorizontal(*E)) + { + //it's possible for adjacent overlapping horz edges to start heading left + //before finishing right, so ... + if (IsClockwise) StartX = E->Prev->Bot.X; + else StartX = E->Next->Bot.X; + if (E->Bot.X != StartX) ReverseHorizontal(*E); + } + + if (Result->OutIdx != Skip) + { + if (IsClockwise) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X == Result->Next->Top.X) + { + if (!IsClockwise) Result = Horz->Prev; + } + else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X) + { + if (!IsClockwise) Result = Horz->Next; + } + else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } + + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + } + + if (Result->OutIdx == Skip) + { + //if edges still remain in the current bound beyond the skip edge then + //create another LocMin and call ProcessBound once more + E = Result; + if (IsClockwise) + { + while (E->Top.Y == E->Next->Bot.Y) E = E->Next; + //don't include top horizontals when parsing a bound a second time, + //they will be contained in the opposite bound ... + while (E != Result && IsHorizontal(*E)) E = E->Prev; + } else + { + while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; + while (E != Result && IsHorizontal(*E)) E = E->Next; + } + if (E == Result) + { + if (IsClockwise) Result = E->Next; + else Result = E->Prev; + } else + { + //there are more edges in the bound beyond result starting with E + if (IsClockwise) + E = Result->Next; + else + E = Result->Prev; + LocalMinima* locMin = new LocalMinima; + locMin->Next = 0; + locMin->Y = E->Bot.Y; + locMin->LeftBound = 0; + locMin->RightBound = E; + locMin->RightBound->WindDelta = 0; + Result = ProcessBound(locMin->RightBound, IsClockwise); + InsertLocalMinima(locMin); + } + } + return Result; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) +{ +#ifdef use_lines + if (!Closed && PolyTyp == ptClip) + throw clipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw clipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.size() -1; + if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; + while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; + + //create a new edge array ... + TEdge *edges = new TEdge [highI +1]; + + bool IsFlat = true; + //1. Basic (first) edge initialization ... + try + { + edges[1].Curr = pg[1]; + RangeTest(pg[0], m_UseFullRange); + RangeTest(pg[highI], m_UseFullRange); + InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); + InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) + { + RangeTest(pg[i], m_UseFullRange); + InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); + } + } + catch(...) + { + delete [] edges; + throw; //range test fails + } + TEdge *eStart = &edges[0]; + + //2. Remove duplicate vertices, and (when closed) collinear edges ... + TEdge *E = eStart, *eLoopStop = eStart; + for (;;) + { + if ((E->Curr == E->Next->Curr)) + { + if (E == E->Next) break; + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + eLoopStop = E; + continue; + } + if (E->Prev == E->Next) + break; //only two vertices + else if (Closed && + SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) + { + //Collinear edges are allowed for open paths but in closed paths + //the default is to merge adjacent collinear edges into a single edge. + //However, if the PreserveCollinear property is enabled, only overlapping + //collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + E = E->Prev; + eLoopStop = E; + continue; + } + E = E->Next; + if (E == eLoopStop) break; + } + + if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) + { + delete [] edges; + return false; + } + + if (!Closed) + { + m_HasOpenPaths = true; + eStart->Prev->OutIdx = Skip; + } + + //3. Do second stage of edge initialization ... + E = eStart; + do + { + InitEdge2(*E, PolyTyp); + E = E->Next; + if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; + } + while (E != eStart); + + //4. Finally, add edge bounds to LocalMinima list ... + + //Totally flat paths must be handled differently when adding them + //to LocalMinima list to avoid endless loops etc ... + if (IsFlat) + { + if (Closed) + { + delete [] edges; + return false; + } + E->Prev->OutIdx = Skip; + if (E->Prev->Bot.X < E->Prev->Top.X) ReverseHorizontal(*E->Prev); + LocalMinima* locMin = new LocalMinima(); + locMin->Next = 0; + locMin->Y = E->Bot.Y; + locMin->LeftBound = 0; + locMin->RightBound = E; + locMin->RightBound->Side = esRight; + locMin->RightBound->WindDelta = 0; + while (E->Next->OutIdx != Skip) + { + E->NextInLML = E->Next; + if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + InsertLocalMinima(locMin); + m_edges.push_back(edges); + return true; + } + + m_edges.push_back(edges); + bool clockwise; + TEdge* EMin = 0; + for (;;) + { + E = FindNextLocMin(E); + if (E == EMin) break; + else if (!EMin) EMin = E; + + //E and E.Prev now share a local minima (left aligned if horizontal). + //Compare their slopes to find which starts which bound ... + LocalMinima* locMin = new LocalMinima; + locMin->Next = 0; + locMin->Y = E->Bot.Y; + if (E->Dx < E->Prev->Dx) + { + locMin->LeftBound = E->Prev; + locMin->RightBound = E; + clockwise = false; //Q.nextInLML = Q.prev + } else + { + locMin->LeftBound = E; + locMin->RightBound = E->Prev; + clockwise = true; //Q.nextInLML = Q.next + } + locMin->LeftBound->Side = esLeft; + locMin->RightBound->Side = esRight; + + if (!Closed) locMin->LeftBound->WindDelta = 0; + else if (locMin->LeftBound->Next == locMin->RightBound) + locMin->LeftBound->WindDelta = -1; + else locMin->LeftBound->WindDelta = 1; + locMin->RightBound->WindDelta = -locMin->LeftBound->WindDelta; + + E = ProcessBound(locMin->LeftBound, clockwise); + TEdge* E2 = ProcessBound(locMin->RightBound, !clockwise); + + if (locMin->LeftBound->OutIdx == Skip) + locMin->LeftBound = 0; + else if (locMin->RightBound->OutIdx == Skip) + locMin->RightBound = 0; + InsertLocalMinima(locMin); + if (!clockwise) E = E2; + } + return true; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) +{ + bool result = false; + for (Paths::size_type i = 0; i < ppg.size(); ++i) + if (AddPath(ppg[i], PolyTyp, Closed)) result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::InsertLocalMinima(LocalMinima *newLm) +{ + if( ! m_MinimaList ) + { + m_MinimaList = newLm; + } + else if( newLm->Y >= m_MinimaList->Y ) + { + newLm->Next = m_MinimaList; + m_MinimaList = newLm; + } else + { + LocalMinima* tmpLm = m_MinimaList; + while( tmpLm->Next && ( newLm->Y < tmpLm->Next->Y ) ) + tmpLm = tmpLm->Next; + newLm->Next = tmpLm->Next; + tmpLm->Next = newLm; + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::Clear() +{ + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) + { + //for each edge array in turn, find the first used edge and + //check for and remove any hiddenPts in each edge in the array. + TEdge* edges = m_edges[i]; + delete [] edges; + } + m_edges.clear(); + m_UseFullRange = false; + m_HasOpenPaths = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() +{ + m_CurrentLM = m_MinimaList; + if( !m_CurrentLM ) return; //ie nothing to process + + //reset all edges ... + LocalMinima* lm = m_MinimaList; + while( lm ) + { + TEdge* e = lm->LeftBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esLeft; + e->OutIdx = Unassigned; + } + + e = lm->RightBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esRight; + e->OutIdx = Unassigned; + } + lm = lm->Next; + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() +{ + while( m_MinimaList ) + { + LocalMinima* tmpLm = m_MinimaList->Next; + delete m_MinimaList; + m_MinimaList = tmpLm; + } + m_CurrentLM = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::PopLocalMinima() +{ + if( ! m_CurrentLM ) return; + m_CurrentLM = m_CurrentLM->Next; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() +{ + IntRect result; + LocalMinima* lm = m_MinimaList; + if (!lm) + { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->LeftBound->Bot.X; + result.top = lm->LeftBound->Bot.Y; + result.right = lm->LeftBound->Bot.X; + result.bottom = lm->LeftBound->Bot.Y; + while (lm) + { + if (lm->LeftBound->Bot.Y > result.bottom) + result.bottom = lm->LeftBound->Bot.Y; + TEdge* e = lm->LeftBound; + for (;;) { + TEdge* bottomE = e; + while (e->NextInLML) + { + if (e->Bot.X < result.left) result.left = e->Bot.X; + if (e->Bot.X > result.right) result.right = e->Bot.X; + e = e->NextInLML; + } + if (e->Bot.X < result.left) result.left = e->Bot.X; + if (e->Bot.X > result.right) result.right = e->Bot.X; + if (e->Top.X < result.left) result.left = e->Top.X; + if (e->Top.X > result.right) result.right = e->Top.X; + if (e->Top.Y < result.top) result.top = e->Top.Y; + + if (bottomE == lm->LeftBound) e = lm->RightBound; + else break; + } + lm = lm->Next; + } + return result; +} + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper(int initOptions) : ClipperBase() //constructor +{ + m_ActiveEdges = 0; + m_SortedEdges = 0; + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); + m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); + m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); + m_HasOpenPaths = false; +#ifdef use_xyz + m_ZFill = 0; +#endif +} +//------------------------------------------------------------------------------ + +Clipper::~Clipper() //destructor +{ + Clear(); + m_Scanbeam.clear(); +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::ZFillFunction(TZFillCallback zFillFunc) +{ + m_ZFill = zFillFunc; +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::Reset() +{ + ClipperBase::Reset(); + m_Scanbeam.clear(); + m_ActiveEdges = 0; + m_SortedEdges = 0; + LocalMinima* lm = m_MinimaList; + while (lm) + { + InsertScanbeam(lm->Y); + lm = lm->Next; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + if (m_HasOpenPaths) + throw clipperException("Error: PolyTree struct is need for open path clipping."); + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult(solution); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outrec) +{ + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outrec.FirstLeft || + (outrec.IsHole != outrec.FirstLeft->IsHole && + outrec.FirstLeft->Pts)) return; + + OutRec* orfl = outrec.FirstLeft; + while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) + orfl = orfl->FirstLeft; + outrec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() +{ + bool succeeded = true; + try { + Reset(); + if (!m_CurrentLM) return false; + cInt botY = PopScanbeam(); + do { + InsertLocalMinimaIntoAEL(botY); + ClearGhostJoins(); + ProcessHorizontals(false); + if (m_Scanbeam.empty()) break; + cInt topY = PopScanbeam(); + succeeded = ProcessIntersections(botY, topY); + if (!succeeded) break; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + } while (!m_Scanbeam.empty() || m_CurrentLM); + } + catch(...) + { + succeeded = false; + } + + if (succeeded) + { + //fix orientations ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts || outRec->IsOpen) continue; + if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) + ReversePolyPtLinks(outRec->Pts); + } + + if (!m_Joins.empty()) JoinCommonEdges(); + + //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (outRec->Pts && !outRec->IsOpen) + FixupOutPolygon(*outRec); + } + + if (m_StrictSimple) DoSimplePolygons(); + } + + ClearJoins(); + ClearGhostJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertScanbeam(const cInt Y) +{ + m_Scanbeam.insert(Y); +} +//------------------------------------------------------------------------------ + +cInt Clipper::PopScanbeam() +{ + cInt Y = *m_Scanbeam.begin(); + m_Scanbeam.erase(m_Scanbeam.begin()); + return Y; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeAllOutRecs(){ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeOutRec(PolyOutList::size_type index) +{ + OutRec *outRec = m_PolyOuts[index]; + if (outRec->Pts) DisposeOutPts(outRec->Pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) +{ + TEdge *e = edge.PrevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; + if (!e) + { + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + edge.WindCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc WindCnt2 + } + else if (edge.WindDelta == 0 && m_ClipType != ctUnion) + { + edge.WindCnt = 1; + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else if (IsEvenOddFillType(edge)) + { + //EvenOdd filling ... + if (edge.WindDelta == 0) + { + //are we inside a subj polygon ... + bool Inside = true; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) + Inside = !Inside; + e2 = e2->PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } + else + { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else + { + //nonZero, Positive or Negative filling ... + if (e->WindCnt * e->WindDelta < 0) + { + //prev edge is 'decreasing' WindCount (WC) toward zero + //so we're outside the previous polygon ... + if (Abs(e->WindCnt) > 1) + { + //outside prev poly but still inside another. + //when reversing direction of prev poly use the same WC + if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise continue to 'decrease' WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + else + //now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } else + { + //prev edge is 'increasing' WindCount (WC) away from zero + //so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); + //if wind direction is reversing prev then use same WC + else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise add to WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + + //update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) + { + //EvenOdd filling ... + while (e != &edge) + { + if (e->WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e->NextInAEL; + } + } else + { + //nonZero, Positive or Negative filling ... + while ( e != &edge ) + { + edge.WindCnt2 += e->WindDelta; + e = e->NextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_SubjFillType == pftEvenOdd; else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_ClipFillType == pftEvenOdd; else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge& edge) const +{ + PolyFillType pft, pft2; + if (edge.PolyTyp == ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch(pft) + { + case pftEvenOdd: + //return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; + break; + case pftNonZero: + if (Abs(edge.WindCnt) != 1) return false; + break; + case pftPositive: + if (edge.WindCnt != 1) return false; + break; + default: //pftNegative + if (edge.WindCnt != -1) return false; + } + + switch(m_ClipType) + { + case ctIntersection: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctUnion: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + break; + case ctDifference: + if (edge.PolyTyp == ptSubject) + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctXor: + if (edge.WindDelta == 0) //XOr always contributing unless open + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + break; + default: + return true; + } +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + OutPt* result; + TEdge *e, *prevE; + if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) + { + result = AddOutPt(e1, Pt); + e2->OutIdx = e1->OutIdx; + e1->Side = esLeft; + e2->Side = esRight; + e = e1; + if (e->PrevInAEL == e2) + prevE = e2->PrevInAEL; + else + prevE = e->PrevInAEL; + } else + { + result = AddOutPt(e2, Pt); + e1->OutIdx = e2->OutIdx; + e1->Side = esRight; + e2->Side = esLeft; + e = e2; + if (e->PrevInAEL == e1) + prevE = e1->PrevInAEL; + else + prevE = e->PrevInAEL; + } + + if (prevE && prevE->OutIdx >= 0 && + (TopX(*prevE, Pt.Y) == TopX(*e, Pt.Y)) && + SlopesEqual(*e, *prevE, m_UseFullRange) && + (e->WindDelta != 0) && (prevE->WindDelta != 0)) + { + OutPt* outPt = AddOutPt(prevE, Pt); + AddJoin(result, outPt, e->Top); + } + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + AddOutPt( e1, Pt ); + if (e2->WindDelta == 0) AddOutPt(e2, Pt); + if( e1->OutIdx == e2->OutIdx ) + { + e1->OutIdx = Unassigned; + e2->OutIdx = Unassigned; + } + else if (e1->OutIdx < e2->OutIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) +{ + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if( !m_SortedEdges ) + { + m_SortedEdges = edge; + edge->PrevInSEL = 0; + edge->NextInSEL = 0; + } + else + { + edge->NextInSEL = m_SortedEdges; + edge->PrevInSEL = 0; + m_SortedEdges->PrevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() +{ + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while ( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) +{ + Join* j = new Join; + j->OutPt1 = op1; + j->OutPt2 = op2; + j->OffPt = OffPt; + m_Joins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearGhostJoins() +{ + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) + delete m_GhostJoins[i]; + m_GhostJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) +{ + Join* j = new Join; + j->OutPt1 = op; + j->OutPt2 = 0; + j->OffPt = OffPt; + m_GhostJoins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) +{ + while( m_CurrentLM && ( m_CurrentLM->Y == botY ) ) + { + TEdge* lb = m_CurrentLM->LeftBound; + TEdge* rb = m_CurrentLM->RightBound; + PopLocalMinima(); + OutPt *Op1 = 0; + if (!lb) + { + //nb: don't insert LB into either AEL or SEL + InsertEdgeIntoAEL(rb, 0); + SetWindingCount(*rb); + if (IsContributing(*rb)) + Op1 = AddOutPt(rb, rb->Bot); + } + else if (!rb) + { + InsertEdgeIntoAEL(lb, 0); + SetWindingCount(*lb); + if (IsContributing(*lb)) + Op1 = AddOutPt(lb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + else + { + InsertEdgeIntoAEL(lb, 0); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount( *lb ); + rb->WindCnt = lb->WindCnt; + rb->WindCnt2 = lb->WindCnt2; + if (IsContributing(*lb)) + Op1 = AddLocalMinPoly(lb, rb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + + if (rb) + { + if(IsHorizontal(*rb)) AddEdgeToSEL(rb); + else InsertScanbeam( rb->Top.Y ); + } + + if (!lb || !rb) continue; + + //if any output polygons share an edge, they'll need joining later ... + if (Op1 && IsHorizontal(*rb) && + m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) + { + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) + { + Join* jr = m_GhostJoins[i]; + //if the horizontal Rb and a 'ghost' horizontal overlap, then convert + //the 'ghost' join to a real join ready for later ... + if (HorzSegmentsOverlap(jr->OutPt1->Pt, jr->OffPt, rb->Bot, rb->Top)) + AddJoin(jr->OutPt1, Op1, jr->OffPt); + } + } + + if (lb->OutIdx >= 0 && lb->PrevInAEL && + lb->PrevInAEL->Curr.X == lb->Bot.X && + lb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(*lb->PrevInAEL, *lb, m_UseFullRange) && + (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); + AddJoin(Op1, Op2, lb->Top); + } + + if(lb->NextInAEL != rb) + { + + if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(*rb->PrevInAEL, *rb, m_UseFullRange) && + (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); + AddJoin(Op1, Op2, rb->Top); + } + + TEdge* e = lb->NextInAEL; + if (e) + { + while( e != rb ) + { + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the Right of param2 ABOVE the intersection ... + IntersectEdges(rb , e , lb->Curr); //order important here + e = e->NextInAEL; + } + } + } + + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromAEL(TEdge *e) +{ + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted + if( AelPrev ) AelPrev->NextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if( AelNext ) AelNext->PrevInAEL = AelPrev; + e->NextInAEL = 0; + e->PrevInAEL = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) +{ + TEdge* SelPrev = e->PrevInSEL; + TEdge* SelNext = e->NextInSEL; + if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted + if( SelPrev ) SelPrev->NextInSEL = SelNext; + else m_SortedEdges = SelNext; + if( SelNext ) SelNext->PrevInSEL = SelPrev; + e->NextInSEL = 0; + e->PrevInSEL = 0; +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz + +void Clipper::SetZ(IntPoint& pt, TEdge& e) +{ + pt.Z = 0; + if (m_ZFill) + { + //put the 'preferred' point as first parameter ... + if (e.OutIdx < 0) + (*m_ZFill)(e.Bot, e.Top, pt); //outside a path so presume entering + else + (*m_ZFill)(e.Top, e.Bot, pt); //inside a path so presume exiting + } +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &Pt, bool protect) +{ + //e1 will be to the Left of e2 BELOW the intersection. Therefore e1 is before + //e2 in AEL except when e1 is being inserted at the intersection point ... + bool e1stops = !protect && !e1->NextInLML && + e1->Top.X == Pt.X && e1->Top.Y == Pt.Y; + bool e2stops = !protect && !e2->NextInLML && + e2->Top.X == Pt.X && e2->Top.Y == Pt.Y; + bool e1Contributing = ( e1->OutIdx >= 0 ); + bool e2Contributing = ( e2->OutIdx >= 0 ); + +#ifdef use_lines + //if either edge is on an OPEN path ... + if (e1->WindDelta == 0 || e2->WindDelta == 0) + { + //ignore subject-subject open path intersections UNLESS they + //are both open paths, AND they are both 'contributing maximas' ... + if (e1->WindDelta == 0 && e2->WindDelta == 0) + { + if ((e1stops || e2stops) && e1Contributing && e2Contributing) + AddLocalMaxPoly(e1, e2, Pt); + } + + //if intersecting a subj line with a subj poly ... + else if (e1->PolyTyp == e2->PolyTyp && + e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) + { + if (e1->WindDelta == 0) + { + if (e2Contributing) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + } + else + { + if (e1Contributing) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + } + else if (e1->PolyTyp != e2->PolyTyp) + { + //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... + if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && + (m_ClipType != ctUnion || e2->WindCnt2 == 0)) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && + (m_ClipType != ctUnion || e1->WindCnt2 == 0)) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + + if (e1stops) + if (e1->OutIdx < 0) DeleteFromAEL(e1); + else throw clipperException("Error intersecting polylines"); + if (e2stops) + if (e2->OutIdx < 0) DeleteFromAEL(e2); + else throw clipperException("Error intersecting polylines"); + return; + } +#endif + + //update winding counts... + //assumes that e1 will be to the Right of e2 ABOVE the intersection + if ( e1->PolyTyp == e2->PolyTyp ) + { + if ( IsEvenOddFillType( *e1) ) + { + int oldE1WindCnt = e1->WindCnt; + e1->WindCnt = e2->WindCnt; + e2->WindCnt = oldE1WindCnt; + } else + { + if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; + else e1->WindCnt += e2->WindDelta; + if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; + else e2->WindCnt -= e1->WindDelta; + } + } else + { + if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; + else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; + else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->PolyTyp == ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->PolyTyp == ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + cInt e1Wc, e2Wc; + switch (e1FillType) + { + case pftPositive: e1Wc = e1->WindCnt; break; + case pftNegative: e1Wc = -e1->WindCnt; break; + default: e1Wc = Abs(e1->WindCnt); + } + switch(e2FillType) + { + case pftPositive: e2Wc = e2->WindCnt; break; + case pftNegative: e2Wc = -e2->WindCnt; break; + default: e2Wc = Abs(e2->WindCnt); + } + + if ( e1Contributing && e2Contributing ) + { + if ( e1stops || e2stops || + (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) + AddLocalMaxPoly(e1, e2, Pt); + else + { + AddOutPt(e1, Pt); + AddOutPt(e2, Pt); + SwapSides( *e1 , *e2 ); + SwapPolyIndexes( *e1 , *e2 ); + } + } + else if ( e1Contributing ) + { + if (e2Wc == 0 || e2Wc == 1) + { + AddOutPt(e1, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( e2Contributing ) + { + if (e1Wc == 0 || e1Wc == 1) + { + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( (e1Wc == 0 || e1Wc == 1) && + (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + { + //neither edge is currently contributing ... + + cInt e1Wc2, e2Wc2; + switch (e1FillType2) + { + case pftPositive: e1Wc2 = e1->WindCnt2; break; + case pftNegative : e1Wc2 = -e1->WindCnt2; break; + default: e1Wc2 = Abs(e1->WindCnt2); + } + switch (e2FillType2) + { + case pftPositive: e2Wc2 = e2->WindCnt2; break; + case pftNegative: e2Wc2 = -e2->WindCnt2; break; + default: e2Wc2 = Abs(e2->WindCnt2); + } + + if (e1->PolyTyp != e2->PolyTyp) + AddLocalMinPoly(e1, e2, Pt); + else if (e1Wc == 1 && e2Wc == 1) + switch( m_ClipType ) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctUnion: + if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctDifference: + if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, Pt); + } + else + SwapSides( *e1, *e2 ); + } + + if( (e1stops != e2stops) && + ( (e1stops && (e1->OutIdx >= 0)) || (e2stops && (e2->OutIdx >= 0)) ) ) + { + SwapSides( *e1, *e2 ); + SwapPolyIndexes( *e1, *e2 ); + } + + //finally, delete any non-contributing maxima edges ... + if( e1stops ) DeleteFromAEL( e1 ); + if( e2stops ) DeleteFromAEL( e2 ); +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) +{ + bool IsHole = false; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->OutIdx >= 0 && e2->WindDelta != 0) + { + IsHole = !IsHole; + if (! outrec->FirstLeft) + outrec->FirstLeft = m_PolyOuts[e2->OutIdx]; + } + e2 = e2->PrevInAEL; + } + if (IsHole) outrec->IsHole = true; +} +//------------------------------------------------------------------------------ + +OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) +{ + //work out which polygon fragment has the correct hole state ... + if (!outRec1->BottomPt) + outRec1->BottomPt = GetBottomPt(outRec1->Pts); + if (!outRec2->BottomPt) + outRec2->BottomPt = GetBottomPt(outRec2->Pts); + OutPt *OutPt1 = outRec1->BottomPt; + OutPt *OutPt2 = outRec2->BottomPt; + if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; + else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; + else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; + else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; + else if (OutPt1->Next == OutPt1) return outRec2; + else if (OutPt2->Next == OutPt2) return outRec1; + else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; + else return outRec2; +} +//------------------------------------------------------------------------------ + +bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) +{ + do + { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::GetOutRec(int Idx) +{ + OutRec* outrec = m_PolyOuts[Idx]; + while (outrec != m_PolyOuts[outrec->Idx]) + outrec = m_PolyOuts[outrec->Idx]; + return outrec; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) +{ + //get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; + + OutRec *holeStateRec; + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + //get the start and ends of both output polygons and + //join e2 poly onto e1 poly and delete pointers to e2 ... + + OutPt* p1_lft = outRec1->Pts; + OutPt* p1_rt = p1_lft->Prev; + OutPt* p2_lft = outRec2->Pts; + OutPt* p2_rt = p2_lft->Prev; + + EdgeSide Side; + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1->Side == esLeft ) + { + if( e2->Side == esLeft ) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + outRec1->Pts = p2_rt; + } else + { + //x y z a b c + p2_rt->Next = p1_lft; + p1_lft->Prev = p2_rt; + p2_lft->Prev = p1_rt; + p1_rt->Next = p2_lft; + outRec1->Pts = p2_lft; + } + Side = esLeft; + } else + { + if( e2->Side == esRight ) + { + //a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + } else + { + //a b c x y z + p1_rt->Next = p2_lft; + p2_lft->Prev = p1_rt; + p1_lft->Prev = p2_rt; + p2_rt->Next = p1_lft; + } + Side = esRight; + } + + outRec1->BottomPt = 0; + if (holeStateRec == outRec2) + { + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->IsHole = outRec2->IsHole; + } + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->OutIdx; + int ObsoleteIdx = e2->OutIdx; + + e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly + e2->OutIdx = Unassigned; + + TEdge* e = m_ActiveEdges; + while( e ) + { + if( e->OutIdx == ObsoleteIdx ) + { + e->OutIdx = OKIdx; + e->Side = Side; + break; + } + e = e->NextInAEL; + } + + outRec2->Idx = outRec1->Idx; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::CreateOutRec() +{ + OutRec* result = new OutRec; + result->IsHole = false; + result->IsOpen = false; + result->FirstLeft = 0; + result->Pts = 0; + result->BottomPt = 0; + result->PolyNd = 0; + m_PolyOuts.push_back(result); + result->Idx = (int)m_PolyOuts.size()-1; + return result; +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) +{ + bool ToFront = (e->Side == esLeft); + if( e->OutIdx < 0 ) + { + OutRec *outRec = CreateOutRec(); + outRec->IsOpen = (e->WindDelta == 0); + OutPt* newOp = new OutPt; + outRec->Pts = newOp; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = newOp; + newOp->Prev = newOp; + if (!outRec->IsOpen) + SetHoleState(e, outRec); +#ifdef use_xyz + if (pt == e->Bot) newOp->Pt = e->Bot; + else if (pt == e->Top) newOp->Pt = e->Top; + else SetZ(newOp->Pt, *e); +#endif + e->OutIdx = outRec->Idx; //nb: do this after SetZ ! + return newOp; + } else + { + OutRec *outRec = m_PolyOuts[e->OutIdx]; + //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt* op = outRec->Pts; + + if (ToFront && (pt == op->Pt)) return op; + else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; + + OutPt* newOp = new OutPt; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = op; + newOp->Prev = op->Prev; + newOp->Prev->Next = newOp; + op->Prev = newOp; + if (ToFront) outRec->Pts = newOp; +#ifdef use_xyz + if (pt == e->Bot) newOp->Pt = e->Bot; + else if (pt == e->Top) newOp->Pt = e->Top; + else SetZ(newOp->Pt, *e); +#endif + return newOp; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals(bool IsTopOfScanbeam) +{ + TEdge* horzEdge = m_SortedEdges; + while(horzEdge) + { + DeleteFromSEL(horzEdge); + ProcessHorizontal(horzEdge, IsTopOfScanbeam); + horzEdge = m_SortedEdges; + } +} +//------------------------------------------------------------------------------ + +inline bool IsMinima(TEdge *e) +{ + return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); +} +//------------------------------------------------------------------------------ + +inline bool IsMaxima(TEdge *e, const cInt Y) +{ + return e && e->Top.Y == Y && !e->NextInLML; +} +//------------------------------------------------------------------------------ + +inline bool IsIntermediate(TEdge *e, const cInt Y) +{ + return e->Top.Y == Y && e->NextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) +{ + TEdge* result = 0; + if ((e->Next->Top == e->Top) && !e->Next->NextInLML) + result = e->Next; + else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) + result = e->Prev; + + if (result && (result->OutIdx == Skip || + //result is false if both NextInAEL & PrevInAEL are nil & not horizontal ... + (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) + return 0; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) +{ + //check that one or other edge hasn't already been removed from AEL ... + if (Edge1->NextInAEL == Edge1->PrevInAEL || + Edge2->NextInAEL == Edge2->PrevInAEL) return; + + if( Edge1->NextInAEL == Edge2 ) + { + TEdge* Next = Edge2->NextInAEL; + if( Next ) Next->PrevInAEL = Edge1; + TEdge* Prev = Edge1->PrevInAEL; + if( Prev ) Prev->NextInAEL = Edge2; + Edge2->PrevInAEL = Prev; + Edge2->NextInAEL = Edge1; + Edge1->PrevInAEL = Edge2; + Edge1->NextInAEL = Next; + } + else if( Edge2->NextInAEL == Edge1 ) + { + TEdge* Next = Edge1->NextInAEL; + if( Next ) Next->PrevInAEL = Edge2; + TEdge* Prev = Edge2->PrevInAEL; + if( Prev ) Prev->NextInAEL = Edge1; + Edge1->PrevInAEL = Prev; + Edge1->NextInAEL = Edge2; + Edge2->PrevInAEL = Edge1; + Edge2->NextInAEL = Next; + } + else + { + TEdge* Next = Edge1->NextInAEL; + TEdge* Prev = Edge1->PrevInAEL; + Edge1->NextInAEL = Edge2->NextInAEL; + if( Edge1->NextInAEL ) Edge1->NextInAEL->PrevInAEL = Edge1; + Edge1->PrevInAEL = Edge2->PrevInAEL; + if( Edge1->PrevInAEL ) Edge1->PrevInAEL->NextInAEL = Edge1; + Edge2->NextInAEL = Next; + if( Edge2->NextInAEL ) Edge2->NextInAEL->PrevInAEL = Edge2; + Edge2->PrevInAEL = Prev; + if( Edge2->PrevInAEL ) Edge2->PrevInAEL->NextInAEL = Edge2; + } + + if( !Edge1->PrevInAEL ) m_ActiveEdges = Edge1; + else if( !Edge2->PrevInAEL ) m_ActiveEdges = Edge2; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) +{ + if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; + if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; + + if( Edge1->NextInSEL == Edge2 ) + { + TEdge* Next = Edge2->NextInSEL; + if( Next ) Next->PrevInSEL = Edge1; + TEdge* Prev = Edge1->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge2; + Edge2->PrevInSEL = Prev; + Edge2->NextInSEL = Edge1; + Edge1->PrevInSEL = Edge2; + Edge1->NextInSEL = Next; + } + else if( Edge2->NextInSEL == Edge1 ) + { + TEdge* Next = Edge1->NextInSEL; + if( Next ) Next->PrevInSEL = Edge2; + TEdge* Prev = Edge2->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge1; + Edge1->PrevInSEL = Prev; + Edge1->NextInSEL = Edge2; + Edge2->PrevInSEL = Edge1; + Edge2->NextInSEL = Next; + } + else + { + TEdge* Next = Edge1->NextInSEL; + TEdge* Prev = Edge1->PrevInSEL; + Edge1->NextInSEL = Edge2->NextInSEL; + if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; + Edge1->PrevInSEL = Edge2->PrevInSEL; + if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; + Edge2->NextInSEL = Next; + if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; + Edge2->PrevInSEL = Prev; + if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; + } + + if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; + else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; +} +//------------------------------------------------------------------------------ + +TEdge* GetNextInAEL(TEdge *e, Direction dir) +{ + return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; +} +//------------------------------------------------------------------------------ + +void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) +{ + if (HorzEdge.Bot.X < HorzEdge.Top.X) + { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = dLeftToRight; + } else + { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = dRightToLeft; + } +} +//------------------------------------------------------------------------ + +void Clipper::PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam) +{ + //get the last Op for this horizontal edge + //the point may be anywhere along the horizontal ... + OutPt* outPt = m_PolyOuts[horzEdge->OutIdx]->Pts; + if (horzEdge->Side != esLeft) outPt = outPt->Prev; + + //First, match up overlapping horizontal edges (eg when one polygon's + //intermediate horz edge overlaps an intermediate horz edge of another, or + //when one polygon sits on top of another) ... + //for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) + //{ + // Join* j = m_GhostJoins[i]; + // if (HorzSegmentsOverlap(j->OutPt1->Pt, j->OffPt, horzEdge->Bot, horzEdge->Top)) + // AddJoin(j->OutPt1, outPt, j->OffPt); + //} + + //Also, since horizontal edges at the top of one SB are often removed from + //the AEL before we process the horizontal edges at the bottom of the next, + //we need to create 'ghost' Join records of 'contrubuting' horizontals that + //we can compare with horizontals at the bottom of the next SB. + if (isTopOfScanbeam) + { + if (outPt->Pt == horzEdge->Top) + AddGhostJoin(outPt, horzEdge->Bot); + else + AddGhostJoin(outPt, horzEdge->Top); + } +} +//------------------------------------------------------------------------------ + +/******************************************************************************* +* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * +* Bottom of a scanbeam) are processed as if layered. The order in which HEs * +* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * +* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * +* and with other non-horizontal edges [*]. Once these intersections are * +* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * +* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * +*******************************************************************************/ + +void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) +{ + Direction dir; + cInt horzLeft, horzRight; + + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + TEdge* eLastHorz = horzEdge, *eMaxPair = 0; + while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) + eLastHorz = eLastHorz->NextInLML; + if (!eLastHorz->NextInLML) + eMaxPair = GetMaximaPair(eLastHorz); + + for (;;) + { + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge* e = GetNextInAEL(horzEdge, dir); + while(e) + { + //Break if we've got to the end of an intermediate horizontal edge ... + //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && + e->Dx < horzEdge->NextInLML->Dx) break; + + TEdge* eNext = GetNextInAEL(e, dir); //saves eNext for later + + if ((dir == dLeftToRight && e->Curr.X <= horzRight) || + (dir == dRightToLeft && e->Curr.X >= horzLeft)) + { + if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) + PrepareHorzJoins(horzEdge, isTopOfScanbeam); + //so far we're still in range of the horizontal Edge but make sure + //we're at the last of consec. horizontals when matching with eMaxPair + if(e == eMaxPair && IsLastHorz) + { + if (dir == dLeftToRight) + IntersectEdges(horzEdge, e, e->Top); + else + IntersectEdges(e, horzEdge, e->Top); + if (eMaxPair->OutIdx >= 0) throw clipperException("ProcessHorizontal error"); + return; + } + else if(dir == dLeftToRight) + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(horzEdge, e, Pt, true); + } + else + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges( e, horzEdge, Pt, true); + } + SwapPositionsInAEL( horzEdge, e ); + } + else if( (dir == dLeftToRight && e->Curr.X >= horzRight) || + (dir == dRightToLeft && e->Curr.X <= horzLeft) ) break; + e = eNext; + } //end while + + if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) + PrepareHorzJoins(horzEdge, isTopOfScanbeam); + + if (horzEdge->NextInLML && IsHorizontal(*horzEdge->NextInLML)) + { + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + } else + break; + } //end for (;;) + + if(horzEdge->NextInLML) + { + if(horzEdge->OutIdx >= 0) + { + OutPt* op1 = AddOutPt( horzEdge, horzEdge->Top); + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->WindDelta == 0) return; + //nb: HorzEdge is no longer horizontal here + TEdge* ePrev = horzEdge->PrevInAEL; + TEdge* eNext = horzEdge->NextInAEL; + if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && + ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && + (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) + { + OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + else if (eNext && eNext->Curr.X == horzEdge->Bot.X && + eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) + { + OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + } + else + UpdateEdgeIntoAEL(horzEdge); + } + else if (eMaxPair) + { + if (eMaxPair->OutIdx >= 0) + { + if (dir == dLeftToRight) + IntersectEdges(horzEdge, eMaxPair, horzEdge->Top); + else + IntersectEdges(eMaxPair, horzEdge, horzEdge->Top); + if (eMaxPair->OutIdx >= 0) + throw clipperException("ProcessHorizontal error"); + } else + { + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + } + } else + { + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +void Clipper::UpdateEdgeIntoAEL(TEdge *&e) +{ + if( !e->NextInLML ) throw + clipperException("UpdateEdgeIntoAEL: invalid call"); + + e->NextInLML->OutIdx = e->OutIdx; + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (AelPrev) AelPrev->NextInAEL = e->NextInLML; + else m_ActiveEdges = e->NextInLML; + if (AelNext) AelNext->PrevInAEL = e->NextInLML; + e->NextInLML->Side = e->Side; + e->NextInLML->WindDelta = e->WindDelta; + e->NextInLML->WindCnt = e->WindCnt; + e->NextInLML->WindCnt2 = e->WindCnt2; + e = e->NextInLML; + e->Curr = e->Bot; + e->PrevInAEL = AelPrev; + e->NextInAEL = AelNext; + if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const cInt botY, const cInt topY) +{ + if( !m_ActiveEdges ) return true; + try { + BuildIntersectList(botY, topY); + size_t IlSize = m_IntersectList.size(); + if (IlSize == 0) return true; + if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); + else return false; + } + catch(...) + { + m_SortedEdges = 0; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + m_SortedEdges = 0; + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() +{ + for (size_t i = 0; i < m_IntersectList.size(); ++i ) + delete m_IntersectList[i]; + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const cInt botY, const cInt topY) +{ + if ( !m_ActiveEdges ) return; + + //prepare for sorting ... + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e->Curr.X = TopX( *e, topY ); + e = e->NextInAEL; + } + + //bubblesort ... + bool isModified; + do + { + isModified = false; + e = m_SortedEdges; + while( e->NextInSEL ) + { + TEdge *eNext = e->NextInSEL; + IntPoint Pt; + if(e->Curr.X > eNext->Curr.X) + { + if (!IntersectPoint(*e, *eNext, Pt, m_UseFullRange) && e->Curr.X > eNext->Curr.X +1) + throw clipperException("Intersection error"); + if (Pt.Y > botY) + { + Pt.Y = botY; + if (std::fabs(e->Dx) > std::fabs(eNext->Dx)) + Pt.X = TopX(*eNext, botY); else + Pt.X = TopX(*e, botY); + } + + IntersectNode * newNode = new IntersectNode; + newNode->Edge1 = e; + newNode->Edge2 = eNext; + newNode->Pt = Pt; + m_IntersectList.push_back(newNode); + + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; + else break; + } + while ( isModified ); + m_SortedEdges = 0; //important +} +//------------------------------------------------------------------------------ + + +void Clipper::ProcessIntersectList() +{ + for (size_t i = 0; i < m_IntersectList.size(); ++i) + { + IntersectNode* iNode = m_IntersectList[i]; + { + IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt, true); + SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); + } + delete iNode; + } + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) +{ + return node2->Pt.Y < node1->Pt.Y; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) +{ + return (inode.Edge1->NextInSEL == inode.Edge2) || + (inode.Edge1->PrevInSEL == inode.Edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() +{ + //pre-condition: intersections are sorted Bottom-most first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + CopyAELToSEL(); + std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); + size_t cnt = m_IntersectList.size(); + for (size_t i = 0; i < cnt; ++i) + { + if (!EdgesAdjacent(*m_IntersectList[i])) + { + size_t j = i + 1; + while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; + if (j == cnt) return false; + std::swap(m_IntersectList[i], m_IntersectList[j]); + } + SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); + } + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e) +{ + TEdge* eMaxPair = GetMaximaPair(e); + if (!eMaxPair) + { + if (e->OutIdx >= 0) + AddOutPt(e, e->Top); + DeleteFromAEL(e); + return; + } + + TEdge* eNext = e->NextInAEL; + while(eNext && eNext != eMaxPair) + { + IntersectEdges(e, eNext, e->Top, true); + SwapPositionsInAEL(e, eNext); + eNext = e->NextInAEL; + } + + if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) + { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } + else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) + { + IntersectEdges( e, eMaxPair, e->Top); + } +#ifdef use_lines + else if (e->WindDelta == 0) + { + if (e->OutIdx >= 0) + { + AddOutPt(e, e->Top); + e->OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair->OutIdx >= 0) + { + AddOutPt(eMaxPair, e->Top); + eMaxPair->OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif + else throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) +{ + TEdge* e = m_ActiveEdges; + while( e ) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + bool IsMaximaEdge = IsMaxima(e, topY); + + if(IsMaximaEdge) + { + TEdge* eMaxPair = GetMaximaPair(e); + IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); + } + + if(IsMaximaEdge) + { + TEdge* ePrev = e->PrevInAEL; + DoMaxima(e); + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->NextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) + { + UpdateEdgeIntoAEL(e); + if (e->OutIdx >= 0) + AddOutPt(e, e->Bot); + AddEdgeToSEL(e); + } + else + { + e->Curr.X = TopX( *e, topY ); + e->Curr.Y = topY; + } + + if (m_StrictSimple) + { + TEdge* ePrev = e->PrevInAEL; + if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && + (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) + { + OutPt* op = AddOutPt(ePrev, e->Curr); + OutPt* op2 = AddOutPt(e, e->Curr); + AddJoin(op, op2, e->Curr); //StrictlySimple (type-3) join + } + } + + e = e->NextInAEL; + } + } + + //3. Process horizontals at the Top of the scanbeam ... + ProcessHorizontals(true); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while(e) + { + if(IsIntermediate(e, topY)) + { + OutPt* op = 0; + if( e->OutIdx >= 0 ) + op = AddOutPt(e, e->Top); + UpdateEdgeIntoAEL(e); + + //if output polygons share an edge, they'll need joining later ... + TEdge* ePrev = e->PrevInAEL; + TEdge* eNext = e->NextInAEL; + if (ePrev && ePrev->Curr.X == e->Bot.X && + ePrev->Curr.Y == e->Bot.Y && op && + ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*e, *ePrev, m_UseFullRange) && + (e->WindDelta != 0) && (ePrev->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(ePrev, e->Bot); + AddJoin(op, op2, e->Top); + } + else if (eNext && eNext->Curr.X == e->Bot.X && + eNext->Curr.Y == e->Bot.Y && op && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*e, *eNext, m_UseFullRange) && + (e->WindDelta != 0) && (eNext->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(eNext, e->Bot); + AddJoin(op, op2, e->Top); + } + } + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outrec) +{ + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outrec.BottomPt = 0; + OutPt *pp = outrec.Pts; + + for (;;) + { + if (pp->Prev == pp || pp->Prev == pp->Next ) + { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } + + //test for duplicate points and collinear edges ... + if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || + (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) + { + lastOK = 0; + OutPt *tmp = pp; + pp->Prev->Next = pp->Next; + pp->Next->Prev = pp->Prev; + pp = pp->Prev; + delete tmp; + } + else if (pp == lastOK) break; + else + { + if (!lastOK) lastOK = pp; + pp = pp->Next; + } + } + outrec.Pts = pp; +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *Pts) +{ + if (!Pts) return 0; + int result = 0; + OutPt* p = Pts; + do + { + result++; + p = p->Next; + } + while (p != Pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Paths &polys) +{ + polys.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + if (!m_PolyOuts[i]->Pts) continue; + Path pg; + OutPt* p = m_PolyOuts[i]->Pts->Prev; + int cnt = PointCount(p); + if (cnt < 2) continue; + pg.reserve(cnt); + for (int i = 0; i < cnt; ++i) + { + pg.push_back(p->Pt); + p = p->Prev; + } + polys.push_back(pg); + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree& polytree) +{ + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->Pts); + if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->PolyNd = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->Pts->Prev; + for (int j = 0; j < cnt; j++) + { + pn->Contour.push_back(op->Pt); + op = op->Prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->PolyNd) continue; + if (outRec->IsOpen) + { + outRec->PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec->PolyNd); + } + else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) + outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else + polytree.AddChild(*outRec->PolyNd); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) +{ + //just swap the contents (because fIntersectNodes is a single-linked-list) + IntersectNode inode = int1; //gets a copy of Int1 + int1.Edge1 = int2.Edge1; + int1.Edge2 = int2.Edge2; + int1.Pt = int2.Pt; + int2.Edge1 = inode.Edge1; + int2.Edge2 = inode.Edge2; + int2.Pt = inode.Pt; +} +//------------------------------------------------------------------------------ + +inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) +{ + if (e2.Curr.X == e1.Curr.X) + { + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else return e1.Top.X > TopX(e2, e1.Top.Y); + } + else return e2.Curr.X < e1.Curr.X; +} +//------------------------------------------------------------------------------ + +bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, + cInt& Left, cInt& Right) +{ + if (a1 < a2) + { + if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} + else {Left = std::max(a1,b2); Right = std::min(a2,b1);} + } + else + { + if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} + else {Left = std::max(a2,b2); Right = std::min(a1,b1);} + } + return Left < Right; +} +//------------------------------------------------------------------------------ + +inline void UpdateOutPtIdxs(OutRec& outrec) +{ + OutPt* op = outrec.Pts; + do + { + op->Idx = outrec.Idx; + op = op->Prev; + } + while(op != outrec.Pts); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) +{ + if(!m_ActiveEdges) + { + edge->PrevInAEL = 0; + edge->NextInAEL = 0; + m_ActiveEdges = edge; + } + else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) + { + edge->PrevInAEL = 0; + edge->NextInAEL = m_ActiveEdges; + m_ActiveEdges->PrevInAEL = edge; + m_ActiveEdges = edge; + } + else + { + if(!startEdge) startEdge = m_ActiveEdges; + while(startEdge->NextInAEL && + !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) + startEdge = startEdge->NextInAEL; + edge->NextInAEL = startEdge->NextInAEL; + if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; + edge->PrevInAEL = startEdge; + startEdge->NextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) +{ + OutPt* result = new OutPt; + result->Pt = outPt->Pt; + result->Idx = outPt->Idx; + if (InsertAfter) + { + result->Next = outPt->Next; + result->Prev = outPt; + outPt->Next->Prev = result; + outPt->Next = result; + } + else + { + result->Prev = outPt->Prev; + result->Next = outPt; + outPt->Prev->Next = result; + outPt->Prev = result; + } + return result; +} +//------------------------------------------------------------------------------ + +bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, + const IntPoint Pt, bool DiscardLeft) +{ + Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); + Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); + if (Dir1 == Dir2) return false; + + //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + //So, to facilitate this while inserting Op1b and Op2b ... + //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == dLeftToRight) + { + while (op1->Next->Pt.X <= Pt.X && + op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } + else + { + while (op1->Next->Pt.X >= Pt.X && + op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == dLeftToRight) + { + while (op2->Next->Pt.X <= Pt.X && + op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else + { + while (op2->Next->Pt.X >= Pt.X && + op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == dLeftToRight) == DiscardLeft) + { + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + } + else + { + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + } + return true; +} +//------------------------------------------------------------------------------ + +bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) +{ + OutPt *op1 = j->OutPt1, *op1b; + OutPt *op2 = j->OutPt2, *op2b; + + //There are 3 kinds of joins for output polygons ... + //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices anywhere + //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). + //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + //location at the Bottom of the overlapping segment (& Join.OffPt is above). + //3. StrictSimple joins where edges touch but are not collinear and where + //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); + + if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && + (j->OffPt == j->OutPt2->Pt)) + { + //Strictly Simple join ... + op1b = j->OutPt1->Next; + while (op1b != op1 && (op1b->Pt == j->OffPt)) + op1b = op1b->Next; + bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); + op2b = j->OutPt2->Next; + while (op2b != op2 && (op2b->Pt == j->OffPt)) + op2b = op2b->Next; + bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); + if (reverse1 == reverse2) return false; + if (reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } + else if (isHorizontal) + { + //treat horizontal joins differently to non-horizontal joins since with + //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + //may be anywhere along the horizontal edge. + op1b = op1; + while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) + op1 = op1->Prev; + while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) + op1b = op1b->Next; + if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' + + op2b = op2; + while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) + op2 = op2->Prev; + while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) + op2b = op2b->Next; + if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' + + cInt Left, Right; + //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) + return false; + + //DiscardLeftSide: when overlapping edges are joined, a spike will created + //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + //on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1->Pt.X >= Left && op1->Pt.X <= Right) + { + Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); + } + else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) + { + Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); + } + else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) + { + Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; + } + else + { + Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); + } + j->OutPt1 = op1; j->OutPt2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else + { + //nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + //make sure the polygons are correctly oriented ... + op1b = op1->Next; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; + bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse1) + { + op1b = op1->Prev; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; + if ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; + }; + op2b = op2->Next; + while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; + bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse2) + { + op2b = op2->Prev; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; + if ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; + + if (Reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) +{ + + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->Pts && outRec->FirstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) +{ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +static OutRec* ParseFirstLeft(OutRec* FirstLeft) +{ + while (FirstLeft && !FirstLeft->Pts) + FirstLeft = FirstLeft->FirstLeft; + return FirstLeft; +} +//------------------------------------------------------------------------------ + +void Clipper::JoinCommonEdges() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + { + Join* join = m_Joins[i]; + + OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); + + if (!outRec1->Pts || !outRec2->Pts) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + if (!JoinPoints(join, outRec1, outRec2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1->Pts = join->OutPt1; + outRec1->BottomPt = 0; + outRec2 = CreateOutRec(); + outRec2->Pts = join->OutPt2; + + //update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(*outRec2); + + //We now need to check every OutRec.FirstLeft pointer. If it points + //to OutRec1 it may need to point to OutRec2 instead ... + if (m_UsingPolyTree) + for (PolyOutList::size_type j = 0; j < m_PolyOuts.size() - 1; j++) + { + OutRec* oRec = m_PolyOuts[j]; + if (!oRec->Pts || ParseFirstLeft(oRec->FirstLeft) != outRec1 || + oRec->IsHole == outRec1->IsHole) continue; + if (Poly2ContainsPoly1(oRec->Pts, join->OutPt2)) + oRec->FirstLeft = outRec2; + } + + if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) + { + //outRec2 is contained by outRec1 ... + outRec2->IsHole = !outRec1->IsHole; + outRec2->FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) + ReversePolyPtLinks(outRec2->Pts); + + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) + { + //outRec1 is contained by outRec2 ... + outRec2->IsHole = outRec1->IsHole; + outRec1->IsHole = !outRec2->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) + ReversePolyPtLinks(outRec1->Pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2->IsHole = outRec1->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + } + + } else + { + //joined 2 polygons together ... + + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->Idx = outRec1->Idx; + + outRec1->IsHole = holeStateRec->IsHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + } + } +} + +//------------------------------------------------------------------------------ +// ClipperOffset support functions ... +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) +{ + if(pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double Dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); + Dx *= f; + dy *= f; + return DoublePoint(dy, -Dx); +} + +//------------------------------------------------------------------------------ +// ClipperOffset class +//------------------------------------------------------------------------------ + +ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) +{ + this->MiterLimit = miterLimit; + this->ArcTolerance = arcTolerance; + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +ClipperOffset::~ClipperOffset() +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Clear() +{ + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + delete m_polyNodes.Childs[i]; + m_polyNodes.Childs.clear(); + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) +{ + int highI = (int)path.size() - 1; + if (highI < 0) return; + PolyNode* newNode = new PolyNode(); + newNode->m_jointype = joinType; + newNode->m_endtype = endType; + + //strip duplicate points from path and also get index to the lowest point ... + if (endType == etClosedLine || endType == etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) highI--; + newNode->Contour.reserve(highI + 1); + newNode->Contour.push_back(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode->Contour[j] != path[i]) + { + j++; + newNode->Contour.push_back(path[i]); + if (path[i].Y > newNode->Contour[k].Y || + (path[i].Y == newNode->Contour[k].Y && + path[i].X < newNode->Contour[k].X)) k = j; + } + if ((endType == etClosedPolygon && j < 2) || + (endType != etClosedPolygon && j < 0)) + { + delete newNode; + return; + } + m_polyNodes.AddChild(*newNode); + + //if this path's lowest pt is lower than all the others then update m_lowest + if (endType != etClosedPolygon) return; + if (m_lowest.X < 0) + m_lowest = IntPoint(0, k); + else + { + IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; + if (newNode->Contour[k].Y > ip.Y || + (newNode->Contour[k].Y == ip.Y && + newNode->Contour[k].X < ip.X)) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) +{ + for (Paths::size_type i = 0; i < paths.size(); ++i) + AddPath(paths[i], joinType, endType); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::FixOrientations() +{ + //fixup orientations of all closed paths if the orientation of the + //closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon || + (node.m_endtype == etClosedLine && Orientation(node.Contour))) + ReversePath(node.Contour); + } + } else + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) + ReversePath(node.Contour); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(Paths& solution, double delta) +{ + solution.clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + if (solution.size() > 0) solution.erase(solution.begin()); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(PolyTree& solution, double delta) +{ + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + //remove the outer PolyNode rectangle ... + if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) + { + PolyNode* outerNode = solution.Childs[0]; + solution.Childs.reserve(outerNode->ChildCount()); + solution.Childs[0] = outerNode->Childs[0]; + for (int i = 1; i < outerNode->ChildCount(); ++i) + solution.AddChild(*outerNode->Childs[i]); + } + else + solution.Clear(); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoOffset(double delta) +{ + m_destPolys.clear(); + m_delta = delta; + + //if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (NEAR_ZERO(delta)) + { + m_destPolys.reserve(m_polyNodes.ChildCount()); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon) + m_destPolys.push_back(node.Contour); + } + return; + } + + //see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); + else m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) y = def_arc_tolerance; + else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) + y = std::fabs(delta) * def_arc_tolerance; + else y = ArcTolerance; + //see offset_triginometry2.svg in the documentation folder ... + double steps = pi / std::acos(1 - y / std::fabs(delta)); + if (steps > std::fabs(delta) * pi) + steps = std::fabs(delta) * pi; //ie excessive precision check + m_sin = std::sin(two_pi / steps); + m_cos = std::cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) m_sin = -m_sin; + + m_destPolys.reserve(m_polyNodes.ChildCount() * 2); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + m_srcPoly = node.Contour; + + int len = (int)m_srcPoly.size(); + if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) + continue; + + m_destPoly.clear(); + if (len == 1) + { + if (node.m_jointype == jtRound) + { + double X = 1.0, Y = 0.0; + for (cInt j = 1; j <= steps; j++) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } + else + { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) X = 1; + else if (Y < 0) Y = 1; + else X = -1; + } + } + m_destPolys.push_back(m_destPoly); + continue; + } + //build m_normals ... + m_normals.clear(); + m_normals.reserve(len); + for (int j = 0; j < len - 1; ++j) + m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) + m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.push_back(DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == etClosedPolygon) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else if (node.m_endtype == etClosedLine) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + m_destPoly.clear(); + //re-build m_normals ... + DoublePoint n = m_normals[len -1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else + { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == etOpenButt) + { + int j = len - 1; + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + //re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); + + k = len - 1; + for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); + + if (node.m_endtype == etOpenButt) + { + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + k = 1; + m_sinA = 0; + if (node.m_endtype == etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.push_back(m_destPoly); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) +{ + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + if (m_sinA < 0.00005 && m_sinA > -0.00005) return; + else if (m_sinA > 1.0) m_sinA = 1.0; + else if (m_sinA < -1.0) m_sinA = -1.0; + + if (m_sinA * m_delta < 0) + { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.push_back(m_srcPoly[j]); + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } + else + switch (jointype) + { + case jtMiter: + { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); + break; + } + case jtSquare: DoSquare(j, k); break; + case jtRound: DoRound(j, k); break; + } + k = j; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoSquare(int j, int k) +{ + double dx = std::tan(std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoMiter(int j, int k, double r) +{ + double q = m_delta / r; + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoRound(int j, int k) +{ + double a = std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); + int steps = (int)Round(m_StepsPerRad * std::fabs(a)); + + double X = m_normals[k].X, Y = m_normals[k].Y, X2; + for (int i = 0; i < steps; ++i) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); +} + +//------------------------------------------------------------------------------ +// Miscellaneous public functions +//------------------------------------------------------------------------------ + +void Clipper::DoSimplePolygons() +{ + PolyOutList::size_type i = 0; + while (i < m_PolyOuts.size()) + { + OutRec* outrec = m_PolyOuts[i++]; + OutPt* op = outrec->Pts; + if (!op) continue; + do //for each Pt in Polygon until duplicate found do ... + { + OutPt* op2 = op->Next; + while (op2 != outrec->Pts) + { + if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) + { + //split the polygon into two ... + OutPt* op3 = op->Prev; + OutPt* op4 = op2->Prev; + op->Prev = op4; + op4->Next = op; + op2->Prev = op3; + op3->Next = op2; + + outrec->Pts = op; + OutRec* outrec2 = CreateOutRec(); + outrec2->Pts = op2; + UpdateOutPtIdxs(*outrec2); + if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) + { + //OutRec2 is contained by OutRec1 ... + outrec2->IsHole = !outrec->IsHole; + outrec2->FirstLeft = outrec; + } + else + if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) + { + //OutRec1 is contained by OutRec2 ... + outrec2->IsHole = outrec->IsHole; + outrec->IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + outrec->FirstLeft = outrec2; + } else + { + //the 2 polygons are separate ... + outrec2->IsHole = outrec->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + } + op2 = op; //ie get ready for the Next iteration + } + op2 = op2->Next; + } + op = op->Next; + } + while (op != outrec->Pts); + } +} +//------------------------------------------------------------------------------ + +void ReversePath(Path& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePaths(Paths& p) +{ + for (Paths::size_type i = 0; i < p.size(); ++i) + ReversePath(p[i]); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) +{ + Clipper c; + c.StrictlySimple(true); + c.AddPath(in_poly, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) +{ + Clipper c; + c.StrictlySimple(true); + c.AddPaths(in_polys, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Paths &polys, PolyFillType fillType) +{ + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) +{ + double Dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (Dx*Dx + dy*dy); +} +//------------------------------------------------------------------------------ + +double DistanceFromLineSqrd( + const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) +{ + //see https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line + double A = double(ln1.Y - ln2.Y); + double B = double(ln2.X - ln1.X); + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); +} +//--------------------------------------------------------------------------- + +bool SlopesNearCollinear(const IntPoint& pt1, + const IntPoint& pt2, const IntPoint& pt3, double distSqrd) +{ + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; +} +//------------------------------------------------------------------------------ + +bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) +{ + double Dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((Dx * Dx) + (dy * dy) <= distSqrd); +} +//------------------------------------------------------------------------------ + +OutPt* ExcludeOp(OutPt* op) +{ + OutPt* result = op->Prev; + result->Next = op->Next; + op->Next->Prev = result; + result->Idx = 0; + return result; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) +{ + //distance = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2). + + size_t size = in_poly.size(); + + if (size == 0) + { + out_poly.clear(); + return; + } + + OutPt* outPts = new OutPt[size]; + for (size_t i = 0; i < size; ++i) + { + outPts[i].Pt = in_poly[i]; + outPts[i].Next = &outPts[(i + 1) % size]; + outPts[i].Next->Prev = &outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt* op = &outPts[0]; + while (op->Idx == 0 && op->Next != op->Prev) + { + if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) + { + ExcludeOp(op->Next); + op = ExcludeOp(op); + size -= 2; + } + else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else + { + op->Idx = 1; + op = op->Next; + } + } + + if (size < 3) size = 0; + out_poly.resize(size); + for (size_t i = 0; i < size; ++i) + { + out_poly[i] = op->Pt; + op = op->Next; + } + delete [] outPts; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Path& poly, double distance) +{ + CleanPolygon(poly, poly, distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) +{ + for (Paths::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(Paths& polys, double distance) +{ + CleanPolygons(polys, polys, distance); +} +//------------------------------------------------------------------------------ + +void Minkowski(const Path& poly, const Path& path, + Paths& solution, bool isSum, bool isClosed) +{ + int delta = (isClosed ? 1 : 0); + size_t polyCnt = poly.size(); + size_t pathCnt = path.size(); + Paths pp; + pp.reserve(pathCnt); + if (isSum) + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); + pp.push_back(p); + } + else + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); + pp.push_back(p); + } + + Paths quads; + quads.reserve((pathCnt + delta) * (polyCnt + 1)); + for (size_t i = 0; i < pathCnt - 1 + delta; ++i) + for (size_t j = 0; j < polyCnt; ++j) + { + Path quad; + quad.reserve(4); + quad.push_back(pp[i % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) ReversePath(quad); + quads.push_back(quad); + } + + Clipper c; + c.AddPaths(quads, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) +{ + Minkowski(pattern, path, solution, true, pathIsClosed); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, + PolyFillType pathFillType, bool pathIsClosed) +{ + Clipper c; + for (size_t i = 0; i < paths.size(); ++i) + { + Paths tmp; + Minkowski(pattern, paths[i], tmp, true, pathIsClosed); + c.AddPaths(tmp, ptSubject, true); + } + if (pathIsClosed) c.AddPaths(paths, ptClip, true); + c.Execute(ctUnion, solution, pathFillType, pathFillType); +} +//------------------------------------------------------------------------------ + +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) +{ + Minkowski(poly1, poly2, solution, false, true); +} +//------------------------------------------------------------------------------ + +enum NodeType {ntAny, ntOpen, ntClosed}; + +void AddPolyNodeToPolygons(const PolyNode& polynode, NodeType nodetype, Paths& paths) +{ + bool match = true; + if (nodetype == ntClosed) match = !polynode.IsOpen(); + else if (nodetype == ntOpen) return; + + if (!polynode.Contour.empty() && match) + paths.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPolygons(*polynode.Childs[i], nodetype, paths); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, ntAny, paths); +} +//------------------------------------------------------------------------------ + +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, ntClosed, paths); +} +//------------------------------------------------------------------------------ + +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + //Open paths are top level only, so ... + for (int i = 0; i < polytree.ChildCount(); ++i) + if (polytree.Childs[i]->IsOpen()) + paths.push_back(polytree.Childs[i]->Contour); +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const IntPoint &p) +{ + s << "(" << p.X << "," << p.Y << ")"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Path &p) +{ + if (p.empty()) return s; + Path::size_type last = p.size() -1; + for (Path::size_type i = 0; i < last; i++) + s << "(" << p[i].X << "," << p[i].Y << "), "; + s << "(" << p[last].X << "," << p[last].Y << ")\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Paths &p) +{ + for (Paths::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +#ifdef use_deprecated + +void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType_ endtype, double limit) +{ + ClipperOffset co(limit, limit); + co.AddPaths(in_polys, jointype, (EndType)endtype); + co.Execute(out_polys, delta); +} +//------------------------------------------------------------------------------ + +#endif + + +} //ClipperLib namespace diff --git a/src/3rdparty/clipper/clipper.h b/src/3rdparty/clipper/clipper.h new file mode 100644 index 0000000..8487014 --- /dev/null +++ b/src/3rdparty/clipper/clipper.h @@ -0,0 +1,398 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.1.3a * +* Date : 22 January 2014 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2014 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#define CLIPPER_VERSION "6.1.3" + +//use_int32: When enabled 32bit ints are used instead of 64bit ints. This +//improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. +//#define use_xyz + +//use_lines: Enables line clipping. Adds a very minor cost to performance. +//#define use_lines + +//use_deprecated: Enables support for the obsolete OffsetPaths() function +//which has been replace with the ClipperOffset class. +#define use_deprecated + +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +//By far the most widely used winding rules for polygon filling are +//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +//see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +#ifdef use_int32 +typedef int cInt; +typedef unsigned int cUInt; +#else +typedef signed long long cInt; +typedef unsigned long long cUInt; +#endif + +struct IntPoint { + cInt X; + cInt Y; +#ifdef use_xyz + cInt Z; + IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; +#else + IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; +#endif + + friend inline bool operator== (const IntPoint& a, const IntPoint& b) + { + return a.X == b.X && a.Y == b.Y; + } + friend inline bool operator!= (const IntPoint& a, const IntPoint& b) + { + return a.X != b.X || a.Y != b.Y; + } +}; +//------------------------------------------------------------------------------ + +typedef std::vector< IntPoint > Path; +typedef std::vector< Path > Paths; + +inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} +inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} + +std::ostream& operator <<(std::ostream &s, const IntPoint &p); +std::ostream& operator <<(std::ostream &s, const Path &p); +std::ostream& operator <<(std::ostream &s, const Paths &p); + +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} +}; +//------------------------------------------------------------------------------ + +#ifdef use_xyz +typedef void (*TZFillCallback)(IntPoint& z1, IntPoint& z2, IntPoint& pt); +#endif + +enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; +enum JoinType {jtSquare, jtRound, jtMiter}; +enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; +#ifdef use_deprecated + enum EndType_ {etClosed, etButt = 2, etSquare, etRound}; +#endif + +class PolyNode; +typedef std::vector< PolyNode* > PolyNodes; + +class PolyNode +{ +public: + PolyNode(); + Path Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext() const; + bool IsHole() const; + bool IsOpen() const; + int ChildCount() const; +private: + unsigned Index; //node index in Parent.Childs + bool m_IsOpen; + JoinType m_jointype; + EndType m_endtype; + PolyNode* GetNextSiblingUp() const; + void AddChild(PolyNode& child); + friend class Clipper; //to access Index + friend class ClipperOffset; +}; + +class PolyTree: public PolyNode +{ +public: + ~PolyTree(){Clear();}; + PolyNode* GetFirst() const; + void Clear(); + int Total() const; +private: + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes +}; + +bool Orientation(const Path &poly); +double Area(const Path &poly); +int PointInPolygon(const IntPoint &pt, const Path &path); + +#ifdef use_deprecated + void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType_ endtype, double limit = 0); +#endif + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); +void CleanPolygon(Path& poly, double distance = 1.415); +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); +void CleanPolygons(Paths& polys, double distance = 1.415); + +void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); +void MinkowskiSum(const Path& pattern, const Paths& paths, + Paths& solution, PolyFillType pathFillType, bool pathIsClosed); +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); + +void ReversePath(Path& p); +void ReversePaths(Paths& p); + +struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; + +//enums that are used internally ... +enum EdgeSide { esLeft = 1, esRight = 2}; + +//forward declarations (for stuff used internally) ... +struct TEdge; +struct IntersectNode; +struct LocalMinima; +struct Scanbeam; +struct OutPt; +struct OutRec; +struct Join; + +typedef std::vector < OutRec* > PolyOutList; +typedef std::vector < TEdge* > EdgeList; +typedef std::vector < Join* > JoinList; +typedef std::vector < IntersectNode* > IntersectList; + + +//------------------------------------------------------------------------------ + +//ClipperBase is the ancestor to the Clipper class. It should not be +//instantiated directly. This class simply abstracts the conversion of sets of +//polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase +{ +public: + ClipperBase(); + virtual ~ClipperBase(); + bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); + virtual void Clear(); + IntRect GetBounds(); + bool PreserveCollinear() {return m_PreserveCollinear;}; + void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; +protected: + void DisposeLocalMinimaList(); + TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); + void PopLocalMinima(); + virtual void Reset(); + TEdge* ProcessBound(TEdge* E, bool IsClockwise); + void InsertLocalMinima(LocalMinima *newLm); + void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed); + TEdge* DescendToMin(TEdge *&E); + void AscendToMax(TEdge *&E, bool Appending, bool IsClosed); + LocalMinima *m_CurrentLM; + LocalMinima *m_MinimaList; + bool m_UseFullRange; + EdgeList m_edges; + bool m_PreserveCollinear; + bool m_HasOpenPaths; +}; +//------------------------------------------------------------------------------ + +class Clipper : public virtual ClipperBase +{ +public: + Clipper(int initOptions = 0); + ~Clipper(); + bool Execute(ClipType clipType, + Paths &solution, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool ReverseSolution() {return m_ReverseOutput;}; + void ReverseSolution(bool value) {m_ReverseOutput = value;}; + bool StrictlySimple() {return m_StrictSimple;}; + void StrictlySimple(bool value) {m_StrictSimple = value;}; + //set the callback function for z value filling on intersections (otherwise Z is 0) +#ifdef use_xyz + void ZFillFunction(TZFillCallback zFillFunc); +#endif +protected: + void Reset(); + virtual bool ExecuteInternal(); +private: + PolyOutList m_PolyOuts; + JoinList m_Joins; + JoinList m_GhostJoins; + IntersectList m_IntersectList; + ClipType m_ClipType; + std::set< cInt, std::greater > m_Scanbeam; + TEdge *m_ActiveEdges; + TEdge *m_SortedEdges; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_StrictSimple; +#ifdef use_xyz + TZFillCallback m_ZFill; //custom callback +#endif + void SetWindingCount(TEdge& edge); + bool IsEvenOddFillType(const TEdge& edge) const; + bool IsEvenOddAltFillType(const TEdge& edge) const; + void InsertScanbeam(const cInt Y); + cInt PopScanbeam(); + void InsertLocalMinimaIntoAEL(const cInt botY); + void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); + void AddEdgeToSEL(TEdge *edge); + void CopyAELToSEL(); + void DeleteFromSEL(TEdge *e); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + bool IsContributing(const TEdge& edge) const; + bool IsTopHorz(const cInt XPos); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DoMaxima(TEdge *e); + void PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam); + void ProcessHorizontals(bool IsTopOfScanbeam); + void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam); + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutRec* GetOutRec(int idx); + void AppendPolygon(TEdge *e1, TEdge *e2); + void IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &pt, bool protect = false); + OutRec* CreateOutRec(); + OutPt* AddOutPt(TEdge *e, const IntPoint &pt); + void DisposeAllOutRecs(); + void DisposeOutRec(PolyOutList::size_type index); + bool ProcessIntersections(const cInt botY, const cInt topY); + void BuildIntersectList(const cInt botY, const cInt topY); + void ProcessIntersectList(); + void ProcessEdgesAtTopOfScanbeam(const cInt topY); + void BuildResult(Paths& polys); + void BuildResult2(PolyTree& polytree); + void SetHoleState(TEdge *e, OutRec *outrec); + void DisposeIntersectNodes(); + bool FixupIntersectionOrder(); + void FixupOutPolygon(OutRec &outrec); + bool IsHole(TEdge *e); + bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); + void FixHoleLinkage(OutRec &outrec); + void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); + void ClearJoins(); + void ClearGhostJoins(); + void AddGhostJoin(OutPt *op, const IntPoint offPt); + bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); + void JoinCommonEdges(); + void DoSimplePolygons(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); +#ifdef use_xyz + void SetZ(IntPoint& pt, TEdge& e); +#endif +}; +//------------------------------------------------------------------------------ + +class ClipperOffset +{ +public: + ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); + ~ClipperOffset(); + void AddPath(const Path& path, JoinType joinType, EndType endType); + void AddPaths(const Paths& paths, JoinType joinType, EndType endType); + void Execute(Paths& solution, double delta); + void Execute(PolyTree& solution, double delta); + void Clear(); + double MiterLimit; + double ArcTolerance; +private: + Paths m_destPolys; + Path m_srcPoly; + Path m_destPoly; + std::vector m_normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_StepsPerRad; + IntPoint m_lowest; + PolyNode m_polyNodes; + + void FixOrientations(); + void DoOffset(double delta); + void OffsetPoint(int j, int& k, JoinType jointype); + void DoSquare(int j, int k); + void DoMiter(int j, int k, double r); + void DoRound(int j, int k); +}; +//------------------------------------------------------------------------------ + +class clipperException : public std::exception +{ + public: + clipperException(const char* description): m_descr(description) {} + virtual ~clipperException() throw() {} + virtual const char* what() const throw() {return m_descr.c_str();} + private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} //ClipperLib namespace + +#endif //clipper_hpp + + diff --git a/src/3rdparty/clipper/clipper.pro b/src/3rdparty/clipper/clipper.pro new file mode 100644 index 0000000..233874e --- /dev/null +++ b/src/3rdparty/clipper/clipper.pro @@ -0,0 +1,17 @@ +TARGET = clipper + +CONFIG += staticlib +CONFIG += exceptions + + +load(qt_helper_lib) + +# workaround for QTBUG-31586 +contains(QT_CONFIG, c++11): CONFIG += c++11 + +*-g++* { + QMAKE_CXXFLAGS += -O3 -ftree-vectorize -ffast-math -funsafe-math-optimizations -Wno-error=return-type +} + +HEADERS += clipper.h +SOURCES += clipper.cpp diff --git a/src/3rdparty/clipper_legal.qdoc b/src/3rdparty/clipper_legal.qdoc new file mode 100644 index 0000000..59b7f57 --- /dev/null +++ b/src/3rdparty/clipper_legal.qdoc @@ -0,0 +1,34 @@ +/*! +\page legal-clipper.html +\title Clipper Polygon Clipping Library +\ingroup licensing + +\legalese +\code +Clipper Copyright Angus Johnson 2010-2014 +http://www.angusj.com + +Use, modification & distribution is subject to Boost Software License Ver 1. +http://www.boost.org/LICENSE_1_0.txt + +Attributions: +The code in this library is an extension of Bala Vatti's clipping algorithm: +"A generic solution to polygon clipping" +Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. +http://portal.acm.org/citation.cfm?id=129906 + +Computer graphics and geometric modeling: implementation and algorithms +By Max K. Agoston +Springer; 1 edition (January 4, 2005) +http://books.google.com/books?q=vatti+clipping+agoston + +See also: +"Polygon Offsetting by Computing Winding Numbers" +Paper no. DETC2005-85513 pp. 565-575 +ASME 2005 International Design Engineering Technical Conferences +and Computers and Information in Engineering Conference (IDETC/CIE2005) +September 24-28, 2005 , Long Beach, California, USA +http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf +\endcode +\endlegalese +*/ diff --git a/src/3rdparty/poly2tri/AUTHORS b/src/3rdparty/poly2tri/AUTHORS new file mode 100644 index 0000000..421601a --- /dev/null +++ b/src/3rdparty/poly2tri/AUTHORS @@ -0,0 +1,8 @@ +Primary Contributors: + + Mason Green (C++, Python) + Thomas Åhlén (Java) + +Other Contributors: + + diff --git a/src/3rdparty/poly2tri/LICENSE b/src/3rdparty/poly2tri/LICENSE new file mode 100644 index 0000000..9417c08 --- /dev/null +++ b/src/3rdparty/poly2tri/LICENSE @@ -0,0 +1,27 @@ +Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors +http://code.google.com/p/poly2tri/ + +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Poly2Tri nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/3rdparty/poly2tri/common/shapes.cpp b/src/3rdparty/poly2tri/common/shapes.cpp new file mode 100644 index 0000000..2ac7e97 --- /dev/null +++ b/src/3rdparty/poly2tri/common/shapes.cpp @@ -0,0 +1,363 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "shapes.h" +#include + +namespace p2t { + +Triangle::Triangle(Point& a, Point& b, Point& c) +{ + points_[0] = &a; points_[1] = &b; points_[2] = &c; + neighbors_[0] = NULL; neighbors_[1] = NULL; neighbors_[2] = NULL; + constrained_edge[0] = constrained_edge[1] = constrained_edge[2] = false; + delaunay_edge[0] = delaunay_edge[1] = delaunay_edge[2] = false; + interior_ = false; +} + +// Update neighbor pointers +void Triangle::MarkNeighbor(Point* p1, Point* p2, Triangle* t) +{ + if ((p1 == points_[2] && p2 == points_[1]) || (p1 == points_[1] && p2 == points_[2])) + neighbors_[0] = t; + else if ((p1 == points_[0] && p2 == points_[2]) || (p1 == points_[2] && p2 == points_[0])) + neighbors_[1] = t; + else if ((p1 == points_[0] && p2 == points_[1]) || (p1 == points_[1] && p2 == points_[0])) + neighbors_[2] = t; + else + assert(0); +} + +// Exhaustive search to update neighbor pointers +void Triangle::MarkNeighbor(Triangle& t) +{ + if (t.Contains(points_[1], points_[2])) { + neighbors_[0] = &t; + t.MarkNeighbor(points_[1], points_[2], this); + } else if (t.Contains(points_[0], points_[2])) { + neighbors_[1] = &t; + t.MarkNeighbor(points_[0], points_[2], this); + } else if (t.Contains(points_[0], points_[1])) { + neighbors_[2] = &t; + t.MarkNeighbor(points_[0], points_[1], this); + } +} + +/** + * Clears all references to all other triangles and points + */ +void Triangle::Clear() +{ + Triangle *t; + for (int i=0; i<3; i++) + { + t = neighbors_[i]; + if (t != NULL) + { + t->ClearNeighbor( this ); + } + } + ClearNeighbors(); + points_[0]=points_[1]=points_[2] = NULL; +} + +void Triangle::ClearNeighbor(Triangle *triangle ) +{ + if (neighbors_[0] == triangle) + { + neighbors_[0] = NULL; + } + else if (neighbors_[1] == triangle) + { + neighbors_[1] = NULL; + } + else + { + neighbors_[2] = NULL; + } +} + +void Triangle::ClearNeighbors() +{ + neighbors_[0] = NULL; + neighbors_[1] = NULL; + neighbors_[2] = NULL; +} + +void Triangle::ClearDelunayEdges() +{ + delaunay_edge[0] = delaunay_edge[1] = delaunay_edge[2] = false; +} + +Point* Triangle::OppositePoint(Triangle& t, Point& p) +{ + Point *cw = t.PointCW(p); + return PointCW(*cw); +} + +// Legalized triangle by rotating clockwise around point(0) +void Triangle::Legalize(Point& point) +{ + points_[1] = points_[0]; + points_[0] = points_[2]; + points_[2] = &point; +} + +// Legalize triagnle by rotating clockwise around oPoint +void Triangle::Legalize(Point& opoint, Point& npoint) +{ + if (&opoint == points_[0]) { + points_[1] = points_[0]; + points_[0] = points_[2]; + points_[2] = &npoint; + } else if (&opoint == points_[1]) { + points_[2] = points_[1]; + points_[1] = points_[0]; + points_[0] = &npoint; + } else if (&opoint == points_[2]) { + points_[0] = points_[2]; + points_[2] = points_[1]; + points_[1] = &npoint; + } else { + assert(0); + } +} + +int Triangle::Index(const Point* p) +{ + if (p == points_[0]) { + return 0; + } else if (p == points_[1]) { + return 1; + } else if (p == points_[2]) { + return 2; + } + assert(0); +} + +int Triangle::EdgeIndex(const Point* p1, const Point* p2) +{ + if (points_[0] == p1) { + if (points_[1] == p2) { + return 2; + } else if (points_[2] == p2) { + return 1; + } + } else if (points_[1] == p1) { + if (points_[2] == p2) { + return 0; + } else if (points_[0] == p2) { + return 2; + } + } else if (points_[2] == p1) { + if (points_[0] == p2) { + return 1; + } else if (points_[1] == p2) { + return 0; + } + } + return -1; +} + +void Triangle::MarkConstrainedEdge(const int index) +{ + constrained_edge[index] = true; +} + +void Triangle::MarkConstrainedEdge(Edge& edge) +{ + MarkConstrainedEdge(edge.p, edge.q); +} + +// Mark edge as constrained +void Triangle::MarkConstrainedEdge(Point* p, Point* q) +{ + if ((q == points_[0] && p == points_[1]) || (q == points_[1] && p == points_[0])) { + constrained_edge[2] = true; + } else if ((q == points_[0] && p == points_[2]) || (q == points_[2] && p == points_[0])) { + constrained_edge[1] = true; + } else if ((q == points_[1] && p == points_[2]) || (q == points_[2] && p == points_[1])) { + constrained_edge[0] = true; + } +} + +// The point counter-clockwise to given point +Point* Triangle::PointCW(Point& point) +{ + if (&point == points_[0]) { + return points_[2]; + } else if (&point == points_[1]) { + return points_[0]; + } else if (&point == points_[2]) { + return points_[1]; + } + assert(0); +} + +// The point counter-clockwise to given point +Point* Triangle::PointCCW(Point& point) +{ + if (&point == points_[0]) { + return points_[1]; + } else if (&point == points_[1]) { + return points_[2]; + } else if (&point == points_[2]) { + return points_[0]; + } + assert(0); +} + +// The neighbor clockwise to given point +Triangle* Triangle::NeighborCW(Point& point) +{ + if (&point == points_[0]) { + return neighbors_[1]; + } else if (&point == points_[1]) { + return neighbors_[2]; + } + return neighbors_[0]; +} + +// The neighbor counter-clockwise to given point +Triangle* Triangle::NeighborCCW(Point& point) +{ + if (&point == points_[0]) { + return neighbors_[2]; + } else if (&point == points_[1]) { + return neighbors_[0]; + } + return neighbors_[1]; +} + +bool Triangle::GetConstrainedEdgeCCW(Point& p) +{ + if (&p == points_[0]) { + return constrained_edge[2]; + } else if (&p == points_[1]) { + return constrained_edge[0]; + } + return constrained_edge[1]; +} + +bool Triangle::GetConstrainedEdgeCW(Point& p) +{ + if (&p == points_[0]) { + return constrained_edge[1]; + } else if (&p == points_[1]) { + return constrained_edge[2]; + } + return constrained_edge[0]; +} + +void Triangle::SetConstrainedEdgeCCW(Point& p, bool ce) +{ + if (&p == points_[0]) { + constrained_edge[2] = ce; + } else if (&p == points_[1]) { + constrained_edge[0] = ce; + } else { + constrained_edge[1] = ce; + } +} + +void Triangle::SetConstrainedEdgeCW(Point& p, bool ce) +{ + if (&p == points_[0]) { + constrained_edge[1] = ce; + } else if (&p == points_[1]) { + constrained_edge[2] = ce; + } else { + constrained_edge[0] = ce; + } +} + +bool Triangle::GetDelunayEdgeCCW(Point& p) +{ + if (&p == points_[0]) { + return delaunay_edge[2]; + } else if (&p == points_[1]) { + return delaunay_edge[0]; + } + return delaunay_edge[1]; +} + +bool Triangle::GetDelunayEdgeCW(Point& p) +{ + if (&p == points_[0]) { + return delaunay_edge[1]; + } else if (&p == points_[1]) { + return delaunay_edge[2]; + } + return delaunay_edge[0]; +} + +void Triangle::SetDelunayEdgeCCW(Point& p, bool e) +{ + if (&p == points_[0]) { + delaunay_edge[2] = e; + } else if (&p == points_[1]) { + delaunay_edge[0] = e; + } else { + delaunay_edge[1] = e; + } +} + +void Triangle::SetDelunayEdgeCW(Point& p, bool e) +{ + if (&p == points_[0]) { + delaunay_edge[1] = e; + } else if (&p == points_[1]) { + delaunay_edge[2] = e; + } else { + delaunay_edge[0] = e; + } +} + +// The neighbor across to given point +Triangle& Triangle::NeighborAcross(Point& opoint) +{ + if (&opoint == points_[0]) { + return *neighbors_[0]; + } else if (&opoint == points_[1]) { + return *neighbors_[1]; + } + return *neighbors_[2]; +} + +void Triangle::DebugPrint() +{ + using namespace std; + cout << points_[0]->x << "," << points_[0]->y << " "; + cout << points_[1]->x << "," << points_[1]->y << " "; + cout << points_[2]->x << "," << points_[2]->y << endl; +} + +} + diff --git a/src/3rdparty/poly2tri/common/shapes.h b/src/3rdparty/poly2tri/common/shapes.h new file mode 100644 index 0000000..5b90ea6 --- /dev/null +++ b/src/3rdparty/poly2tri/common/shapes.h @@ -0,0 +1,325 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Include guard +#ifndef SHAPES_H +#define SHAPES_H + +#include +#include +#include +#include + +namespace p2t { + +struct Edge; + +struct Point { + + double x, y; + + /// Default constructor does nothing (for performance). + Point() + { + x = 0.0; + y = 0.0; + } + + /// The edges this point constitutes an upper ending point + std::vector edge_list; + + /// Construct using coordinates. + Point(double x, double y) : x(x), y(y) {} + + /// Set this point to all zeros. + void set_zero() + { + x = 0.0; + y = 0.0; + } + + /// Set this point to some specified coordinates. + void set(double x_, double y_) + { + x = x_; + y = y_; + } + + /// Negate this point. + Point operator -() const + { + Point v; + v.set(-x, -y); + return v; + } + + /// Add a point to this point. + void operator +=(const Point& v) + { + x += v.x; + y += v.y; + } + + /// Subtract a point from this point. + void operator -=(const Point& v) + { + x -= v.x; + y -= v.y; + } + + /// Multiply this point by a scalar. + void operator *=(double a) + { + x *= a; + y *= a; + } + + /// Get the length of this point (the norm). + double Length() const + { + return std::sqrt(x * x + y * y); + } + + /// Convert this point into a unit point. Returns the Length. + double Normalize() + { + double len = Length(); + x /= len; + y /= len; + return len; + } + +}; + +// Represents a simple polygon's edge +struct Edge { + + Point* p, *q; + + /// Constructor + Edge(Point& p1, Point& p2) : p(&p1), q(&p2) + { + if (p1.y > p2.y) { + q = &p1; + p = &p2; + } else if (p1.y == p2.y) { + if (p1.x > p2.x) { + q = &p1; + p = &p2; + } else if (p1.x == p2.x) { + // Repeat points + assert(false); + } + } + + q->edge_list.push_back(this); + } +}; + +// Triangle-based data structures are know to have better performance than quad-edge structures +// See: J. Shewchuk, "Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator" +// "Triangulations in CGAL" +class Triangle { +public: + +/// Constructor +Triangle(Point& a, Point& b, Point& c); + +/// Flags to determine if an edge is a Constrained edge +bool constrained_edge[3]; +/// Flags to determine if an edge is a Delauney edge +bool delaunay_edge[3]; + +Point* GetPoint(const int& index); +Point* PointCW(Point& point); +Point* PointCCW(Point& point); +Point* OppositePoint(Triangle& t, Point& p); + +Triangle* GetNeighbor(const int& index); +void MarkNeighbor(Point* p1, Point* p2, Triangle* t); +void MarkNeighbor(Triangle& t); + +void MarkConstrainedEdge(const int index); +void MarkConstrainedEdge(Edge& edge); +void MarkConstrainedEdge(Point* p, Point* q); + +int Index(const Point* p); +int EdgeIndex(const Point* p1, const Point* p2); + +Triangle* NeighborCW(Point& point); +Triangle* NeighborCCW(Point& point); +bool GetConstrainedEdgeCCW(Point& p); +bool GetConstrainedEdgeCW(Point& p); +void SetConstrainedEdgeCCW(Point& p, bool ce); +void SetConstrainedEdgeCW(Point& p, bool ce); +bool GetDelunayEdgeCCW(Point& p); +bool GetDelunayEdgeCW(Point& p); +void SetDelunayEdgeCCW(Point& p, bool e); +void SetDelunayEdgeCW(Point& p, bool e); + +bool Contains(Point* p); +bool Contains(const Edge& e); +bool Contains(Point* p, Point* q); +void Legalize(Point& point); +void Legalize(Point& opoint, Point& npoint); +/** + * Clears all references to all other triangles and points + */ +void Clear(); +void ClearNeighbor(Triangle *triangle ); +void ClearNeighbors(); +void ClearDelunayEdges(); + +inline bool IsInterior(); +inline void IsInterior(bool b); + +Triangle& NeighborAcross(Point& opoint); + +void DebugPrint(); + +private: + +/// Triangle points +Point* points_[3]; +/// Neighbor list +Triangle* neighbors_[3]; + +/// Has this triangle been marked as an interior triangle? +bool interior_; +}; + +inline bool cmp(const Point* a, const Point* b) +{ + if (a->y < b->y) { + return true; + } else if (a->y == b->y) { + // Make sure q is point with greater x value + if (a->x < b->x) { + return true; + } + } + return false; +} + +/// Add two points_ component-wise. +inline Point operator +(const Point& a, const Point& b) +{ + return Point(a.x + b.x, a.y + b.y); +} + +/// Subtract two points_ component-wise. +inline Point operator -(const Point& a, const Point& b) +{ + return Point(a.x - b.x, a.y - b.y); +} + +/// Multiply point by scalar +inline Point operator *(double s, const Point& a) +{ + return Point(s * a.x, s * a.y); +} + +inline bool operator ==(const Point& a, const Point& b) +{ + return a.x == b.x && a.y == b.y; +} + +inline bool operator !=(const Point& a, const Point& b) +{ + return !(a.x == b.x) && !(a.y == b.y); +} + +/// Peform the dot product on two vectors. +inline double Dot(const Point& a, const Point& b) +{ + return a.x * b.x + a.y * b.y; +} + +/// Perform the cross product on two vectors. In 2D this produces a scalar. +inline double Cross(const Point& a, const Point& b) +{ + return a.x * b.y - a.y * b.x; +} + +/// Perform the cross product on a point and a scalar. In 2D this produces +/// a point. +inline Point Cross(const Point& a, double s) +{ + return Point(s * a.y, -s * a.x); +} + +/// Perform the cross product on a scalar and a point. In 2D this produces +/// a point. +inline Point Cross(const double s, const Point& a) +{ + return Point(-s * a.y, s * a.x); +} + +inline Point* Triangle::GetPoint(const int& index) +{ + return points_[index]; +} + +inline Triangle* Triangle::GetNeighbor(const int& index) +{ + return neighbors_[index]; +} + +inline bool Triangle::Contains(Point* p) +{ + return p == points_[0] || p == points_[1] || p == points_[2]; +} + +inline bool Triangle::Contains(const Edge& e) +{ + return Contains(e.p) && Contains(e.q); +} + +inline bool Triangle::Contains(Point* p, Point* q) +{ + return Contains(p) && Contains(q); +} + +inline bool Triangle::IsInterior() +{ + return interior_; +} + +inline void Triangle::IsInterior(bool b) +{ + interior_ = b; +} + +} + +#endif + + diff --git a/src/3rdparty/poly2tri/common/utils.h b/src/3rdparty/poly2tri/common/utils.h new file mode 100644 index 0000000..8744b6d --- /dev/null +++ b/src/3rdparty/poly2tri/common/utils.h @@ -0,0 +1,127 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UTILS_H +#define UTILS_H + +// Otherwise #defines like M_PI are undeclared under Visual Studio +#define _USE_MATH_DEFINES + +#include +#include + +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + +namespace p2t { + +const double PI_3div4 = 3 * M_PI / 4; +const double PI_div2 = 1.57079632679489661923; +const double EPSILON = 1e-12; + +enum Orientation { CW, CCW, COLLINEAR }; + +/** + * Forumla to calculate signed area
    + * Positive if CCW
    + * Negative if CW
    + * 0 if collinear
    + *
    + * A[P1,P2,P3]  =  (x1*y2 - y1*x2) + (x2*y3 - y2*x3) + (x3*y1 - y3*x1)
    + *              =  (x1-x3)*(y2-y3) - (y1-y3)*(x2-x3)
    + * 
    + */ +Orientation Orient2d(Point& pa, Point& pb, Point& pc) +{ + double detleft = (pa.x - pc.x) * (pb.y - pc.y); + double detright = (pa.y - pc.y) * (pb.x - pc.x); + double val = detleft - detright; + if (val > -EPSILON && val < EPSILON) { + return COLLINEAR; + } else if (val > 0) { + return CCW; + } + return CW; +} + +/* +bool InScanArea(Point& pa, Point& pb, Point& pc, Point& pd) +{ + double pdx = pd.x; + double pdy = pd.y; + double adx = pa.x - pdx; + double ady = pa.y - pdy; + double bdx = pb.x - pdx; + double bdy = pb.y - pdy; + + double adxbdy = adx * bdy; + double bdxady = bdx * ady; + double oabd = adxbdy - bdxady; + + if (oabd <= EPSILON) { + return false; + } + + double cdx = pc.x - pdx; + double cdy = pc.y - pdy; + + double cdxady = cdx * ady; + double adxcdy = adx * cdy; + double ocad = cdxady - adxcdy; + + if (ocad <= EPSILON) { + return false; + } + + return true; +} + +*/ + +bool InScanArea(Point& pa, Point& pb, Point& pc, Point& pd) +{ + double oadb = (pa.x - pb.x)*(pd.y - pb.y) - (pd.x - pb.x)*(pa.y - pb.y); + if (oadb >= -EPSILON) { + return false; + } + + double oadc = (pa.x - pc.x)*(pd.y - pc.y) - (pd.x - pc.x)*(pa.y - pc.y); + if (oadc <= EPSILON) { + return false; + } + return true; +} + +} + +#endif + diff --git a/src/3rdparty/poly2tri/poly2tri.h b/src/3rdparty/poly2tri/poly2tri.h new file mode 100644 index 0000000..042cb3d --- /dev/null +++ b/src/3rdparty/poly2tri/poly2tri.h @@ -0,0 +1,39 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef POLY2TRI_H +#define POLY2TRI_H + +#include "common/shapes.h" +#include "sweep/cdt.h" + +#endif + diff --git a/src/3rdparty/poly2tri/poly2tri.pro b/src/3rdparty/poly2tri/poly2tri.pro new file mode 100644 index 0000000..d4ae9cd --- /dev/null +++ b/src/3rdparty/poly2tri/poly2tri.pro @@ -0,0 +1,26 @@ +TARGET = poly2tri + +CONFIG += staticlib + +load(qt_helper_lib) + +# workaround for QTBUG-31586 +contains(QT_CONFIG, c++11): CONFIG += c++11 + +*-g++* { + QMAKE_CXXFLAGS += -O3 -ftree-vectorize -ffast-math -funsafe-math-optimizations -Wno-error=return-type +} + +HEADERS += poly2tri.h \ + common/shapes.h \ + common/utils.h \ + sweep/advancing_front.h \ + sweep/cdt.h \ + sweep/sweep.h \ + sweep/sweep_context.h + +SOURCES += common/shapes.cpp \ + sweep/sweep_context.cpp \ + sweep/cdt.cpp \ + sweep/sweep.cpp \ + sweep/advancing_front.cpp diff --git a/src/3rdparty/poly2tri/sweep/advancing_front.cpp b/src/3rdparty/poly2tri/sweep/advancing_front.cpp new file mode 100644 index 0000000..0377984 --- /dev/null +++ b/src/3rdparty/poly2tri/sweep/advancing_front.cpp @@ -0,0 +1,109 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "advancing_front.h" + +namespace p2t { + +AdvancingFront::AdvancingFront(Node& head, Node& tail) +{ + head_ = &head; + tail_ = &tail; + search_node_ = &head; +} + +Node* AdvancingFront::LocateNode(const double& x) +{ + Node* node = search_node_; + + if (x < node->value) { + while ((node = node->prev) != NULL) { + if (x >= node->value) { + search_node_ = node; + return node; + } + } + } else { + while ((node = node->next) != NULL) { + if (x < node->value) { + search_node_ = node->prev; + return node->prev; + } + } + } + return NULL; +} + +Node* AdvancingFront::FindSearchNode(const double& x) +{ + (void)x; // suppress compiler warnings "unused parameter 'x'" + // TODO: implement BST index + return search_node_; +} + +Node* AdvancingFront::LocatePoint(const Point* point) +{ + const double px = point->x; + Node* node = FindSearchNode(px); + const double nx = node->point->x; + + if (px == nx) { + if (point != node->point) { + // We might have two nodes with same x value for a short time + if (point == node->prev->point) { + node = node->prev; + } else if (point == node->next->point) { + node = node->next; + } else { + assert(0); + } + } + } else if (px < nx) { + while ((node = node->prev) != NULL) { + if (point == node->point) { + break; + } + } + } else { + while ((node = node->next) != NULL) { + if (point == node->point) + break; + } + } + if (node) search_node_ = node; + return node; +} + +AdvancingFront::~AdvancingFront() +{ +} + +} + diff --git a/src/3rdparty/poly2tri/sweep/advancing_front.h b/src/3rdparty/poly2tri/sweep/advancing_front.h new file mode 100644 index 0000000..bab73d4 --- /dev/null +++ b/src/3rdparty/poly2tri/sweep/advancing_front.h @@ -0,0 +1,118 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ADVANCED_FRONT_H +#define ADVANCED_FRONT_H + +#include "../common/shapes.h" + +namespace p2t { + +struct Node; + +// Advancing front node +struct Node { + Point* point; + Triangle* triangle; + + Node* next; + Node* prev; + + double value; + + Node(Point& p) : point(&p), triangle(NULL), next(NULL), prev(NULL), value(p.x) + { + } + + Node(Point& p, Triangle& t) : point(&p), triangle(&t), next(NULL), prev(NULL), value(p.x) + { + } + +}; + +// Advancing front +class AdvancingFront { +public: + +AdvancingFront(Node& head, Node& tail); +// Destructor +~AdvancingFront(); + +Node* head(); +void set_head(Node* node); +Node* tail(); +void set_tail(Node* node); +Node* search(); +void set_search(Node* node); + +/// Locate insertion point along advancing front +Node* LocateNode(const double& x); + +Node* LocatePoint(const Point* point); + +private: + +Node* head_, *tail_, *search_node_; + +Node* FindSearchNode(const double& x); +}; + +inline Node* AdvancingFront::head() +{ + return head_; +} +inline void AdvancingFront::set_head(Node* node) +{ + head_ = node; +} + +inline Node* AdvancingFront::tail() +{ + return tail_; +} +inline void AdvancingFront::set_tail(Node* node) +{ + tail_ = node; +} + +inline Node* AdvancingFront::search() +{ + return search_node_; +} + +inline void AdvancingFront::set_search(Node* node) +{ + search_node_ = node; +} + +} + +#endif diff --git a/src/3rdparty/poly2tri/sweep/cdt.cpp b/src/3rdparty/poly2tri/sweep/cdt.cpp new file mode 100644 index 0000000..e0b3ec7 --- /dev/null +++ b/src/3rdparty/poly2tri/sweep/cdt.cpp @@ -0,0 +1,72 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "cdt.h" + +namespace p2t { + +CDT::CDT(std::vector polyline) +{ + sweep_context_ = new SweepContext(polyline); + sweep_ = new Sweep; +} + +void CDT::AddHole(std::vector polyline) +{ + sweep_context_->AddHole(polyline); +} + +void CDT::AddPoint(Point* point) { + sweep_context_->AddPoint(point); +} + +void CDT::Triangulate() +{ + sweep_->Triangulate(*sweep_context_); +} + +std::vector CDT::GetTriangles() +{ + return sweep_context_->GetTriangles(); +} + +std::list CDT::GetMap() +{ + return sweep_context_->GetMap(); +} + +CDT::~CDT() +{ + delete sweep_context_; + delete sweep_; +} + +} + diff --git a/src/3rdparty/poly2tri/sweep/cdt.h b/src/3rdparty/poly2tri/sweep/cdt.h new file mode 100644 index 0000000..e7b703d --- /dev/null +++ b/src/3rdparty/poly2tri/sweep/cdt.h @@ -0,0 +1,105 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CDT_H +#define CDT_H + +#include "advancing_front.h" +#include "sweep_context.h" +#include "sweep.h" + +/** + * + * @author Mason Green + * + */ + +namespace p2t { + +class CDT +{ +public: + + /** + * Constructor - add polyline with non repeating points + * + * @param polyline + */ + CDT(std::vector polyline); + + /** + * Destructor - clean up memory + */ + ~CDT(); + + /** + * Add a hole + * + * @param polyline + */ + void AddHole(std::vector polyline); + + /** + * Add a steiner point + * + * @param point + */ + void AddPoint(Point* point); + + /** + * Triangulate - do this AFTER you've added the polyline, holes, and Steiner points + */ + void Triangulate(); + + /** + * Get CDT triangles + */ + std::vector GetTriangles(); + + /** + * Get triangle map + */ + std::list GetMap(); + + private: + + /** + * Internals + */ + + SweepContext* sweep_context_; + Sweep* sweep_; + +}; + +} + +#endif diff --git a/src/3rdparty/poly2tri/sweep/sweep.cpp b/src/3rdparty/poly2tri/sweep/sweep.cpp new file mode 100644 index 0000000..954d2db --- /dev/null +++ b/src/3rdparty/poly2tri/sweep/sweep.cpp @@ -0,0 +1,814 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include "sweep.h" +#include "sweep_context.h" +#include "advancing_front.h" +#include "../common/utils.h" + +namespace p2t { + +// Triangulate simple polygon with holes +void Sweep::Triangulate(SweepContext& tcx) +{ + tcx.InitTriangulation(); + tcx.CreateAdvancingFront(nodes_); + // Sweep points; build mesh + SweepPoints(tcx); + // Clean up + FinalizationPolygon(tcx); +} + +void Sweep::SweepPoints(SweepContext& tcx) +{ + for (int i = 1; i < tcx.point_count(); i++) { + Point& point = *tcx.GetPoint(i); + Node* node = &PointEvent(tcx, point); + for (unsigned int i = 0; i < point.edge_list.size(); i++) { + EdgeEvent(tcx, point.edge_list[i], node); + } + } +} + +void Sweep::FinalizationPolygon(SweepContext& tcx) +{ + // Get an Internal triangle to start with + Triangle* t = tcx.front()->head()->next->triangle; + Point* p = tcx.front()->head()->next->point; + while (!t->GetConstrainedEdgeCW(*p)) { + t = t->NeighborCCW(*p); + } + + // Collect interior triangles constrained by edges + tcx.MeshClean(*t); +} + +Node& Sweep::PointEvent(SweepContext& tcx, Point& point) +{ + Node& node = tcx.LocateNode(point); + Node& new_node = NewFrontTriangle(tcx, point, node); + + // Only need to check +epsilon since point never have smaller + // x value than node due to how we fetch nodes from the front + if (point.x <= node.point->x + EPSILON) { + Fill(tcx, node); + } + + //tcx.AddNode(new_node); + + FillAdvancingFront(tcx, new_node); + return new_node; +} + +void Sweep::EdgeEvent(SweepContext& tcx, Edge* edge, Node* node) +{ + tcx.edge_event.constrained_edge = edge; + tcx.edge_event.right = (edge->p->x > edge->q->x); + + if (IsEdgeSideOfTriangle(*node->triangle, *edge->p, *edge->q)) { + return; + } + + // For now we will do all needed filling + // TODO: integrate with flip process might give some better performance + // but for now this avoid the issue with cases that needs both flips and fills + FillEdgeEvent(tcx, edge, node); + EdgeEvent(tcx, *edge->p, *edge->q, node->triangle, *edge->q); +} + +void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangle, Point& point) +{ + if (IsEdgeSideOfTriangle(*triangle, ep, eq)) { + return; + } + + Point* p1 = triangle->PointCCW(point); + Orientation o1 = Orient2d(eq, *p1, ep); + if (o1 == COLLINEAR) { + if ( triangle->Contains(&eq, p1)) { + triangle->MarkConstrainedEdge(&eq, p1 ); + // We are modifying the constraint maybe it would be better to + // not change the given constraint and just keep a variable for the new constraint + tcx.edge_event.constrained_edge->q = p1; + triangle = &triangle->NeighborAcross(point); + EdgeEvent( tcx, ep, *p1, triangle, *p1 ); + } else { + std::runtime_error("EdgeEvent - collinear points not supported"); + assert(0); + } + return; + } + + Point* p2 = triangle->PointCW(point); + Orientation o2 = Orient2d(eq, *p2, ep); + if (o2 == COLLINEAR) { + if ( triangle->Contains(&eq, p2)) { + triangle->MarkConstrainedEdge(&eq, p2 ); + // We are modifying the constraint maybe it would be better to + // not change the given constraint and just keep a variable for the new constraint + tcx.edge_event.constrained_edge->q = p2; + triangle = &triangle->NeighborAcross(point); + EdgeEvent( tcx, ep, *p2, triangle, *p2 ); + } else { + std::runtime_error("EdgeEvent - collinear points not supported"); + assert(0); + } + return; + } + + if (o1 == o2) { + // Need to decide if we are rotating CW or CCW to get to a triangle + // that will cross edge + if (o1 == CW) { + triangle = triangle->NeighborCCW(point); + } else{ + triangle = triangle->NeighborCW(point); + } + EdgeEvent(tcx, ep, eq, triangle, point); + } else { + // This triangle crosses constraint so lets flippin start! + FlipEdgeEvent(tcx, ep, eq, triangle, point); + } +} + +bool Sweep::IsEdgeSideOfTriangle(Triangle& triangle, Point& ep, Point& eq) +{ + int index = triangle.EdgeIndex(&ep, &eq); + + if (index != -1) { + triangle.MarkConstrainedEdge(index); + Triangle* t = triangle.GetNeighbor(index); + if (t) { + t->MarkConstrainedEdge(&ep, &eq); + } + return true; + } + return false; +} + +Node& Sweep::NewFrontTriangle(SweepContext& tcx, Point& point, Node& node) +{ + Triangle* triangle = new Triangle(point, *node.point, *node.next->point); + + triangle->MarkNeighbor(*node.triangle); + tcx.AddToMap(triangle); + + Node* new_node = new Node(point); + nodes_.push_back(new_node); + + new_node->next = node.next; + new_node->prev = &node; + node.next->prev = new_node; + node.next = new_node; + + if (!Legalize(tcx, *triangle)) { + tcx.MapTriangleToNodes(*triangle); + } + + return *new_node; +} + +void Sweep::Fill(SweepContext& tcx, Node& node) +{ + Triangle* triangle = new Triangle(*node.prev->point, *node.point, *node.next->point); + + // TODO: should copy the constrained_edge value from neighbor triangles + // for now constrained_edge values are copied during the legalize + triangle->MarkNeighbor(*node.prev->triangle); + triangle->MarkNeighbor(*node.triangle); + + tcx.AddToMap(triangle); + + // Update the advancing front + node.prev->next = node.next; + node.next->prev = node.prev; + + // If it was legalized the triangle has already been mapped + if (!Legalize(tcx, *triangle)) { + tcx.MapTriangleToNodes(*triangle); + } + +} + +void Sweep::FillAdvancingFront(SweepContext& tcx, Node& n) +{ + + // Fill right holes + Node* node = n.next; + + while (node->next) { + // if HoleAngle exceeds 90 degrees then break. + if (LargeHole_DontFill(node)) break; + Fill(tcx, *node); + node = node->next; + } + + // Fill left holes + node = n.prev; + + while (node->prev) { + // if HoleAngle exceeds 90 degrees then break. + if (LargeHole_DontFill(node)) break; + Fill(tcx, *node); + node = node->prev; + } + + // Fill right basins + if (n.next && n.next->next) { + double angle = BasinAngle(n); + if (angle < PI_3div4) { + FillBasin(tcx, n); + } + } +} + +// True if HoleAngle exceeds 90 degrees. +bool Sweep::LargeHole_DontFill(Node* node) { + + Node* nextNode = node->next; + Node* prevNode = node->prev; + if (!AngleExceeds90Degrees(node->point, nextNode->point, prevNode->point)) + return false; + + // Check additional points on front. + Node* next2Node = nextNode->next; + // "..Plus.." because only want angles on same side as point being added. + if ((next2Node != NULL) && !AngleExceedsPlus90DegreesOrIsNegative(node->point, next2Node->point, prevNode->point)) + return false; + + Node* prev2Node = prevNode->prev; + // "..Plus.." because only want angles on same side as point being added. + if ((prev2Node != NULL) && !AngleExceedsPlus90DegreesOrIsNegative(node->point, nextNode->point, prev2Node->point)) + return false; + + return true; +} + +bool Sweep::AngleExceeds90Degrees(Point* origin, Point* pa, Point* pb) { + double angle = Angle(*origin, *pa, *pb); + bool exceeds90Degrees = ((angle > PI_div2) || (angle < -PI_div2)); + return exceeds90Degrees; +} + +bool Sweep::AngleExceedsPlus90DegreesOrIsNegative(Point* origin, Point* pa, Point* pb) { + double angle = Angle(*origin, *pa, *pb); + bool exceedsPlus90DegreesOrIsNegative = (angle > PI_div2) || (angle < 0); + return exceedsPlus90DegreesOrIsNegative; +} + +double Sweep::Angle(Point& origin, Point& pa, Point& pb) { + /* Complex plane + * ab = cosA +i*sinA + * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx) + * atan2(y,x) computes the principal value of the argument function + * applied to the complex number x+iy + * Where x = ax*bx + ay*by + * y = ax*by - ay*bx + */ + double px = origin.x; + double py = origin.y; + double ax = pa.x- px; + double ay = pa.y - py; + double bx = pb.x - px; + double by = pb.y - py; + double x = ax * by - ay * bx; + double y = ax * bx + ay * by; + double angle = atan2(x, y); + return angle; +} + +double Sweep::BasinAngle(Node& node) +{ + double ax = node.point->x - node.next->next->point->x; + double ay = node.point->y - node.next->next->point->y; + return atan2(ay, ax); +} + +double Sweep::HoleAngle(Node& node) +{ + /* Complex plane + * ab = cosA +i*sinA + * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx) + * atan2(y,x) computes the principal value of the argument function + * applied to the complex number x+iy + * Where x = ax*bx + ay*by + * y = ax*by - ay*bx + */ + double ax = node.next->point->x - node.point->x; + double ay = node.next->point->y - node.point->y; + double bx = node.prev->point->x - node.point->x; + double by = node.prev->point->y - node.point->y; + return atan2(ax * by - ay * bx, ax * bx + ay * by); +} + +bool Sweep::Legalize(SweepContext& tcx, Triangle& t) +{ + // To legalize a triangle we start by finding if any of the three edges + // violate the Delaunay condition + for (int i = 0; i < 3; i++) { + if (t.delaunay_edge[i]) + continue; + + Triangle* ot = t.GetNeighbor(i); + + if (ot) { + Point* p = t.GetPoint(i); + Point* op = ot->OppositePoint(t, *p); + int oi = ot->Index(op); + + // If this is a Constrained Edge or a Delaunay Edge(only during recursive legalization) + // then we should not try to legalize + if (ot->constrained_edge[oi] || ot->delaunay_edge[oi]) { + t.constrained_edge[i] = ot->constrained_edge[oi]; + continue; + } + + bool inside = Incircle(*p, *t.PointCCW(*p), *t.PointCW(*p), *op); + + if (inside) { + // Lets mark this shared edge as Delaunay + t.delaunay_edge[i] = true; + ot->delaunay_edge[oi] = true; + + // Lets rotate shared edge one vertex CW to legalize it + RotateTrianglePair(t, *p, *ot, *op); + + // We now got one valid Delaunay Edge shared by two triangles + // This gives us 4 new edges to check for Delaunay + + // Make sure that triangle to node mapping is done only one time for a specific triangle + bool not_legalized = !Legalize(tcx, t); + if (not_legalized) { + tcx.MapTriangleToNodes(t); + } + + not_legalized = !Legalize(tcx, *ot); + if (not_legalized) + tcx.MapTriangleToNodes(*ot); + + // Reset the Delaunay edges, since they only are valid Delaunay edges + // until we add a new triangle or point. + // XXX: need to think about this. Can these edges be tried after we + // return to previous recursive level? + t.delaunay_edge[i] = false; + ot->delaunay_edge[oi] = false; + + // If triangle have been legalized no need to check the other edges since + // the recursive legalization will handles those so we can end here. + return true; + } + } + } + return false; +} + +bool Sweep::Incircle(Point& pa, Point& pb, Point& pc, Point& pd) +{ + double adx = pa.x - pd.x; + double ady = pa.y - pd.y; + double bdx = pb.x - pd.x; + double bdy = pb.y - pd.y; + + double adxbdy = adx * bdy; + double bdxady = bdx * ady; + double oabd = adxbdy - bdxady; + + if (oabd <= 0) + return false; + + double cdx = pc.x - pd.x; + double cdy = pc.y - pd.y; + + double cdxady = cdx * ady; + double adxcdy = adx * cdy; + double ocad = cdxady - adxcdy; + + if (ocad <= 0) + return false; + + double bdxcdy = bdx * cdy; + double cdxbdy = cdx * bdy; + + double alift = adx * adx + ady * ady; + double blift = bdx * bdx + bdy * bdy; + double clift = cdx * cdx + cdy * cdy; + + double det = alift * (bdxcdy - cdxbdy) + blift * ocad + clift * oabd; + + return det > 0; +} + +void Sweep::RotateTrianglePair(Triangle& t, Point& p, Triangle& ot, Point& op) +{ + Triangle* n1, *n2, *n3, *n4; + n1 = t.NeighborCCW(p); + n2 = t.NeighborCW(p); + n3 = ot.NeighborCCW(op); + n4 = ot.NeighborCW(op); + + bool ce1, ce2, ce3, ce4; + ce1 = t.GetConstrainedEdgeCCW(p); + ce2 = t.GetConstrainedEdgeCW(p); + ce3 = ot.GetConstrainedEdgeCCW(op); + ce4 = ot.GetConstrainedEdgeCW(op); + + bool de1, de2, de3, de4; + de1 = t.GetDelunayEdgeCCW(p); + de2 = t.GetDelunayEdgeCW(p); + de3 = ot.GetDelunayEdgeCCW(op); + de4 = ot.GetDelunayEdgeCW(op); + + t.Legalize(p, op); + ot.Legalize(op, p); + + // Remap delaunay_edge + ot.SetDelunayEdgeCCW(p, de1); + t.SetDelunayEdgeCW(p, de2); + t.SetDelunayEdgeCCW(op, de3); + ot.SetDelunayEdgeCW(op, de4); + + // Remap constrained_edge + ot.SetConstrainedEdgeCCW(p, ce1); + t.SetConstrainedEdgeCW(p, ce2); + t.SetConstrainedEdgeCCW(op, ce3); + ot.SetConstrainedEdgeCW(op, ce4); + + // Remap neighbors + // XXX: might optimize the markNeighbor by keeping track of + // what side should be assigned to what neighbor after the + // rotation. Now mark neighbor does lots of testing to find + // the right side. + t.ClearNeighbors(); + ot.ClearNeighbors(); + if (n1) ot.MarkNeighbor(*n1); + if (n2) t.MarkNeighbor(*n2); + if (n3) t.MarkNeighbor(*n3); + if (n4) ot.MarkNeighbor(*n4); + t.MarkNeighbor(ot); +} + +void Sweep::FillBasin(SweepContext& tcx, Node& node) +{ + if (Orient2d(*node.point, *node.next->point, *node.next->next->point) == CCW) { + tcx.basin.left_node = node.next->next; + } else { + tcx.basin.left_node = node.next; + } + + // Find the bottom and right node + tcx.basin.bottom_node = tcx.basin.left_node; + while (tcx.basin.bottom_node->next + && tcx.basin.bottom_node->point->y >= tcx.basin.bottom_node->next->point->y) { + tcx.basin.bottom_node = tcx.basin.bottom_node->next; + } + if (tcx.basin.bottom_node == tcx.basin.left_node) { + // No valid basin + return; + } + + tcx.basin.right_node = tcx.basin.bottom_node; + while (tcx.basin.right_node->next + && tcx.basin.right_node->point->y < tcx.basin.right_node->next->point->y) { + tcx.basin.right_node = tcx.basin.right_node->next; + } + if (tcx.basin.right_node == tcx.basin.bottom_node) { + // No valid basins + return; + } + + tcx.basin.width = tcx.basin.right_node->point->x - tcx.basin.left_node->point->x; + tcx.basin.left_highest = tcx.basin.left_node->point->y > tcx.basin.right_node->point->y; + + FillBasinReq(tcx, tcx.basin.bottom_node); +} + +void Sweep::FillBasinReq(SweepContext& tcx, Node* node) +{ + // if shallow stop filling + if (IsShallow(tcx, *node)) { + return; + } + + Fill(tcx, *node); + + if (node->prev == tcx.basin.left_node && node->next == tcx.basin.right_node) { + return; + } else if (node->prev == tcx.basin.left_node) { + Orientation o = Orient2d(*node->point, *node->next->point, *node->next->next->point); + if (o == CW) { + return; + } + node = node->next; + } else if (node->next == tcx.basin.right_node) { + Orientation o = Orient2d(*node->point, *node->prev->point, *node->prev->prev->point); + if (o == CCW) { + return; + } + node = node->prev; + } else { + // Continue with the neighbor node with lowest Y value + if (node->prev->point->y < node->next->point->y) { + node = node->prev; + } else { + node = node->next; + } + } + + FillBasinReq(tcx, node); +} + +bool Sweep::IsShallow(SweepContext& tcx, Node& node) +{ + double height; + + if (tcx.basin.left_highest) { + height = tcx.basin.left_node->point->y - node.point->y; + } else { + height = tcx.basin.right_node->point->y - node.point->y; + } + + // if shallow stop filling + if (tcx.basin.width > height) { + return true; + } + return false; +} + +void Sweep::FillEdgeEvent(SweepContext& tcx, Edge* edge, Node* node) +{ + if (tcx.edge_event.right) { + FillRightAboveEdgeEvent(tcx, edge, node); + } else { + FillLeftAboveEdgeEvent(tcx, edge, node); + } +} + +void Sweep::FillRightAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node) +{ + while (node->next->point->x < edge->p->x) { + // Check if next node is below the edge + if (Orient2d(*edge->q, *node->next->point, *edge->p) == CCW) { + FillRightBelowEdgeEvent(tcx, edge, *node); + } else { + node = node->next; + } + } +} + +void Sweep::FillRightBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) +{ + if (node.point->x < edge->p->x) { + if (Orient2d(*node.point, *node.next->point, *node.next->next->point) == CCW) { + // Concave + FillRightConcaveEdgeEvent(tcx, edge, node); + } else{ + // Convex + FillRightConvexEdgeEvent(tcx, edge, node); + // Retry this one + FillRightBelowEdgeEvent(tcx, edge, node); + } + } +} + +void Sweep::FillRightConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) +{ + Fill(tcx, *node.next); + if (node.next->point != edge->p) { + // Next above or below edge? + if (Orient2d(*edge->q, *node.next->point, *edge->p) == CCW) { + // Below + if (Orient2d(*node.point, *node.next->point, *node.next->next->point) == CCW) { + // Next is concave + FillRightConcaveEdgeEvent(tcx, edge, node); + } else { + // Next is convex + } + } + } + +} + +void Sweep::FillRightConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) +{ + // Next concave or convex? + if (Orient2d(*node.next->point, *node.next->next->point, *node.next->next->next->point) == CCW) { + // Concave + FillRightConcaveEdgeEvent(tcx, edge, *node.next); + } else{ + // Convex + // Next above or below edge? + if (Orient2d(*edge->q, *node.next->next->point, *edge->p) == CCW) { + // Below + FillRightConvexEdgeEvent(tcx, edge, *node.next); + } else{ + // Above + } + } +} + +void Sweep::FillLeftAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node) +{ + while (node->prev->point->x > edge->p->x) { + // Check if next node is below the edge + if (Orient2d(*edge->q, *node->prev->point, *edge->p) == CW) { + FillLeftBelowEdgeEvent(tcx, edge, *node); + } else { + node = node->prev; + } + } +} + +void Sweep::FillLeftBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) +{ + if (node.point->x > edge->p->x) { + if (Orient2d(*node.point, *node.prev->point, *node.prev->prev->point) == CW) { + // Concave + FillLeftConcaveEdgeEvent(tcx, edge, node); + } else { + // Convex + FillLeftConvexEdgeEvent(tcx, edge, node); + // Retry this one + FillLeftBelowEdgeEvent(tcx, edge, node); + } + } +} + +void Sweep::FillLeftConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) +{ + // Next concave or convex? + if (Orient2d(*node.prev->point, *node.prev->prev->point, *node.prev->prev->prev->point) == CW) { + // Concave + FillLeftConcaveEdgeEvent(tcx, edge, *node.prev); + } else{ + // Convex + // Next above or below edge? + if (Orient2d(*edge->q, *node.prev->prev->point, *edge->p) == CW) { + // Below + FillLeftConvexEdgeEvent(tcx, edge, *node.prev); + } else{ + // Above + } + } +} + +void Sweep::FillLeftConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) +{ + Fill(tcx, *node.prev); + if (node.prev->point != edge->p) { + // Next above or below edge? + if (Orient2d(*edge->q, *node.prev->point, *edge->p) == CW) { + // Below + if (Orient2d(*node.point, *node.prev->point, *node.prev->prev->point) == CW) { + // Next is concave + FillLeftConcaveEdgeEvent(tcx, edge, node); + } else{ + // Next is convex + } + } + } + +} + +void Sweep::FlipEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* t, Point& p) +{ + Triangle& ot = t->NeighborAcross(p); + Point& op = *ot.OppositePoint(*t, p); + + if (&ot == NULL) { + // If we want to integrate the fillEdgeEvent do it here + // With current implementation we should never get here + //throw new RuntimeException( "[BUG:FIXME] FLIP failed due to missing triangle"); + assert(0); + } + + if (InScanArea(p, *t->PointCCW(p), *t->PointCW(p), op)) { + // Lets rotate shared edge one vertex CW + RotateTrianglePair(*t, p, ot, op); + tcx.MapTriangleToNodes(*t); + tcx.MapTriangleToNodes(ot); + + if (p == eq && op == ep) { + if (eq == *tcx.edge_event.constrained_edge->q && ep == *tcx.edge_event.constrained_edge->p) { + t->MarkConstrainedEdge(&ep, &eq); + ot.MarkConstrainedEdge(&ep, &eq); + Legalize(tcx, *t); + Legalize(tcx, ot); + } else { + // XXX: I think one of the triangles should be legalized here? + } + } else { + Orientation o = Orient2d(eq, op, ep); + t = &NextFlipTriangle(tcx, (int)o, *t, ot, p, op); + FlipEdgeEvent(tcx, ep, eq, t, p); + } + } else { + Point& newP = NextFlipPoint(ep, eq, ot, op); + FlipScanEdgeEvent(tcx, ep, eq, *t, ot, newP); + EdgeEvent(tcx, ep, eq, t, p); + } +} + +Triangle& Sweep::NextFlipTriangle(SweepContext& tcx, int o, Triangle& t, Triangle& ot, Point& p, Point& op) +{ + if (o == CCW) { + // ot is not crossing edge after flip + int edge_index = ot.EdgeIndex(&p, &op); + ot.delaunay_edge[edge_index] = true; + Legalize(tcx, ot); + ot.ClearDelunayEdges(); + return t; + } + + // t is not crossing edge after flip + int edge_index = t.EdgeIndex(&p, &op); + + t.delaunay_edge[edge_index] = true; + Legalize(tcx, t); + t.ClearDelunayEdges(); + return ot; +} + +Point& Sweep::NextFlipPoint(Point& ep, Point& eq, Triangle& ot, Point& op) +{ + Orientation o2d = Orient2d(eq, op, ep); + if (o2d == CW) { + // Right + return *ot.PointCCW(op); + } else if (o2d == CCW) { + // Left + return *ot.PointCW(op); + } else{ + //throw new RuntimeException("[Unsupported] Opposing point on constrained edge"); + assert(0); + } +} + +void Sweep::FlipScanEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle& flip_triangle, + Triangle& t, Point& p) +{ + Triangle& ot = t.NeighborAcross(p); + Point& op = *ot.OppositePoint(t, p); + + if (&t.NeighborAcross(p) == NULL) { + // If we want to integrate the fillEdgeEvent do it here + // With current implementation we should never get here + //throw new RuntimeException( "[BUG:FIXME] FLIP failed due to missing triangle"); + assert(0); + } + + if (InScanArea(eq, *flip_triangle.PointCCW(eq), *flip_triangle.PointCW(eq), op)) { + // flip with new edge op->eq + FlipEdgeEvent(tcx, eq, op, &ot, op); + // TODO: Actually I just figured out that it should be possible to + // improve this by getting the next ot and op before the above + // flip and continue the flipScanEdgeEvent here + // set new ot and op here and loop back to inScanArea test + // also need to set a new flip_triangle first + // Turns out at first glance that this is somewhat complicated + // so it will have to wait. + } else{ + Point& newP = NextFlipPoint(ep, eq, ot, op); + FlipScanEdgeEvent(tcx, ep, eq, flip_triangle, ot, newP); + } +} + +Sweep::~Sweep() { + + // Clean up memory + for (size_t i = 0; i < nodes_.size(); i++) { + delete nodes_[i]; + } + +} + +} + diff --git a/src/3rdparty/poly2tri/sweep/sweep.h b/src/3rdparty/poly2tri/sweep/sweep.h new file mode 100644 index 0000000..9bb0b5d --- /dev/null +++ b/src/3rdparty/poly2tri/sweep/sweep.h @@ -0,0 +1,285 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Sweep-line, Constrained Delauney Triangulation (CDT) See: Domiter, V. and + * Zalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation', + * International Journal of Geographical Information Science + * + * "FlipScan" Constrained Edge Algorithm invented by Thomas Åhlén, thahlen@gmail.com + */ + +#ifndef SWEEP_H +#define SWEEP_H + +#include + +namespace p2t { + +class SweepContext; +struct Node; +struct Point; +struct Edge; +class Triangle; + +class Sweep +{ +public: + + /** + * Triangulate + * + * @param tcx + */ + void Triangulate(SweepContext& tcx); + + /** + * Destructor - clean up memory + */ + ~Sweep(); + +private: + + /** + * Start sweeping the Y-sorted point set from bottom to top + * + * @param tcx + */ + void SweepPoints(SweepContext& tcx); + + /** + * Find closes node to the left of the new point and + * create a new triangle. If needed new holes and basins + * will be filled to. + * + * @param tcx + * @param point + * @return + */ + Node& PointEvent(SweepContext& tcx, Point& point); + + /** + * + * + * @param tcx + * @param edge + * @param node + */ + void EdgeEvent(SweepContext& tcx, Edge* edge, Node* node); + + void EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangle, Point& point); + + /** + * Creates a new front triangle and legalize it + * + * @param tcx + * @param point + * @param node + * @return + */ + Node& NewFrontTriangle(SweepContext& tcx, Point& point, Node& node); + + /** + * Adds a triangle to the advancing front to fill a hole. + * @param tcx + * @param node - middle node, that is the bottom of the hole + */ + void Fill(SweepContext& tcx, Node& node); + + /** + * Returns true if triangle was legalized + */ + bool Legalize(SweepContext& tcx, Triangle& t); + + /** + * Requirement:
    + * 1. a,b and c form a triangle.
    + * 2. a and d is know to be on opposite side of bc
    + *
    +   *                a
    +   *                +
    +   *               / \
    +   *              /   \
    +   *            b/     \c
    +   *            +-------+
    +   *           /    d    \
    +   *          /           \
    +   * 
    + * Fact: d has to be in area B to have a chance to be inside the circle formed by + * a,b and c
    + * d is outside B if orient2d(a,b,d) or orient2d(c,a,d) is CW
    + * This preknowledge gives us a way to optimize the incircle test + * @param a - triangle point, opposite d + * @param b - triangle point + * @param c - triangle point + * @param d - point opposite a + * @return true if d is inside circle, false if on circle edge + */ + bool Incircle(Point& pa, Point& pb, Point& pc, Point& pd); + + /** + * Rotates a triangle pair one vertex CW + *
    +   *       n2                    n2
    +   *  P +-----+             P +-----+
    +   *    | t  /|               |\  t |
    +   *    |   / |               | \   |
    +   *  n1|  /  |n3           n1|  \  |n3
    +   *    | /   |    after CW   |   \ |
    +   *    |/ oT |               | oT \|
    +   *    +-----+ oP            +-----+
    +   *       n4                    n4
    +   * 
    + */ + void RotateTrianglePair(Triangle& t, Point& p, Triangle& ot, Point& op); + + /** + * Fills holes in the Advancing Front + * + * + * @param tcx + * @param n + */ + void FillAdvancingFront(SweepContext& tcx, Node& n); + + // Decision-making about when to Fill hole. + // Contributed by ToolmakerSteve2 + bool LargeHole_DontFill(Node* node); + bool AngleExceeds90Degrees(Point* origin, Point* pa, Point* pb); + bool AngleExceedsPlus90DegreesOrIsNegative(Point* origin, Point* pa, Point* pb); + double Angle(Point& origin, Point& pa, Point& pb); + + /** + * + * @param node - middle node + * @return the angle between 3 front nodes + */ + double HoleAngle(Node& node); + + /** + * The basin angle is decided against the horizontal line [1,0] + */ + double BasinAngle(Node& node); + + /** + * Fills a basin that has formed on the Advancing Front to the right + * of given node.
    + * First we decide a left,bottom and right node that forms the + * boundaries of the basin. Then we do a reqursive fill. + * + * @param tcx + * @param node - starting node, this or next node will be left node + */ + void FillBasin(SweepContext& tcx, Node& node); + + /** + * Recursive algorithm to fill a Basin with triangles + * + * @param tcx + * @param node - bottom_node + * @param cnt - counter used to alternate on even and odd numbers + */ + void FillBasinReq(SweepContext& tcx, Node* node); + + bool IsShallow(SweepContext& tcx, Node& node); + + bool IsEdgeSideOfTriangle(Triangle& triangle, Point& ep, Point& eq); + + void FillEdgeEvent(SweepContext& tcx, Edge* edge, Node* node); + + void FillRightAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node); + + void FillRightBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); + + void FillRightConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); + + void FillRightConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); + + void FillLeftAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node); + + void FillLeftBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); + + void FillLeftConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); + + void FillLeftConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); + + void FlipEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* t, Point& p); + + /** + * After a flip we have two triangles and know that only one will still be + * intersecting the edge. So decide which to contiune with and legalize the other + * + * @param tcx + * @param o - should be the result of an orient2d( eq, op, ep ) + * @param t - triangle 1 + * @param ot - triangle 2 + * @param p - a point shared by both triangles + * @param op - another point shared by both triangles + * @return returns the triangle still intersecting the edge + */ + Triangle& NextFlipTriangle(SweepContext& tcx, int o, Triangle& t, Triangle& ot, Point& p, Point& op); + + /** + * When we need to traverse from one triangle to the next we need + * the point in current triangle that is the opposite point to the next + * triangle. + * + * @param ep + * @param eq + * @param ot + * @param op + * @return + */ + Point& NextFlipPoint(Point& ep, Point& eq, Triangle& ot, Point& op); + + /** + * Scan part of the FlipScan algorithm
    + * When a triangle pair isn't flippable we will scan for the next + * point that is inside the flip triangle scan area. When found + * we generate a new flipEdgeEvent + * + * @param tcx + * @param ep - last point on the edge we are traversing + * @param eq - first point on the edge we are traversing + * @param flipTriangle - the current triangle sharing the point eq with edge + * @param t + * @param p + */ + void FlipScanEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle& flip_triangle, Triangle& t, Point& p); + + void FinalizationPolygon(SweepContext& tcx); + + std::vector nodes_; + +}; + +} + +#endif diff --git a/src/3rdparty/poly2tri/sweep/sweep_context.cpp b/src/3rdparty/poly2tri/sweep/sweep_context.cpp new file mode 100644 index 0000000..24dde11 --- /dev/null +++ b/src/3rdparty/poly2tri/sweep/sweep_context.cpp @@ -0,0 +1,216 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "sweep_context.h" +#include +#include "advancing_front.h" + +namespace p2t { + +SweepContext::SweepContext(std::vector polyline) : + front_(0), + head_(0), + tail_(0), + af_head_(0), + af_middle_(0), + af_tail_(0) +{ + basin = Basin(); + edge_event = EdgeEvent(); + + points_ = polyline; + + InitEdges(points_); +} + +void SweepContext::AddHole(std::vector polyline) +{ + InitEdges(polyline); + for (unsigned int i = 0; i < polyline.size(); i++) { + points_.push_back(polyline[i]); + } +} + +void SweepContext::AddPoint(Point* point) { + points_.push_back(point); +} + +std::vector SweepContext::GetTriangles() +{ + return triangles_; +} + +std::list SweepContext::GetMap() +{ + return map_; +} + +void SweepContext::InitTriangulation() +{ + double xmax(points_[0]->x), xmin(points_[0]->x); + double ymax(points_[0]->y), ymin(points_[0]->y); + + // Calculate bounds. + for (unsigned int i = 0; i < points_.size(); i++) { + Point& p = *points_[i]; + if (p.x > xmax) + xmax = p.x; + if (p.x < xmin) + xmin = p.x; + if (p.y > ymax) + ymax = p.y; + if (p.y < ymin) + ymin = p.y; + } + + double dx = kAlpha * (xmax - xmin); + double dy = kAlpha * (ymax - ymin); + head_ = new Point(xmax + dx, ymin - dy); + tail_ = new Point(xmin - dx, ymin - dy); + + // Sort points along y-axis + std::sort(points_.begin(), points_.end(), cmp); + +} + +void SweepContext::InitEdges(std::vector polyline) +{ + int num_points = polyline.size(); + for (int i = 0; i < num_points; i++) { + int j = i < num_points - 1 ? i + 1 : 0; + edge_list.push_back(new Edge(*polyline[i], *polyline[j])); + } +} + +Point* SweepContext::GetPoint(const int& index) +{ + return points_[index]; +} + +void SweepContext::AddToMap(Triangle* triangle) +{ + map_.push_back(triangle); +} + +Node& SweepContext::LocateNode(Point& point) +{ + // TODO implement search tree + return *front_->LocateNode(point.x); +} + +void SweepContext::CreateAdvancingFront(std::vector nodes) +{ + + (void) nodes; + // Initial triangle + Triangle* triangle = new Triangle(*points_[0], *tail_, *head_); + + map_.push_back(triangle); + + af_head_ = new Node(*triangle->GetPoint(1), *triangle); + af_middle_ = new Node(*triangle->GetPoint(0), *triangle); + af_tail_ = new Node(*triangle->GetPoint(2)); + front_ = new AdvancingFront(*af_head_, *af_tail_); + + // TODO: More intuitive if head is middles next and not previous? + // so swap head and tail + af_head_->next = af_middle_; + af_middle_->next = af_tail_; + af_middle_->prev = af_head_; + af_tail_->prev = af_middle_; +} + +void SweepContext::RemoveNode(Node* node) +{ + delete node; +} + +void SweepContext::MapTriangleToNodes(Triangle& t) +{ + for (int i = 0; i < 3; i++) { + if (!t.GetNeighbor(i)) { + Node* n = front_->LocatePoint(t.PointCW(*t.GetPoint(i))); + if (n) + n->triangle = &t; + } + } +} + +void SweepContext::RemoveFromMap(Triangle* triangle) +{ + map_.remove(triangle); +} + +void SweepContext::MeshClean(Triangle& triangle) +{ + std::vector triangles; + triangles.push_back(&triangle); + + while(!triangles.empty()){ + Triangle *t = triangles.back(); + triangles.pop_back(); + + if (t != NULL && !t->IsInterior()) { + t->IsInterior(true); + triangles_.push_back(t); + for (int i = 0; i < 3; i++) { + if (!t->constrained_edge[i]) + triangles.push_back(t->GetNeighbor(i)); + } + } + } +} + +SweepContext::~SweepContext() +{ + + // Clean up memory + + delete head_; + delete tail_; + delete front_; + delete af_head_; + delete af_middle_; + delete af_tail_; + + typedef std::list type_list; + + for (type_list::iterator iter = map_.begin(); iter != map_.end(); ++iter) { + Triangle* ptr = *iter; + delete ptr; + } + + for (unsigned int i = 0; i < edge_list.size(); i++) { + delete edge_list[i]; + } + +} + +} diff --git a/src/3rdparty/poly2tri/sweep/sweep_context.h b/src/3rdparty/poly2tri/sweep/sweep_context.h new file mode 100644 index 0000000..c110a74 --- /dev/null +++ b/src/3rdparty/poly2tri/sweep/sweep_context.h @@ -0,0 +1,186 @@ +/* + * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SWEEP_CONTEXT_H +#define SWEEP_CONTEXT_H + +#include +#include +#include + +namespace p2t { + +// Initial triangle factor, seed triangle will extend 30% of +// PointSet width to both left and right. +const double kAlpha = 0.3; + +struct Point; +class Triangle; +struct Node; +struct Edge; +class AdvancingFront; + +class SweepContext { +public: + +/// Constructor +SweepContext(std::vector polyline); +/// Destructor +~SweepContext(); + +void set_head(Point* p1); + +Point* head(); + +void set_tail(Point* p1); + +Point* tail(); + +int point_count(); + +Node& LocateNode(Point& point); + +void RemoveNode(Node* node); + +void CreateAdvancingFront(std::vector nodes); + +/// Try to map a node to all sides of this triangle that don't have a neighbor +void MapTriangleToNodes(Triangle& t); + +void AddToMap(Triangle* triangle); + +Point* GetPoint(const int& index); + +Point* GetPoints(); + +void RemoveFromMap(Triangle* triangle); + +void AddHole(std::vector polyline); + +void AddPoint(Point* point); + +AdvancingFront* front(); + +void MeshClean(Triangle& triangle); + +std::vector GetTriangles(); +std::list GetMap(); + +std::vector edge_list; + +struct Basin { + Node* left_node; + Node* bottom_node; + Node* right_node; + double width; + bool left_highest; + + Basin() : left_node(NULL), bottom_node(NULL), right_node(NULL), width(0.0), left_highest(false) + { + } + + void Clear() + { + left_node = NULL; + bottom_node = NULL; + right_node = NULL; + width = 0.0; + left_highest = false; + } +}; + +struct EdgeEvent { + Edge* constrained_edge; + bool right; + + EdgeEvent() : constrained_edge(NULL), right(false) + { + } +}; + +Basin basin; +EdgeEvent edge_event; + +private: + +friend class Sweep; + +std::vector triangles_; +std::list map_; +std::vector points_; + +// Advancing front +AdvancingFront* front_; +// head point used with advancing front +Point* head_; +// tail point used with advancing front +Point* tail_; + +Node *af_head_, *af_middle_, *af_tail_; + +void InitTriangulation(); +void InitEdges(std::vector polyline); + +}; + +inline AdvancingFront* SweepContext::front() +{ + return front_; +} + +inline int SweepContext::point_count() +{ + return int(points_.size()); +} + +inline void SweepContext::set_head(Point* p1) +{ + head_ = p1; +} + +inline Point* SweepContext::head() +{ + return head_; +} + +inline void SweepContext::set_tail(Point* p1) +{ + tail_ = p1; +} + +inline Point* SweepContext::tail() +{ + return tail_; +} + +} + +#endif diff --git a/src/3rdparty/poly2tri_legal.qdoc b/src/3rdparty/poly2tri_legal.qdoc new file mode 100644 index 0000000..acbb264 --- /dev/null +++ b/src/3rdparty/poly2tri_legal.qdoc @@ -0,0 +1,37 @@ +/*! +\page legal-poly2tri.html +\title Poly2Tri Polygon Triangulation Library +\ingroup licensing + +\legalese +\code +Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors +http://code.google.com/p/poly2tri/ + +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Poly2Tri nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +\endcode +\endlegalese +*/ diff --git a/src/imports/imports.pro b/src/imports/imports.pro new file mode 100644 index 0000000..460fe15 --- /dev/null +++ b/src/imports/imports.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +qtHaveModule(positioning): SUBDIRS += positioning +qtHaveModule(location): SUBDIRS += location + diff --git a/src/imports/location/declarativeplaces/declarativeplaces.pri b/src/imports/location/declarativeplaces/declarativeplaces.pri new file mode 100644 index 0000000..b580ee4 --- /dev/null +++ b/src/imports/location/declarativeplaces/declarativeplaces.pri @@ -0,0 +1,50 @@ +INCLUDEPATH *= $$PWD +SOURCES += \ +#models + declarativeplaces/qdeclarativeplacecontentmodel.cpp \ + declarativeplaces/qdeclarativesupportedcategoriesmodel.cpp \ + declarativeplaces/qdeclarativesearchsuggestionmodel.cpp \ + declarativeplaces/qdeclarativesearchresultmodel.cpp \ + declarativeplaces/qdeclarativereviewmodel.cpp \ + declarativeplaces/qdeclarativeplaceimagemodel.cpp \ + declarativeplaces/qdeclarativeplaceeditorialmodel.cpp \ +#data + declarativeplaces/qdeclarativecontactdetail.cpp \ + declarativeplaces/qdeclarativecategory.cpp \ + declarativeplaces/qdeclarativeplace.cpp \ + declarativeplaces/qdeclarativeplaceattribute.cpp \ + declarativeplaces/qdeclarativeplaceicon.cpp \ + declarativeplaces/qdeclarativeplaceuser.cpp \ + declarativeplaces/qdeclarativeratings.cpp \ + declarativeplaces/qdeclarativesupplier.cpp \ + declarativeplaces/qdeclarativesearchmodelbase.cpp + +HEADERS += \ +#models + declarativeplaces/qdeclarativeplacecontentmodel.h \ + declarativeplaces/qdeclarativesupportedcategoriesmodel_p.h \ + declarativeplaces/qdeclarativesearchsuggestionmodel_p.h \ + declarativeplaces/qdeclarativesearchresultmodel_p.h \ + declarativeplaces/qdeclarativereviewmodel_p.h \ + declarativeplaces/qdeclarativeplaceimagemodel_p.h \ + declarativeplaces/qdeclarativeplaceeditorialmodel.h \ +#data + declarativeplaces/qdeclarativecontactdetail_p.h \ + declarativeplaces/qdeclarativecategory_p.h \ + declarativeplaces/qdeclarativeplace_p.h \ + declarativeplaces/qdeclarativeplaceattribute_p.h \ + declarativeplaces/qdeclarativeplaceicon_p.h \ + declarativeplaces/qdeclarativeplaceuser_p.h \ + declarativeplaces/qdeclarativeratings_p.h \ + declarativeplaces/qdeclarativesupplier_p.h \ + declarativeplaces/qdeclarativesearchmodelbase.h + + + + + + + + + + diff --git a/src/imports/location/declarativeplaces/qdeclarativecategory.cpp b/src/imports/location/declarativeplaces/qdeclarativecategory.cpp new file mode 100644 index 0000000..ae496c1 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativecategory.cpp @@ -0,0 +1,456 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativecategory_p.h" +#include "qdeclarativeplaceicon_p.h" +#include "qdeclarativegeoserviceprovider_p.h" +#include "error_messages.h" + +#include +#include +#include +#include + +QT_USE_NAMESPACE + +/*! + \qmltype Category + \instantiates QDeclarativeCategory + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + + \since Qt Location 5.5 + + \brief The Category type represents a category that a \l Place can be associated with. + + Categories are used to search for places based on the categories they are associated with. The + list of available categories can be obtained from the \l CategoryModel. The + \l PlaceSearchModel has a \l {PlaceSearchModel::categories}{categories} property that is used + to limit the search results to places with the specified categories. + + If the \l Plugin supports it, categories can be created or removed. To create a new category + construct a new Category object and set its properties, then invoke the \l save() method. + + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml Category + \dots 0 + \snippet declarative/places.qml Category save + + To remove a category ensure that the \l plugin and categoryId properties are set and call the + \l remove() method. + + \sa CategoryModel +*/ + +QDeclarativeCategory::QDeclarativeCategory(QObject *parent) +: QObject(parent), m_icon(0), m_plugin(0), m_reply(0), m_complete(false), m_status(Ready) +{ +} + +QDeclarativeCategory::QDeclarativeCategory(const QPlaceCategory &category, + QDeclarativeGeoServiceProvider *plugin, + QObject *parent) +: QObject(parent), m_category(category), m_icon(0), m_plugin(plugin), m_reply(0), + m_complete(false), m_status(Ready) +{ + setCategory(category); +} + +QDeclarativeCategory::~QDeclarativeCategory() {} + +// From QQmlParserStatus +void QDeclarativeCategory::componentComplete() +{ + // delayed instantiation of QObject based properties. + if (!m_icon) { + m_icon = new QDeclarativePlaceIcon(this); + m_icon->setPlugin(m_plugin); + } + + m_complete = true; +} + +/*! + \qmlproperty Plugin Category::plugin + + This property holds the location based service to which the category belongs. +*/ +void QDeclarativeCategory::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (m_plugin == plugin) + return; + + m_plugin = plugin; + if (m_complete) + emit pluginChanged(); + + if (m_icon && m_icon->parent() == this && !m_icon->plugin()) + m_icon->setPlugin(m_plugin); + + if (!m_plugin) + return; + + if (m_plugin->isAttached()) { + pluginReady(); + } else { + connect(m_plugin, SIGNAL(attached()), + this, SLOT(pluginReady())); + } +} + +QDeclarativeGeoServiceProvider *QDeclarativeCategory::plugin() const +{ + return m_plugin; +} + +/*! + \internal +*/ +void QDeclarativeCategory::pluginReady() +{ + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (!placeManager || serviceProvider->error() != QGeoServiceProvider::NoError) { + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_ERROR) + .arg(m_plugin->name()).arg(serviceProvider->errorString())); + return; + } +} + + +/*! + \qmlproperty QPlaceCategory Category::category + \keyword Category::category + + For details on how to use this property to interface between C++ and QML see + "\l {Category - QPlaceCategory} {Interfaces between C++ and QML Code}". +*/ +void QDeclarativeCategory::setCategory(const QPlaceCategory &category) +{ + QPlaceCategory previous = m_category; + m_category = category; + + if (category.name() != previous.name()) + emit nameChanged(); + + if (category.categoryId() != previous.categoryId()) + emit categoryIdChanged(); + + if (m_icon && m_icon->parent() == this) { + m_icon->setPlugin(m_plugin); + m_icon->setIcon(m_category.icon()); + } else if (!m_icon || m_icon->parent() != this) { + m_icon = new QDeclarativePlaceIcon(m_category.icon(), m_plugin, this); + emit iconChanged(); + } +} + +QPlaceCategory QDeclarativeCategory::category() +{ + m_category.setIcon(m_icon ? m_icon->icon() : QPlaceIcon()); + return m_category; +} + +/*! + \qmlproperty string Category::categoryId + + This property holds the identifier of the category. The categoryId is a string which uniquely + identifies this category within the categories \l plugin. +*/ +void QDeclarativeCategory::setCategoryId(const QString &id) +{ + if (m_category.categoryId() != id) { + m_category.setCategoryId(id); + emit categoryIdChanged(); + } +} + +QString QDeclarativeCategory::categoryId() const +{ + return m_category.categoryId(); +} + +/*! + \qmlproperty string Category::name + + This property holds string based name of the category. +*/ +void QDeclarativeCategory::setName(const QString &name) +{ + if (m_category.name() != name) { + m_category.setName(name); + emit nameChanged(); + } +} + +QString QDeclarativeCategory::name() const +{ + return m_category.name(); +} + +/*! + \qmlproperty enumeration Category::visibility + + This property holds the visibility of the category. It can be one of: + + \table + \row + \li Category.UnspecifiedVisibility + \li The visibility of the category is unspecified. If saving a category, the + plugin will automatically set a default visibility to the category saved in the backend. + This default is dependent on the plugin implementation. + \row + \li Category.DeviceVisibility + \li The category is limited to the current device. The category will not be transferred + off of the device. + \row + \li Category.PrivateVisibility + \li The category is private to the current user. The category may be transferred to an + online service but is only ever visible to the current user. + \row + \li Category.PublicVisibility + \li The category is public. + \endtable + + Note that visibility does not affect how \l{Place}s associated with + the category are displayed in the user-interface of an application + on the device. Instead, it defines the sharing semantics of the + category. +*/ +QDeclarativeCategory::Visibility QDeclarativeCategory::visibility() const +{ + return static_cast(m_category.visibility()); +} + +void QDeclarativeCategory::setVisibility(Visibility visibility) +{ + if (static_cast(m_category.visibility()) == visibility) + return; + + m_category.setVisibility(static_cast(visibility)); + emit visibilityChanged(); +} + +/*! + \qmlproperty PlaceIcon Category::icon + + This property holds the image source associated with the category. To display the icon you can use + the \l Image type. +*/ +QDeclarativePlaceIcon *QDeclarativeCategory::icon() const +{ + return m_icon; +} + +void QDeclarativeCategory::setIcon(QDeclarativePlaceIcon *icon) +{ + if (m_icon == icon) + return; + + if (m_icon && m_icon->parent() == this) + delete m_icon; + + m_icon = icon; + emit iconChanged(); +} + +/*! + \qmlmethod string Category::errorString() + + Returns a string description of the error of the last operation. + If the last operation completed successfully then the string is empty. +*/ +QString QDeclarativeCategory::errorString() const +{ + return m_errorString; +} + +void QDeclarativeCategory::setStatus(Status status, const QString &errorString) +{ + Status originalStatus = m_status; + m_status = status; + m_errorString = errorString; + + if (originalStatus != m_status) + emit statusChanged(); +} + +/*! + \qmlproperty enumeration Category::status + + This property holds the status of the category. It can be one of: + + \table + \row + \li Category.Ready + \li No error occurred during the last operation, further operations may be performed on + the category. + \row + \li Category.Saving + \li The category is currently being saved, no other operations may be performed until the + current operation completes. + \row + \li Category.Removing + \li The category is currently being removed, no other operations can be performed until + the current operation completes. + \row + \li Category.Error + \li An error occurred during the last operation, further operations can still be + performed on the category. + \endtable +*/ +QDeclarativeCategory::Status QDeclarativeCategory::status() const +{ + return m_status; +} + +/*! + \qmlmethod void Category::save() + + This method saves the category to the backend service. +*/ +void QDeclarativeCategory::save(const QString &parentId) +{ + QPlaceManager *placeManager = manager(); + if (!placeManager) + return; + + m_reply = placeManager->saveCategory(category(), parentId); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); + setStatus(QDeclarativeCategory::Saving); +} + +/*! + \qmlmethod void Category::remove() + + This method permanently removes the category from the backend service. +*/ +void QDeclarativeCategory::remove() +{ + QPlaceManager *placeManager = manager(); + if (!placeManager) + return; + + m_reply = placeManager->removeCategory(m_category.categoryId()); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); + setStatus(QDeclarativeCategory::Removing); +} + +/*! + \internal +*/ +void QDeclarativeCategory::replyFinished() +{ + if (!m_reply) + return; + + if (m_reply->error() == QPlaceReply::NoError) { + switch (m_reply->type()) { + case (QPlaceReply::IdReply) : { + QPlaceIdReply *idReply = qobject_cast(m_reply); + + switch (idReply->operationType()) { + case QPlaceIdReply::SaveCategory: + setCategoryId(idReply->id()); + break; + case QPlaceIdReply::RemoveCategory: + setCategoryId(QString()); + break; + default: + //Other operation types shouldn't ever be received. + break; + } + break; + } + default: + //other types of replies shouldn't ever be received. + break; + } + + m_errorString.clear(); + + m_reply->deleteLater(); + m_reply = 0; + + setStatus(QDeclarativeCategory::Ready); + } else { + QString errorString = m_reply->errorString(); + + m_reply->deleteLater(); + m_reply = 0; + + setStatus(QDeclarativeCategory::Error, errorString); + } +} + +/*! + \internal + Helper function to return the manager, this manager is intended to be used to perform the next + operation. Sets status to Error and an appropriate m_errorString if the manager cannot be + obtained. +*/ +QPlaceManager *QDeclarativeCategory::manager() +{ + if (m_status != QDeclarativeCategory::Ready && m_status != QDeclarativeCategory::Error) + return 0; + + if (m_reply) { + m_reply->abort(); + m_reply->deleteLater(); + m_reply = 0; + } + + if (!m_plugin) { + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_PROPERTY_NOT_SET)); + return 0; + } + + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (!serviceProvider) { + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_NOT_VALID)); + return 0; + } + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (!placeManager) { + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_ERROR) + .arg(m_plugin->name()).arg(serviceProvider->errorString())); + return 0; + } + + return placeManager; +} diff --git a/src/imports/location/declarativeplaces/qdeclarativecategory_p.h b/src/imports/location/declarativeplaces/qdeclarativecategory_p.h new file mode 100644 index 0000000..63b255b --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativecategory_p.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVECATEGORY_P_H +#define QDECLARATIVECATEGORY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include + +#include "qdeclarativegeoserviceprovider_p.h" + +QT_BEGIN_NAMESPACE + +class QDeclarativePlaceIcon; +class QPlaceReply; +class QPlaceManager; + +class QDeclarativeCategory : public QObject, public QQmlParserStatus +{ + Q_OBJECT + + Q_ENUMS(Status Visibility) + + + Q_PROPERTY(QPlaceCategory category READ category WRITE setCategory) + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(QString categoryId READ categoryId WRITE setCategoryId NOTIFY categoryIdChanged) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(Visibility visibility READ visibility WRITE setVisibility NOTIFY visibilityChanged) + Q_PROPERTY(QDeclarativePlaceIcon *icon READ icon WRITE setIcon NOTIFY iconChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + + Q_INTERFACES(QQmlParserStatus) + +public: + explicit QDeclarativeCategory(QObject *parent = 0); + QDeclarativeCategory(const QPlaceCategory &category, QDeclarativeGeoServiceProvider *plugin, QObject *parent = 0); + ~QDeclarativeCategory(); + + enum Visibility { + UnspecifiedVisibility = QLocation::UnspecifiedVisibility, + DeviceVisibility = QLocation::DeviceVisibility, + PrivateVisibility = QLocation::PrivateVisibility, + PublicVisibility = QLocation::PublicVisibility + }; + enum Status {Ready, Saving, Removing, Error}; + + //From QQmlParserStatus + virtual void classBegin() {} + virtual void componentComplete(); + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + + QPlaceCategory category(); + void setCategory(const QPlaceCategory &category); + + QString categoryId() const; + void setCategoryId(const QString &catID); + QString name() const; + void setName(const QString &name); + + Visibility visibility() const; + void setVisibility(Visibility visibility); + + QDeclarativePlaceIcon *icon() const; + void setIcon(QDeclarativePlaceIcon *icon); + + Q_INVOKABLE QString errorString() const; + + Status status() const; + void setStatus(Status status, const QString &errorString = QString()); + + Q_INVOKABLE void save(const QString &parentId = QString()); + Q_INVOKABLE void remove(); + +Q_SIGNALS: + void pluginChanged(); + void categoryIdChanged(); + void nameChanged(); + void visibilityChanged(); + void iconChanged(); + void statusChanged(); + +private Q_SLOTS: + void replyFinished(); + void pluginReady(); + +private: + QPlaceManager *manager(); + + QPlaceCategory m_category; + QDeclarativePlaceIcon *m_icon; + QDeclarativeGeoServiceProvider *m_plugin; + QPlaceReply *m_reply; + bool m_complete; + Status m_status; + QString m_errorString; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeCategory) + +#endif // QDECLARATIVECATEGORY_P_H diff --git a/src/imports/location/declarativeplaces/qdeclarativecontactdetail.cpp b/src/imports/location/declarativeplaces/qdeclarativecontactdetail.cpp new file mode 100644 index 0000000..c16c201 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativecontactdetail.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativecontactdetail_p.h" + +/*! + \qmltype ContactDetails + \instantiates QDeclarativeContactDetails + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + \since Qt Location 5.5 + + \brief The ContactDetails type holds contact details for a \l Place. + + The ContactDetails type is a map of \l {QtLocation::ContactDetail}{ContactDetail} objects. + To access contact details in the map use the \l keys() method to get the list of keys stored in + the map and then use the \c {[]} operator to access the + \l {QtLocation::ContactDetail}{ContactDetail} items. + + The following keys are defined in the API. \l Plugin implementations are free to define + additional keys. + + \list + \li phone + \li fax + \li email + \li website + \endlist + + ContactDetails instances are only ever used in the context of \l {Place}{Places}. It is not possible + to create a ContactDetails instance directly or re-assign ContactDetails instances to \l {Place}{Places}. + Modification of ContactDetails can only be accomplished via Javascript. + + \section1 Examples + + The following example shows how to access all \l {QtLocation::ContactDetail}{ContactDetails} + and print them to the console: + + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml ContactDetails read + + The returned list of contact details is an \l {QObjectList-based model}{object list} and so can be used directly as a data model. For example, the + following demonstrates how to display a list of contact phone numbers in a list view: + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml ContactDetails phoneList + + The following example demonstrates how to assign a single phone number to a place in JavaScript: + \snippet declarative/places.qml ContactDetails write single + + The following demonstrates how to assign multiple phone numbers to a place in JavaScript: + \snippet declarative/places.qml ContactDetails write multiple +*/ + +/*! + \qmlmethod variant ContactDetails::keys() + + Returns an array of contact detail keys currently stored in the map. +*/ +QDeclarativeContactDetails::QDeclarativeContactDetails(QObject *parent) + : QQmlPropertyMap(parent) +{ +} + +QVariant QDeclarativeContactDetails::updateValue(const QString &, const QVariant &input) +{ + if (input.userType() == QMetaType::QObjectStar) { + QDeclarativeContactDetail *detail = + qobject_cast(input.value()); + if (detail) { + QVariantList varList; + varList.append(input); + return varList; + } + } + + return input; +} + +/*! + \qmltype ContactDetail + \instantiates QDeclarativeContactDetail + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + \since Qt Location 5.5 + + \brief The ContactDetail type holds a contact detail such as a phone number or a website + address. + + The ContactDetail provides a single detail on how one could contact a \l Place. The + ContactDetail consists of a \l label, which is a localized string describing the contact + method, and a \l value representing the actual contact detail. + + \section1 Examples + + The following example demonstrates how to assign a single phone number to a place in JavaScript: + \snippet declarative/places.qml ContactDetails write single + + The following demonstrates how to assign multiple phone numbers to a place in JavaScript: + \snippet declarative/places.qml ContactDetails write multiple + + Note, due to limitations of the QQmlPropertyMap, it is not possible + to declaratively specify the contact details in QML, it can only be accomplished + via JavaScript. +*/ +QDeclarativeContactDetail::QDeclarativeContactDetail(QObject *parent) + : QObject(parent) +{ +} + +QDeclarativeContactDetail::QDeclarativeContactDetail(const QPlaceContactDetail &src, QObject *parent) + : QObject(parent), m_contactDetail(src) +{ +} + +QDeclarativeContactDetail::~QDeclarativeContactDetail() +{ +} + +/*! + \qmlproperty QPlaceContactDetail QtLocation::ContactDetail::contactDetail + + For details on how to use this property to interface between C++ and QML see + "\l {ContactDetail - QDeclarativeContactDetail} {Interfaces between C++ and QML Code}". +*/ +void QDeclarativeContactDetail::setContactDetail(const QPlaceContactDetail &src) +{ + QPlaceContactDetail prevContactDetail = m_contactDetail; + m_contactDetail = src; + + if (m_contactDetail.label() != prevContactDetail.label()) + emit labelChanged(); + if (m_contactDetail.value() != prevContactDetail.value()) + emit valueChanged(); +} + +QPlaceContactDetail QDeclarativeContactDetail::contactDetail() const +{ + return m_contactDetail; +} + +/*! + \qmlproperty string QtLocation::ContactDetail::label + + This property holds a label describing the contact detail. + + The label can potentially be localized. The language is dependent on the entity that sets it, + typically this is the \l {Plugin}. The \l {Plugin::locales} property defines + what language is used. +*/ +QString QDeclarativeContactDetail::label() const +{ + return m_contactDetail.label(); +} + +void QDeclarativeContactDetail::setLabel(const QString &label) +{ + if (m_contactDetail.label() != label) { + m_contactDetail.setLabel(label); + emit labelChanged(); + } +} + +/*! + \qmlproperty string QtLocation::ContactDetail::value + + This property holds the value of the contact detail which may be a phone number, an email + address, a website url and so on. +*/ +QString QDeclarativeContactDetail::value() const +{ + return m_contactDetail.value(); +} + +void QDeclarativeContactDetail::setValue(const QString &value) +{ + if (m_contactDetail.value() != value) { + m_contactDetail.setValue(value); + emit valueChanged(); + } +} diff --git a/src/imports/location/declarativeplaces/qdeclarativecontactdetail_p.h b/src/imports/location/declarativeplaces/qdeclarativecontactdetail_p.h new file mode 100644 index 0000000..9d404de --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativecontactdetail_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVECONTACTDETAIL_P_H +#define QDECLARATIVECONTACTDETAIL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeContactDetails : public QQmlPropertyMap +{ + Q_OBJECT + +public: + explicit QDeclarativeContactDetails(QObject *parent = 0); + virtual QVariant updateValue(const QString &key, const QVariant &input); +}; + +class QDeclarativeContactDetail : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPlaceContactDetail contactDetail READ contactDetail WRITE setContactDetail) + Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) + Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged) + +public: + explicit QDeclarativeContactDetail(QObject *parent = 0); + explicit QDeclarativeContactDetail(const QPlaceContactDetail &src, QObject *parent = 0); + ~QDeclarativeContactDetail(); + + QPlaceContactDetail contactDetail() const; + void setContactDetail(const QPlaceContactDetail &contactDetail); + + QString label() const; + void setLabel(const QString &label); + + QString value() const; + void setValue(const QString &value); + +Q_SIGNALS: + void labelChanged(); + void valueChanged(); + +private: + QPlaceContactDetail m_contactDetail; + +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeContactDetail) + +#endif diff --git a/src/imports/location/declarativeplaces/qdeclarativeperiod_p.h b/src/imports/location/declarativeplaces/qdeclarativeperiod_p.h new file mode 100644 index 0000000..3ded010 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeperiod_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPERIOD_P_H +#define QDECLARATIVEPERIOD_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativePeriod : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QDate startDate READ startDate WRITE setStartDate NOTIFY startDateChanged) + Q_PROPERTY(QTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) + Q_PROPERTY(QDate endDate READ endDate WRITE setEndDate NOTIFY endDateChanged) + Q_PROPERTY(QTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) + +public: + explicit QDeclarativePeriod(QObject *parent = 0); + explicit QDeclarativePeriod(const QPlacePeriod &period, QObject *parent = 0); + ~QDeclarativePeriod(); + + QPlacePeriod period() const; + void setPeriod(const QPlacePeriod &period); + + QDate startDate() const; + void setStartDate(const QDate &data); + QTime startTime() const; + void setStartTime(const QTime &data); + QDate endDate() const; + void setEndDate(const QDate &data); + QTime endTime() const; + void setEndTime(const QTime &data); + +Q_SIGNALS: + void startDateChanged(); + void startTimeChanged(); + void endDateChanged(); + void endTimeChanged(); + +private: + QPlacePeriod m_period; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QT_PREPEND_NAMESPACE(QDeclarativePeriod)); + +#endif // QDECLARATIVEPERIOD_P_H diff --git a/src/imports/location/declarativeplaces/qdeclarativeplace.cpp b/src/imports/location/declarativeplaces/qdeclarativeplace.cpp new file mode 100644 index 0000000..69ded2d --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplace.cpp @@ -0,0 +1,1227 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativeplace_p.h" +#include "qdeclarativecontactdetail_p.h" +#include "qdeclarativegeoserviceprovider_p.h" +#include "qdeclarativeplaceattribute_p.h" +#include "qdeclarativeplaceicon_p.h" +#include "error_messages.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +/*! + \qmltype Place + \instantiates QDeclarativePlace + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + \since Qt Location 5.5 + + \brief The Place type represents a location that is a position of interest. + + The Place type represents a physical location with additional metadata describing that + location. Contrasted with \l Location, \l Address, and + \l {coordinate} type which are used to describe where a location is. + The basic properties of a Place are its \l name and \l location. + + Place objects are typically obtained from a search model and will generally only have their + basic properties set. The \l detailsFetched property can be used to test if further property + values need to be fetched from the \l Plugin. This can be done by invoking the \l getDetails() + method. Progress of the fetching operation can be monitored with the \l status property, which + will be set to Place.Fetching when the details are being fetched. + + The Place type has many properties holding information about the location. Details on how to + contact the place are available from the \l contactDetails property. Convenience properties + for obtaining the primary \l {primaryPhone}{phone}, \l {primaryFax}{fax}, + \l {primaryEmail}{email} and \l {primaryWebsite}{website} are also available. + + Each place is assigned zero or more \l categories. Categories are typically used when + searching for a particular kind of place, such as a restaurant or hotel. Some places have a + \l ratings object, which gives an indication of the quality of the place. + + Place metadata is provided by a \l supplier who may require that an \l attribution message be + displayed to the user when the place details are viewed. + + Places have an associated \l icon which can be used to represent a place on a map or to + decorate a delegate in a view. + + Places may have additional rich content associated with them. The currently supported rich + content include editorial descriptions, reviews and images. These are exposed as a set of + models for retrieving the content. Editorial descriptions of the place are available from the + \l editorialModel property. Reviews of the place are available from the \l reviewModel + property. A gallery of pictures of the place can be accessed using the \l imageModel property. + + Places may have additional attributes which are not covered in the formal API. The + \l extendedAttributes property provides access to these. The type of extended attributes + available is specific to each \l Plugin. + + A Place is almost always tied to a \l plugin. The \l plugin property must be set before it is + possible to call \l save(), \l remove() or \l getDetails(). The \l reviewModel, \l imageModel + and \l editorialModel are only valid then the \l plugin property is set. + + \section2 Saving a Place + + If the \l Plugin supports it, the Place type can be used to save a place. First create a new + Place and set its properties: + + \snippet declarative/places.qml Place savePlace def + + Then invoke the \l save() method: + + \snippet declarative/places.qml Place savePlace + + The \l status property will change to Place.Saving and then to Place.Ready if the save was + successful or to Place.Error if an error occurs. + + If the \l placeId property is set, the backend will update an existing place otherwise it will + create a new place. On success the \l placeId property will be updated with the identifier of the newly + saved place. + + \section3 Caveats + \input place-caveats.qdocinc + + \section3 Saving Between Plugins + When saving places between plugins, there are a few things to be aware of. + Some fields of a place such as the id, categories and icons are plugin specific entities. For example + the categories in one manager may not be recognised in another. + Therefore trying to save a place directly from one plugin to another is not possible. + + It is generally recommended that saving across plugins be handled as saving \l {Favorites}{favorites} + as explained in the Favorites section. However there is another approach which is to create a new place, + set its (destination) plugin and then use the \l copyFrom() method to copy the details of the original place. + Using \l copyFrom() only copies data that is supported by the destination plugin, + plugin specific data such as the place identifier is not copied over. Once the copy is done, + the place is in a suitable state to be saved. + + The following snippet provides an example of saving a place to a different plugin + using the \l copyFrom method: + + \snippet declarative/places.qml Place save to different plugin + + \section2 Removing a Place + + To remove a place, ensure that a Place object with a valid \l placeId property exists and call + its \l remove() method. The \l status property will change to Place.Removing and then to + Place.Ready if the save was successful or to Place.Error if an error occurs. + + \section2 Favorites + The Places API supports the concept of favorites. Favorites are generally implemented + by using two plugins, the first plugin is typically a read-only source of places (origin plugin) and a second + read/write plugin (destination plugin) is used to store places from the origin as favorites. + + Each Place has a favorite property which is intended to contain the corresponding place + from the destination plugin (the place itself is sourced from the origin plugin). Because both the original + place and favorite instances are available, the developer can choose which + properties to show to the user. For example the favorite may have a modified name which should + be displayed rather than the original name. + + \snippet declarative/places.qml Place favorite + + The following demonstrates how to save a new favorite instance. A call is made + to create/initialize the favorite instance and then the instance is saved. + + \snippet declarative/places.qml Place saveFavorite + + The following demonstrates favorite removal: + + \snippet declarative/places.qml Place removeFavorite 1 + \dots + \snippet declarative/places.qml Place removeFavorite 2 + + The PlaceSearchModel has a favoritesPlugin property. If the property is set, any places found + during a search are checked against the favoritesPlugin to see if there is a corresponding + favorite place. If so, the favorite property of the Place is set, otherwise the favorite + property is remains null. + + \sa PlaceSearchModel +*/ + +QDeclarativePlace::QDeclarativePlace(QObject *parent) +: QObject(parent), m_location(0), m_ratings(0), m_supplier(0), m_icon(0), + m_reviewModel(0), m_imageModel(0), m_editorialModel(0), + m_extendedAttributes(new QQmlPropertyMap(this)), + m_contactDetails(new QDeclarativeContactDetails(this)), m_reply(0), m_plugin(0), + m_complete(false), m_favorite(0), m_status(QDeclarativePlace::Ready) +{ + connect(m_contactDetails, SIGNAL(valueChanged(QString,QVariant)), + this, SLOT(contactsModified(QString,QVariant))); + + setPlace(QPlace()); +} + +QDeclarativePlace::QDeclarativePlace(const QPlace &src, QDeclarativeGeoServiceProvider *plugin, QObject *parent) +: QObject(parent), m_location(0), m_ratings(0), m_supplier(0), m_icon(0), + m_reviewModel(0), m_imageModel(0), m_editorialModel(0), + m_extendedAttributes(new QQmlPropertyMap(this)), + m_contactDetails(new QDeclarativeContactDetails(this)), m_reply(0), m_plugin(plugin), + m_complete(false), m_favorite(0), m_status(QDeclarativePlace::Ready) +{ + Q_ASSERT(plugin); + + connect(m_contactDetails, SIGNAL(valueChanged(QString,QVariant)), + this, SLOT(contactsModified(QString,QVariant))); + + setPlace(src); +} + +QDeclarativePlace::~QDeclarativePlace() +{ +} + +// From QQmlParserStatus +void QDeclarativePlace::componentComplete() +{ + m_complete = true; +} + +/*! + \qmlproperty Plugin Place::plugin + + This property holds the \l Plugin that provided this place which can be used to retrieve more information about the service. +*/ +void QDeclarativePlace::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (m_plugin == plugin) + return; + + m_plugin = plugin; + if (m_complete) + emit pluginChanged(); + + if (m_plugin->isAttached()) { + pluginReady(); + } else { + connect(m_plugin, SIGNAL(attached()), + this, SLOT(pluginReady())); + } +} + +void QDeclarativePlace::pluginReady() +{ + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (!placeManager || serviceProvider->error() != QGeoServiceProvider::NoError) { + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_ERROR) + .arg(m_plugin->name()).arg(serviceProvider->errorString())); + return; + } +} + +QDeclarativeGeoServiceProvider *QDeclarativePlace::plugin() const +{ + return m_plugin; +} + +/*! + \qmlproperty ReviewModel Place::reviewModel + + This property holds a model which can be used to retrieve reviews about the place. +*/ +QDeclarativeReviewModel *QDeclarativePlace::reviewModel() +{ + if (!m_reviewModel) { + m_reviewModel = new QDeclarativeReviewModel(this); + m_reviewModel->setPlace(this); + } + + return m_reviewModel; +} + +/*! + \qmlproperty ImageModel Place::imageModel + + This property holds a model which can be used to retrieve images of the place. +*/ +QDeclarativePlaceImageModel *QDeclarativePlace::imageModel() +{ + if (!m_imageModel) { + m_imageModel = new QDeclarativePlaceImageModel(this); + m_imageModel->setPlace(this); + } + + return m_imageModel; +} + +/*! + \qmlproperty EditorialModel Place::editorialModel + + This property holds a model which can be used to retrieve editorial descriptions of the place. +*/ +QDeclarativePlaceEditorialModel *QDeclarativePlace::editorialModel() +{ + if (!m_editorialModel) { + m_editorialModel = new QDeclarativePlaceEditorialModel(this); + m_editorialModel->setPlace(this); + } + + return m_editorialModel; +} + +/*! + \qmlproperty QPlace Place::place + + For details on how to use this property to interface between C++ and QML see + "\l {Place - QPlace} {Interfaces between C++ and QML Code}". +*/ +void QDeclarativePlace::setPlace(const QPlace &src) +{ + QPlace previous = m_src; + m_src = src; + + if (previous.categories() != m_src.categories()) { + synchronizeCategories(); + emit categoriesChanged(); + } + + if (m_location && m_location->parent() == this) { + m_location->setLocation(m_src.location()); + } else if (!m_location || m_location->parent() != this) { + m_location = new QDeclarativeGeoLocation(m_src.location(), this); + emit locationChanged(); + } + + if (m_ratings && m_ratings->parent() == this) { + m_ratings->setRatings(m_src.ratings()); + } else if (!m_ratings || m_ratings->parent() != this) { + m_ratings = new QDeclarativeRatings(m_src.ratings(), this); + emit ratingsChanged(); + } + + if (m_supplier && m_supplier->parent() == this) { + m_supplier->setSupplier(m_src.supplier(), m_plugin); + } else if (!m_supplier || m_supplier->parent() != this) { + m_supplier = new QDeclarativeSupplier(m_src.supplier(), m_plugin, this); + emit supplierChanged(); + } + + if (m_icon && m_icon->parent() == this) { + m_icon->setPlugin(m_plugin); + m_icon->setIcon(m_src.icon()); + } else if (!m_icon || m_icon->parent() != this) { + m_icon = new QDeclarativePlaceIcon(m_src.icon(), m_plugin, this); + emit iconChanged(); + } + + if (previous.name() != m_src.name()) { + emit nameChanged(); + } + if (previous.placeId() != m_src.placeId()) { + emit placeIdChanged(); + } + if (previous.attribution() != m_src.attribution()) { + emit attributionChanged(); + } + if (previous.detailsFetched() != m_src.detailsFetched()) { + emit detailsFetchedChanged(); + } + if (previous.primaryPhone() != m_src.primaryPhone()) { + emit primaryPhoneChanged(); + } + if (previous.primaryFax() != m_src.primaryFax()) { + emit primaryFaxChanged(); + } + if (previous.primaryEmail() != m_src.primaryEmail()) { + emit primaryEmailChanged(); + } + if (previous.primaryWebsite() != m_src.primaryWebsite()) { + emit primaryWebsiteChanged(); + } + + if (m_reviewModel && m_src.totalContentCount(QPlaceContent::ReviewType) >= 0) { + m_reviewModel->initializeCollection(m_src.totalContentCount(QPlaceContent::ReviewType), + m_src.content(QPlaceContent::ReviewType)); + } + if (m_imageModel && m_src.totalContentCount(QPlaceContent::ImageType) >= 0) { + m_imageModel->initializeCollection(m_src.totalContentCount(QPlaceContent::ImageType), + m_src.content(QPlaceContent::ImageType)); + } + if (m_editorialModel && m_src.totalContentCount(QPlaceContent::EditorialType) >= 0) { + m_editorialModel->initializeCollection(m_src.totalContentCount(QPlaceContent::EditorialType), + m_src.content(QPlaceContent::EditorialType)); + } + + synchronizeExtendedAttributes(); + synchronizeContacts(); +} + +QPlace QDeclarativePlace::place() +{ + // The following properties are not stored in m_src but instead stored in QDeclarative* objects + + QPlace result = m_src; + + // Categories + QList categories; + foreach (QDeclarativeCategory *value, m_categories) + categories.append(value->category()); + + result.setCategories(categories); + + // Location + result.setLocation(m_location ? m_location->location() : QGeoLocation()); + + // Rating + result.setRatings(m_ratings ? m_ratings->ratings() : QPlaceRatings()); + + // Supplier + result.setSupplier(m_supplier ? m_supplier->supplier() : QPlaceSupplier()); + + // Icon + result.setIcon(m_icon ? m_icon->icon() : QPlaceIcon()); + + //contact details + QList cppDetails; + foreach (const QString &key, m_contactDetails->keys()) { + cppDetails.clear(); + if (m_contactDetails->value(key).type() == QVariant::List) { + QVariantList detailsVarList = m_contactDetails->value(key).toList(); + foreach (const QVariant &detailVar, detailsVarList) { + QDeclarativeContactDetail *detail = qobject_cast(detailVar.value()); + if (detail) + cppDetails.append(detail->contactDetail()); + } + } else { + QDeclarativeContactDetail *detail = qobject_cast(m_contactDetails->value(key).value()); + if (detail) + cppDetails.append(detail->contactDetail()); + } + result.setContactDetails(key, cppDetails); + } + + return result; +} + +/*! + \qmlproperty QtPositioning::Location Place::location + + This property holds the location of the place which can be used to retrieve the coordinate, + address and the bounding box. +*/ +void QDeclarativePlace::setLocation(QDeclarativeGeoLocation *location) +{ + if (m_location == location) + return; + + if (m_location && m_location->parent() == this) + delete m_location; + + m_location = location; + emit locationChanged(); +} + +QDeclarativeGeoLocation *QDeclarativePlace::location() +{ + return m_location; +} + +/*! + \qmlproperty Ratings Place::ratings + + This property holds ratings of the place. The ratings provide an indication of the quality of a + place. +*/ +void QDeclarativePlace::setRatings(QDeclarativeRatings *rating) +{ + if (m_ratings == rating) + return; + + if (m_ratings && m_ratings->parent() == this) + delete m_ratings; + + m_ratings = rating; + emit ratingsChanged(); +} + +QDeclarativeRatings *QDeclarativePlace::ratings() +{ + + return m_ratings; +} + +/*! + \qmlproperty Supplier Place::supplier + + This property holds the supplier of the place data. + The supplier is typically a business or organization that collected the data about the place. +*/ +void QDeclarativePlace::setSupplier(QDeclarativeSupplier *supplier) +{ + if (m_supplier == supplier) + return; + + if (m_supplier && m_supplier->parent() == this) + delete m_supplier; + + m_supplier = supplier; + emit supplierChanged(); +} + +QDeclarativeSupplier *QDeclarativePlace::supplier() const +{ + return m_supplier; +} + +/*! + \qmlproperty Icon Place::icon + + This property holds a graphical icon which can be used to represent the place. +*/ +QDeclarativePlaceIcon *QDeclarativePlace::icon() const +{ + return m_icon; +} + +void QDeclarativePlace::setIcon(QDeclarativePlaceIcon *icon) +{ + if (m_icon == icon) + return; + + if (m_icon && m_icon->parent() == this) + delete m_icon; + + m_icon = icon; + emit iconChanged(); +} + +/*! + \qmlproperty string Place::name + + This property holds the name of the place which can be used to represent the place. +*/ +void QDeclarativePlace::setName(const QString &name) +{ + if (m_src.name() != name) { + m_src.setName(name); + emit nameChanged(); + } +} + +QString QDeclarativePlace::name() const +{ + return m_src.name(); +} + +/*! + \qmlproperty string Place::placeId + + This property holds the unique identifier of the place. The place identifier is only meaningful to the + \l Plugin that generated it and is not transferable between \l {Plugin}{Plugins}. The place id + is not guaranteed to be universally unique, but unique within the \l Plugin that generated it. + + If only the place identifier is known, all other place data can fetched from the \l Plugin. + + \snippet declarative/places.qml Place placeId +*/ +void QDeclarativePlace::setPlaceId(const QString &placeId) +{ + if (m_src.placeId() != placeId) { + m_src.setPlaceId(placeId); + emit placeIdChanged(); + } +} + +QString QDeclarativePlace::placeId() const +{ + return m_src.placeId(); +} + +/*! + \qmlproperty string Place::attribution + + This property holds a rich text attribution string for the place. + Some providers may require that the attribution be shown to the user + whenever a place is displayed. The contents of this property should + be shown to the user if it is not empty. +*/ +void QDeclarativePlace::setAttribution(const QString &attribution) +{ + if (m_src.attribution() != attribution) { + m_src.setAttribution(attribution); + emit attributionChanged(); + } +} + +QString QDeclarativePlace::attribution() const +{ + return m_src.attribution(); +} + +/*! + \qmlproperty bool Place::detailsFetched + + This property indicates whether the details of the place have been fetched. If this property + is false, the place details have not yet been fetched. Fetching can be done by invoking the + \l getDetails() method. + + \sa getDetails() +*/ +bool QDeclarativePlace::detailsFetched() const +{ + return m_src.detailsFetched(); +} + +/*! + \qmlproperty enumeration Place::status + + This property holds the status of the place. It can be one of: + + \table + \row + \li Place.Ready + \li No error occurred during the last operation, further operations may be performed on + the place. + \row + \li Place.Saving + \li The place is currently being saved, no other operation may be performed until + complete. + \row + \li Place.Fetching + \li The place details are currently being fetched, no other operations may be performed + until complete. + \row + \li Place.Removing + \li The place is currently being removed, no other operations can be performed until + complete. + \row + \li Place.Error + \li An error occurred during the last operation, further operations can still be + performed on the place. + \endtable + + The status of a place can be checked by connecting the status property + to a handler function, and then have the handler function process the change + in status. + + \snippet declarative/places.qml Place checkStatus + \dots + \snippet declarative/places.qml Place checkStatus handler + +*/ +void QDeclarativePlace::setStatus(Status status, const QString &errorString) +{ + Status originalStatus = m_status; + m_status = status; + m_errorString = errorString; + + if (originalStatus != m_status) + emit statusChanged(); +} + +QDeclarativePlace::Status QDeclarativePlace::status() const +{ + return m_status; +} + +/*! + \internal +*/ +void QDeclarativePlace::finished() +{ + if (!m_reply) + return; + + if (m_reply->error() == QPlaceReply::NoError) { + switch (m_reply->type()) { + case (QPlaceReply::IdReply) : { + QPlaceIdReply *idReply = qobject_cast(m_reply); + + switch (idReply->operationType()) { + case QPlaceIdReply::SavePlace: + setPlaceId(idReply->id()); + break; + case QPlaceIdReply::RemovePlace: + break; + default: + //Other operation types shouldn't ever be received. + break; + } + break; + } + case (QPlaceReply::DetailsReply): { + QPlaceDetailsReply *detailsReply = qobject_cast(m_reply); + setPlace(detailsReply->place()); + break; + } + default: + //other types of replies shouldn't ever be received. + break; + } + + m_errorString.clear(); + + m_reply->deleteLater(); + m_reply = 0; + + setStatus(QDeclarativePlace::Ready); + } else { + QString errorString = m_reply->errorString(); + + m_reply->deleteLater(); + m_reply = 0; + + setStatus(QDeclarativePlace::Error, errorString); + } +} + +/*! + \internal +*/ +void QDeclarativePlace::contactsModified(const QString &key, const QVariant &) +{ + primarySignalsEmission(key); +} + +/*! + \internal +*/ +void QDeclarativePlace::cleanupDeletedCategories() +{ + foreach (QDeclarativeCategory * category, m_categoriesToBeDeleted) { + if (category->parent() == this) + delete category; + } + m_categoriesToBeDeleted.clear(); +} + +/*! + \qmlmethod void Place::getDetails() + + This method starts fetching place details. + + The \l status property will change to Place.Fetching while the fetch is in progress. On + success the object's properties will be updated, \l status will be set to Place.Ready and + \l detailsFetched will be set to true. On error \l status will be set to Place.Error. The + \l errorString() method can be used to get the details of the error. +*/ +void QDeclarativePlace::getDetails() +{ + QPlaceManager *placeManager = manager(); + if (!placeManager) + return; + + m_reply = placeManager->getPlaceDetails(placeId()); + connect(m_reply, SIGNAL(finished()), this, SLOT(finished())); + setStatus(QDeclarativePlace::Fetching); +} + +/*! + \qmlmethod void Place::save() + + This method performs a save operation on the place. + + The \l status property will change to Place.Saving while the save operation is in progress. On + success the \l status will be set to Place.Ready. On error \l status will be set to Place.Error. + The \l errorString() method can be used to get the details of the error. + + If the \l placeId property was previously empty, it will be assigned a valid value automatically + during a successful save operation. + + Note that a \l PlaceSearchModel will call Place::getDetails on any place that it detects an update + on. A consequence of this is that whenever a Place from a \l PlaceSearchModel is successfully saved, + it will be followed by a fetch of place details, leading to a sequence of state changes + of \c Saving, \c Ready, \c Fetching, \c Ready. + +*/ +void QDeclarativePlace::save() +{ + QPlaceManager *placeManager = manager(); + if (!placeManager) + return; + + m_reply = placeManager->savePlace(place()); + connect(m_reply, SIGNAL(finished()), this, SLOT(finished())); + setStatus(QDeclarativePlace::Saving); +} + +/*! + \qmlmethod void Place::remove() + + This method performs a remove operation on the place. + + The \l status property will change to Place.Removing while the save operation is in progress. + On success \l status will be set to Place.Ready. On error \l status will be set to + Place.Error. The \l errorString() method can be used to get the details of the error. +*/ +void QDeclarativePlace::remove() +{ + QPlaceManager *placeManager = manager(); + if (!placeManager) + return; + + m_reply = placeManager->removePlace(place().placeId()); + connect(m_reply, SIGNAL(finished()), this, SLOT(finished())); + setStatus(QDeclarativePlace::Removing); +} + +/*! + \qmlmethod string Place::errorString() + + Returns a string description of the error of the last operation. If the last operation + completed successfully then the string is empty. +*/ +QString QDeclarativePlace::errorString() const +{ + return m_errorString; +} + +/*! + \qmlproperty string Place::primaryPhone + + This property holds the primary phone number of the place. If no "phone" contact detail is + defined for this place, this property will be an empty string. It is equivalent to: + + + \snippet declarative/places.qml Place primaryPhone +*/ +QString QDeclarativePlace::primaryPhone() const +{ + return primaryValue(QPlaceContactDetail::Phone); +} + +/*! + \qmlproperty string Place::primaryFax + + This property holds the primary fax number of the place. If no "fax" contact detail is + defined for this place this property will be an empty string. It is equivalent to + + \snippet declarative/places.qml Place primaryFax +*/ +QString QDeclarativePlace::primaryFax() const +{ + return primaryValue(QPlaceContactDetail::Fax); +} + +/*! + \qmlproperty string Place::primaryEmail + + This property holds the primary email address of the place. If no "email" contact detail is + defined for this place this property will be an empty string. It is equivalent to + + \snippet declarative/places.qml Place primaryEmail +*/ +QString QDeclarativePlace::primaryEmail() const +{ + return primaryValue(QPlaceContactDetail::Email); +} + +/*! + \qmlproperty string Place::primaryWebsite + + This property holds the primary website url of the place. If no "website" contact detail is + defined for this place this property will be an empty string. It is equivalent to + + \snippet declarative/places.qml Place primaryWebsite +*/ + +QUrl QDeclarativePlace::primaryWebsite() const +{ + return QUrl(primaryValue(QPlaceContactDetail::Website)); +} + +/*! + \qmlproperty ExtendedAttributes Place::extendedAttributes + + This property holds the extended attributes of a place. Extended attributes are additional + information about a place not covered by the place's properties. +*/ +QQmlPropertyMap *QDeclarativePlace::extendedAttributes() const +{ + return m_extendedAttributes; +} + +/*! + \qmlproperty ContactDetails Place::contactDetails + + This property holds the contact information for this place, for example a phone number or + a website URL. This property is a map of \l ContactDetail objects. +*/ +QDeclarativeContactDetails *QDeclarativePlace::contactDetails() const +{ + return m_contactDetails; +} + +/*! + \qmlproperty list Place::categories + + This property holds the list of categories this place is a member of. The categories that can + be assigned to a place are specific to each \l plugin. +*/ +QQmlListProperty QDeclarativePlace::categories() +{ + return QQmlListProperty(this, + 0, // opaque data parameter + category_append, + category_count, + category_at, + category_clear); +} + +/*! + \internal +*/ +void QDeclarativePlace::category_append(QQmlListProperty *prop, + QDeclarativeCategory *value) +{ + QDeclarativePlace *object = static_cast(prop->object); + + if (object->m_categoriesToBeDeleted.contains(value)) + object->m_categoriesToBeDeleted.removeAll(value); + + if (!object->m_categories.contains(value)) { + object->m_categories.append(value); + QList list = object->m_src.categories(); + list.append(value->category()); + object->m_src.setCategories(list); + + emit object->categoriesChanged(); + } +} + +/*! + \internal +*/ +int QDeclarativePlace::category_count(QQmlListProperty *prop) +{ + return static_cast(prop->object)->m_categories.count(); +} + +/*! + \internal +*/ +QDeclarativeCategory *QDeclarativePlace::category_at(QQmlListProperty *prop, + int index) +{ + QDeclarativePlace *object = static_cast(prop->object); + QDeclarativeCategory *res = NULL; + if (object->m_categories.count() > index && index > -1) { + res = object->m_categories[index]; + } + return res; +} + +/*! + \internal +*/ +void QDeclarativePlace::category_clear(QQmlListProperty *prop) +{ + QDeclarativePlace *object = static_cast(prop->object); + if (object->m_categories.isEmpty()) + return; + + for (int i = 0; i < object->m_categories.count(); ++i) { + if (object->m_categories.at(i)->parent() == object) + object->m_categoriesToBeDeleted.append(object->m_categories.at(i)); + } + + object->m_categories.clear(); + object->m_src.setCategories(QList()); + emit object->categoriesChanged(); + QMetaObject::invokeMethod(object, "cleanupDeletedCategories", Qt::QueuedConnection); +} + +/*! + \internal +*/ +void QDeclarativePlace::synchronizeCategories() +{ + qDeleteAll(m_categories); + m_categories.clear(); + foreach (const QPlaceCategory &value, m_src.categories()) { + QDeclarativeCategory *declarativeValue = new QDeclarativeCategory(value, m_plugin, this); + m_categories.append(declarativeValue); + } +} + +/*! + \qmlproperty enumeration Place::visibility + + This property holds the visibility of the place. It can be one of: + + \table + \row + \li Place.UnspecifiedVisibility + \li The visibility of the place is unspecified, the default visibility of the \l Plugin + will be used. + \row + \li Place.DeviceVisibility + \li The place is limited to the current device. The place will not be transferred off + of the device. + \row + \li Place.PrivateVisibility + \li The place is private to the current user. The place may be transferred to an online + service but is only ever visible to the current user. + \row + \li Place.PublicVisibility + \li The place is public. + \endtable + + Note that visibility does not affect how the place is displayed + in the user-interface of an application on the device. Instead, + it defines the sharing semantics of the place. +*/ +QDeclarativePlace::Visibility QDeclarativePlace::visibility() const +{ + return static_cast(m_src.visibility()); +} + +void QDeclarativePlace::setVisibility(Visibility visibility) +{ + if (static_cast(m_src.visibility()) == visibility) + return; + + m_src.setVisibility(static_cast(visibility)); + emit visibilityChanged(); +} + +/*! + \qmlproperty Place Place::favorite + + This property holds the favorite instance of a place. +*/ +QDeclarativePlace *QDeclarativePlace::favorite() const +{ + return m_favorite; +} + +void QDeclarativePlace::setFavorite(QDeclarativePlace *favorite) +{ + + if (m_favorite == favorite) + return; + + if (m_favorite && m_favorite->parent() == this) + delete m_favorite; + + m_favorite = favorite; + emit favoriteChanged(); +} + +/*! + \qmlmethod void Place::copyFrom(Place original) + + Copies data from an \a original place into this place. Only data that is supported by this + place's plugin is copied over and plugin specific data such as place identifier is not copied over. +*/ +void QDeclarativePlace::copyFrom(QDeclarativePlace *original) +{ + QPlaceManager *placeManager = manager(); + if (!placeManager) + return; + + setPlace(placeManager->compatiblePlace(original->place())); +} + +/*! + \qmlmethod void Place::initializeFavorite(Plugin destinationPlugin) + + Creates a favorite instance for the place which is to be saved into the + \a destination plugin. This method does nothing if the favorite property is + not null. +*/ +void QDeclarativePlace::initializeFavorite(QDeclarativeGeoServiceProvider *plugin) +{ + if (m_favorite == 0) { + QDeclarativePlace *place = new QDeclarativePlace(this); + place->setPlugin(plugin); + place->copyFrom(this); + setFavorite(place); + } +} + +/*! + \internal +*/ +void QDeclarativePlace::synchronizeExtendedAttributes() +{ + QStringList keys = m_extendedAttributes->keys(); + foreach (const QString &key, keys) + m_extendedAttributes->clear(key); + + QStringList attributeTypes = m_src.extendedAttributeTypes(); + foreach (const QString &attributeType, attributeTypes) { + m_extendedAttributes->insert(attributeType, + qVariantFromValue(new QDeclarativePlaceAttribute(m_src.extendedAttribute(attributeType)))); + } + + emit extendedAttributesChanged(); +} + +/*! + \internal +*/ +void QDeclarativePlace::synchronizeContacts() +{ + //clear out contact data + foreach (const QString &contactType, m_contactDetails->keys()) { + QList contacts = m_contactDetails->value(contactType).toList(); + foreach (const QVariant &var, contacts) { + QObject *obj = var.value(); + if (obj->parent() == this) + delete obj; + } + m_contactDetails->insert(contactType, QVariantList()); + } + + //insert new contact data from source place + foreach (const QString &contactType, m_src.contactTypes()) { + QList sourceContacts = m_src.contactDetails(contactType); + QVariantList declContacts; + foreach (const QPlaceContactDetail &sourceContact, sourceContacts) { + QDeclarativeContactDetail *declContact = new QDeclarativeContactDetail(this); + declContact->setContactDetail(sourceContact); + declContacts.append(QVariant::fromValue(qobject_cast(declContact))); + } + m_contactDetails->insert(contactType, declContacts); + } + primarySignalsEmission(); +} + +/*! + \internal + Helper function to emit the signals for the primary___() + fields. It is expected that the values of the primary___() + functions have already been modified to new values. +*/ +void QDeclarativePlace::primarySignalsEmission(const QString &type) +{ + if (type.isEmpty() || type == QPlaceContactDetail::Phone) { + if (m_prevPrimaryPhone != primaryPhone()) { + m_prevPrimaryPhone = primaryPhone(); + emit primaryPhoneChanged(); + } + if (!type.isEmpty()) + return; + } + + if (type.isEmpty() || type == QPlaceContactDetail::Email) { + if (m_prevPrimaryEmail != primaryEmail()) { + m_prevPrimaryEmail = primaryEmail(); + emit primaryEmailChanged(); + } + if (!type.isEmpty()) + return; + } + + if (type.isEmpty() || type == QPlaceContactDetail::Website) { + if (m_prevPrimaryWebsite != primaryWebsite()) { + m_prevPrimaryWebsite = primaryWebsite(); + emit primaryWebsiteChanged(); + } + if (!type.isEmpty()) + return; + } + + if (type.isEmpty() || type == QPlaceContactDetail::Fax) { + if (m_prevPrimaryFax != primaryFax()) { + m_prevPrimaryFax = primaryFax(); + emit primaryFaxChanged(); + } + } +} + +/*! + \internal + Helper function to return the manager, this manager is intended to be used + to perform the next operation. If a an operation is currently underway + then return a null pointer. +*/ +QPlaceManager *QDeclarativePlace::manager() +{ + if (m_status != QDeclarativePlace::Ready && m_status != QDeclarativePlace::Error) + return 0; + + if (m_reply) { + m_reply->abort(); + m_reply->deleteLater(); + m_reply = 0; + } + + if (!m_plugin) { + qmlInfo(this) << QStringLiteral("Plugin is not assigned to place."); + return 0; + } + + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (!serviceProvider) + return 0; + + QPlaceManager *placeManager = serviceProvider->placeManager(); + + if (!placeManager) { + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_ERROR) + .arg(m_plugin->name()).arg(serviceProvider->errorString())); + return 0; + } + + return placeManager; +} + +/*! + \internal +*/ +QString QDeclarativePlace::primaryValue(const QString &contactType) const +{ + QVariant value = m_contactDetails->value(contactType); + if (value.userType() == qMetaTypeId()) + value = value.value().toVariant(); + + if (value.userType() == QVariant::List) { + QVariantList detailList = m_contactDetails->value(contactType).toList(); + if (!detailList.isEmpty()) { + QDeclarativeContactDetail *primaryDetail = qobject_cast(detailList.at(0).value()); + if (primaryDetail) + return primaryDetail->value(); + } + } else if (value.userType() == QMetaType::QObjectStar) { + QDeclarativeContactDetail *primaryDetail = qobject_cast(m_contactDetails->value(contactType).value()); + if (primaryDetail) + return primaryDetail->value(); + } + + return QString(); +} diff --git a/src/imports/location/declarativeplaces/qdeclarativeplace_p.h b/src/imports/location/declarativeplaces/qdeclarativeplace_p.h new file mode 100644 index 0000000..7855709 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplace_p.h @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPLACE_P_H +#define QDECLARATIVEPLACE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +#include "qdeclarativegeolocation_p.h" +#include "qdeclarativecategory_p.h" +#include "qdeclarativecontactdetail_p.h" +#include "qdeclarativesupplier_p.h" +#include "qdeclarativeratings_p.h" +#include "qdeclarativereviewmodel_p.h" +#include "qdeclarativeplaceimagemodel_p.h" +#include "qdeclarativeplaceeditorialmodel.h" + +QT_BEGIN_NAMESPACE + +class QPlaceReply; + +class QPlaceManager; +class QDeclarativePlaceIcon; + +class QDeclarativePlace : public QObject, public QQmlParserStatus +{ + Q_OBJECT + + Q_ENUMS(Status Visibility) + + Q_PROPERTY(QPlace place READ place WRITE setPlace) + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(QQmlListProperty categories READ categories NOTIFY categoriesChanged) + Q_PROPERTY(QDeclarativeGeoLocation *location READ location WRITE setLocation NOTIFY locationChanged) + Q_PROPERTY(QDeclarativeRatings *ratings READ ratings WRITE setRatings NOTIFY ratingsChanged) + Q_PROPERTY(QDeclarativeSupplier *supplier READ supplier WRITE setSupplier NOTIFY supplierChanged) + Q_PROPERTY(QDeclarativePlaceIcon *icon READ icon WRITE setIcon NOTIFY iconChanged) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QString placeId READ placeId WRITE setPlaceId NOTIFY placeIdChanged) + Q_PROPERTY(QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged) + + Q_PROPERTY(QDeclarativeReviewModel *reviewModel READ reviewModel NOTIFY reviewModelChanged) + Q_PROPERTY(QDeclarativePlaceImageModel *imageModel READ imageModel NOTIFY imageModelChanged) + Q_PROPERTY(QDeclarativePlaceEditorialModel *editorialModel READ editorialModel NOTIFY editorialModelChanged) + + Q_PROPERTY(QObject *extendedAttributes READ extendedAttributes NOTIFY extendedAttributesChanged) + Q_PROPERTY(QObject *contactDetails READ contactDetails NOTIFY contactDetailsChanged) + Q_PROPERTY(bool detailsFetched READ detailsFetched NOTIFY detailsFetchedChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + + Q_PROPERTY(QString primaryPhone READ primaryPhone NOTIFY primaryPhoneChanged) + Q_PROPERTY(QString primaryFax READ primaryFax NOTIFY primaryFaxChanged) + Q_PROPERTY(QString primaryEmail READ primaryEmail NOTIFY primaryEmailChanged) + Q_PROPERTY(QUrl primaryWebsite READ primaryWebsite NOTIFY primaryWebsiteChanged) + + Q_PROPERTY(Visibility visibility READ visibility WRITE setVisibility NOTIFY visibilityChanged) + Q_PROPERTY(QDeclarativePlace *favorite READ favorite WRITE setFavorite NOTIFY favoriteChanged) + + Q_INTERFACES(QQmlParserStatus) + +public: + explicit QDeclarativePlace(QObject *parent = 0); + QDeclarativePlace(const QPlace &src, QDeclarativeGeoServiceProvider *plugin, QObject *parent = 0); + ~QDeclarativePlace(); + + enum Status {Ready, Saving, Fetching, Removing, Error}; + enum Visibility { + UnspecifiedVisibility = QLocation::UnspecifiedVisibility, + DeviceVisibility = QLocation::DeviceVisibility, + PrivateVisibility = QLocation::PrivateVisibility, + PublicVisibility = QLocation::PublicVisibility + }; + + //From QQmlParserStatus + virtual void classBegin() {} + virtual void componentComplete(); + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + + QDeclarativeReviewModel *reviewModel(); + QDeclarativePlaceImageModel *imageModel(); + QDeclarativePlaceEditorialModel *editorialModel(); + + QPlace place(); + void setPlace(const QPlace &src); + + QQmlListProperty categories(); + static void category_append(QQmlListProperty *prop, + QDeclarativeCategory *value); + static int category_count(QQmlListProperty *prop); + static QDeclarativeCategory *category_at(QQmlListProperty *prop, int index); + static void category_clear(QQmlListProperty *prop); + + QDeclarativeGeoLocation *location(); + void setLocation(QDeclarativeGeoLocation *location); + QDeclarativeRatings *ratings(); + void setRatings(QDeclarativeRatings *ratings); + QDeclarativeSupplier *supplier() const; + void setSupplier(QDeclarativeSupplier *supplier); + QDeclarativePlaceIcon *icon() const; + void setIcon(QDeclarativePlaceIcon *icon); + QString name() const; + void setName(const QString &name); + QString placeId() const; + void setPlaceId(const QString &placeId); + QString attribution() const; + void setAttribution(const QString &attribution); + bool detailsFetched() const; + + Status status() const; + void setStatus(Status status, const QString &errorString = QString()); + + Q_INVOKABLE void getDetails(); + Q_INVOKABLE void save(); + Q_INVOKABLE void remove(); + Q_INVOKABLE QString errorString() const; + + QString primaryPhone() const; + QString primaryFax() const; + QString primaryEmail() const; + QUrl primaryWebsite() const; + + QQmlPropertyMap *extendedAttributes() const; + + QDeclarativeContactDetails *contactDetails() const; + + Visibility visibility() const; + void setVisibility(Visibility visibility); + + QDeclarativePlace *favorite() const; + void setFavorite(QDeclarativePlace *favorite); + + Q_INVOKABLE void copyFrom(QDeclarativePlace *original); + Q_INVOKABLE void initializeFavorite(QDeclarativeGeoServiceProvider *plugin); + +Q_SIGNALS: + void pluginChanged(); + void categoriesChanged(); + void locationChanged(); + void ratingsChanged(); + void supplierChanged(); + void iconChanged(); + void nameChanged(); + void placeIdChanged(); + void attributionChanged(); + void detailsFetchedChanged(); + void reviewModelChanged(); + void imageModelChanged(); + void editorialModelChanged(); + + void primaryPhoneChanged(); + void primaryFaxChanged(); + void primaryEmailChanged(); + void primaryWebsiteChanged(); + + void extendedAttributesChanged(); + void contactDetailsChanged(); + void statusChanged(); + void visibilityChanged(); + void favoriteChanged(); + +private Q_SLOTS: + void finished(); + void contactsModified(const QString &, const QVariant &); + void pluginReady(); + void cleanupDeletedCategories(); +private: + void synchronizeCategories(); + void synchronizeExtendedAttributes(); + void synchronizeContacts(); + void primarySignalsEmission(const QString &type = QString()); + QString primaryValue(const QString &contactType) const; + +private: + QPlaceManager *manager(); + + QList m_categories; + QDeclarativeGeoLocation *m_location; + QDeclarativeRatings *m_ratings; + QDeclarativeSupplier *m_supplier; + QDeclarativePlaceIcon *m_icon; + QDeclarativeReviewModel *m_reviewModel; + QDeclarativePlaceImageModel *m_imageModel; + QDeclarativePlaceEditorialModel *m_editorialModel; + QQmlPropertyMap *m_extendedAttributes; + QDeclarativeContactDetails *m_contactDetails; + + QPlace m_src; + + QPlaceReply *m_reply; + + QDeclarativeGeoServiceProvider *m_plugin; + bool m_complete; + + QString m_prevPrimaryPhone; + QString m_prevPrimaryEmail; + QString m_prevPrimaryFax; + QUrl m_prevPrimaryWebsite; + + QDeclarativePlace *m_favorite; + + Status m_status; + QString m_errorString; + + QListm_categoriesToBeDeleted; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativePlace) + +#endif // QDECLARATIVEPLACE_P_H diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceattribute.cpp b/src/imports/location/declarativeplaces/qdeclarativeplaceattribute.cpp new file mode 100644 index 0000000..9a04d38 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceattribute.cpp @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativeplaceattribute_p.h" + +/*! + \qmltype ExtendedAttributes + \instantiates QQmlPropertyMap + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + \since Qt Location 5.5 + + \brief The ExtendedAttributes type holds additional data about a \l Place. + + The ExtendedAttributes type is a map of \l {PlaceAttribute}{PlaceAttributes}. To access + attributes in the map use the \l keys() method to get the list of keys stored in the map and + use the \c {[]} operator to access the \l PlaceAttribute items. + + The following are standard keys that are defined by the API. \l Plugin + implementations are free to define additional keys. Custom keys should + be qualified by a unique prefix to avoid clashes. + \table + \header + \li key + \li description + \row + \li openingHours + \li The trading hours of the place + \row + \li payment + \li The types of payment the place accepts, for example visa, mastercard. + \row + \li x_provider + \li The name of the provider that a place is sourced from + \row + \li x_id_ (for example x_id_here) + \li An alternative identifier which identifies the place from the + perspective of the specified provider. + \endtable + + Some plugins may not support attributes at all, others may only support a + certain set, others still may support a dynamically changing set of attributes + over time or even allow attributes to be arbitrarily defined by the client + application. The attributes could also vary on a place by place basis, + for example one place may have opening hours while another does not. + Consult the \l {Plugin References and Parameters}{plugin + references} for details. + + Some attributes may not be intended to be readable by end users, the label field + of such attributes is empty to indicate this fact. + + \note ExtendedAttributes instances are only ever used in the context of \l {Place}s. It is not + possible to create an ExtendedAttributes instance directly or re-assign a \l {Place}'s + ExtendedAttributes property. Modification of ExtendedAttributes can only be accomplished + via Javascript. + + The following example shows how to access all \l {PlaceAttribute}{PlaceAttributes} and print + them to the console: + + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml ExtendedAttributes read + + The following example shows how to assign and modify an attribute: + \snippet declarative/places.qml ExtendedAttributes write + + \sa PlaceAttribute, QQmlPropertyMap +*/ + +/*! + \qmlmethod variant ExtendedAttributes::keys() + + Returns an array of place attribute keys currently stored in the map. +*/ + +/*! + \qmlsignal void ExtendedAttributes::valueChanged(string key, variant value) + + This signal is emitted when the set of attributes changes. \a key is the key + corresponding to the \a value that was changed. + + The corresponding handler is \c onValueChanged. +*/ + +/*! + \qmltype PlaceAttribute + \instantiates QDeclarativePlaceAttribute + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + \since Qt Location 5.5 + + \brief The PlaceAttribute type holds generic place attribute information. + + A place attribute stores an additional piece of information about a \l Place that is not + otherwise exposed through the \l Place type. A PlaceAttribute is a textual piece of data, + accessible through the \l text property, and a \l label. Both the \l text and \l label + properties are intended to be displayed to the user. PlaceAttributes are stored in an + \l ExtendedAttributes map with a unique key. + + The following example shows how to display all attributes in a list: + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml ExtendedAttributes + + The following example shows how to assign and modify an attribute: + \snippet declarative/places.qml ExtendedAttributes write +*/ + +QDeclarativePlaceAttribute::QDeclarativePlaceAttribute(QObject *parent) + : QObject(parent) +{ +} + +QDeclarativePlaceAttribute::QDeclarativePlaceAttribute(const QPlaceAttribute &src, QObject *parent) + : QObject(parent),m_attribute(src) +{ +} + +QDeclarativePlaceAttribute::~QDeclarativePlaceAttribute() +{ +} + +/*! + \qmlproperty QPlaceAttribute PlaceAttribute::attribute + + For details on how to use this property to interface between C++ and QML see + "\l {PlaceAttribute - QPlaceAttribute} {Interfaces between C++ and QML Code}". +*/ +void QDeclarativePlaceAttribute::setAttribute(const QPlaceAttribute &src) +{ + QPlaceAttribute prevAttribute = m_attribute; + m_attribute = src; + + if (m_attribute.label() != prevAttribute.label()) + emit labelChanged(); + if (m_attribute.text() != prevAttribute.text()) + emit textChanged(); +} + +QPlaceAttribute QDeclarativePlaceAttribute::attribute() const +{ + return m_attribute; +} + +/*! + \qmlproperty string PlaceAttribute::label + + This property holds the attribute label which is a user visible string + describing the attribute. +*/ +void QDeclarativePlaceAttribute::setLabel(const QString &label) +{ + if (m_attribute.label() != label) { + m_attribute.setLabel(label); + emit labelChanged(); + } +} + +QString QDeclarativePlaceAttribute::label() const +{ + return m_attribute.label(); +} + +/*! + \qmlproperty string PlaceAttribute::text + + This property holds the attribute text which can be used to show additional information about the place. +*/ +void QDeclarativePlaceAttribute::setText(const QString &text) +{ + if (m_attribute.text() != text) { + m_attribute.setText(text); + emit textChanged(); + } +} + +QString QDeclarativePlaceAttribute::text() const +{ + return m_attribute.text(); +} diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceattribute_p.h b/src/imports/location/declarativeplaces/qdeclarativeplaceattribute_p.h new file mode 100644 index 0000000..f1c873c --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceattribute_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPLACEATTRIBUTE_P_H +#define QDECLARATIVEPLACEATTRIBUTE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativePlaceAttribute : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPlaceAttribute attribute READ attribute WRITE setAttribute) + Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + +public: + explicit QDeclarativePlaceAttribute(QObject *parent = 0); + explicit QDeclarativePlaceAttribute(const QPlaceAttribute &src, QObject *parent = 0); + ~QDeclarativePlaceAttribute(); + + QPlaceAttribute attribute() const; + void setAttribute(const QPlaceAttribute &place); + + QString text() const; + void setText(const QString &text); + + + QString label() const; + void setLabel(const QString &label); + +Q_SIGNALS: + void labelChanged(); + void textChanged(); + +private: + QPlaceAttribute m_attribute; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativePlaceAttribute) + +#endif diff --git a/src/imports/location/declarativeplaces/qdeclarativeplacecontentmodel.cpp b/src/imports/location/declarativeplaces/qdeclarativeplacecontentmodel.cpp new file mode 100644 index 0000000..b031e0c --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplacecontentmodel.cpp @@ -0,0 +1,397 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativeplacecontentmodel.h" +#include "qdeclarativeplace_p.h" +#include "qdeclarativegeoserviceprovider_p.h" +#include "qdeclarativeplaceuser_p.h" +#include "error_messages.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QDeclarativePlaceContentModel::QDeclarativePlaceContentModel(QPlaceContent::Type type, + QObject *parent) +: QAbstractListModel(parent), m_place(0), m_type(type), m_batchSize(1), m_contentCount(-1), + m_reply(0), m_complete(false) +{ +} + +QDeclarativePlaceContentModel::~QDeclarativePlaceContentModel() +{ +} + +/*! + \internal +*/ +QDeclarativePlace *QDeclarativePlaceContentModel::place() const +{ + return m_place; +} + +/*! + \internal +*/ +void QDeclarativePlaceContentModel::setPlace(QDeclarativePlace *place) +{ + if (m_place != place) { + beginResetModel(); + + int initialCount = m_contentCount; + clearData(); + m_place = place; + endResetModel(); + + emit placeChanged(); + if (initialCount != -1) + emit totalCountChanged(); + + fetchMore(QModelIndex()); + } +} + +/*! + \internal +*/ +int QDeclarativePlaceContentModel::batchSize() const +{ + return m_batchSize; +} + +/*! + \internal +*/ +void QDeclarativePlaceContentModel::setBatchSize(int batchSize) +{ + if (m_batchSize != batchSize) { + m_batchSize = batchSize; + emit batchSizeChanged(); + } +} + +/*! + \internal +*/ +int QDeclarativePlaceContentModel::totalCount() const +{ + return m_contentCount; +} + +/*! + \internal + Clears the model data but does not reset it. +*/ +void QDeclarativePlaceContentModel::clearData() +{ + qDeleteAll(m_users); + m_users.clear(); + + qDeleteAll(m_suppliers); + m_suppliers.clear(); + + m_content.clear(); + + m_contentCount = -1; + + if (m_reply) { + m_reply->abort(); + m_reply->deleteLater(); + m_reply = 0; + } + + m_nextRequest.clear(); +} + +/*! + \internal +*/ +void QDeclarativePlaceContentModel::initializeCollection(int totalCount, const QPlaceContent::Collection &collection) +{ + beginResetModel(); + + int initialCount = m_contentCount; + clearData(); + + QMapIterator i(collection); + while (i.hasNext()) { + i.next(); + + const QPlaceContent &content = i.value(); + if (content.type() != m_type) + continue; + + m_content.insert(i.key(), content); + if (!m_suppliers.contains(content.supplier().supplierId())) { + m_suppliers.insert(content.supplier().supplierId(), + new QDeclarativeSupplier(content.supplier(), m_place->plugin(), this)); + } + if (!m_users.contains(content.user().userId())) { + m_users.insert(content.user().userId(), + new QDeclarativePlaceUser(content.user(), this)); + } + } + + m_contentCount = totalCount; + + if (initialCount != totalCount) + emit totalCountChanged(); + + endResetModel(); +} + +/*! + \internal +*/ +int QDeclarativePlaceContentModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return m_content.count(); +} + +/*! + \internal +*/ +QVariant QDeclarativePlaceContentModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= rowCount(index.parent()) || index.row() < 0) + return QVariant(); + + const QPlaceContent &content = m_content.value(index.row()); + + switch (role) { + case SupplierRole: + return QVariant::fromValue(static_cast(m_suppliers.value(content.supplier().supplierId()))); + case PlaceUserRole: + return QVariant::fromValue(static_cast(m_users.value(content.user().userId()))); + case AttributionRole: + return content.attribution(); + default: + return QVariant(); + } +} + +QHash QDeclarativePlaceContentModel::roleNames() const +{ + QHash roles = QAbstractListModel::roleNames(); + roles.insert(SupplierRole, "supplier"); + roles.insert(PlaceUserRole, "user"); + roles.insert(AttributionRole, "attribution"); + return roles; +} + +/*! + \internal +*/ +bool QDeclarativePlaceContentModel::canFetchMore(const QModelIndex &parent) const +{ + if (parent.isValid()) + return false; + + if (!m_place) + return false; + + if (m_contentCount == -1) + return true; + + return m_content.count() != m_contentCount; +} + +/*! + \internal +*/ +void QDeclarativePlaceContentModel::fetchMore(const QModelIndex &parent) +{ + if (parent.isValid()) + return; + + if (!m_place) + return; + + if (m_reply) + return; + + if (!m_place->plugin()) + return; + + QDeclarativeGeoServiceProvider *plugin = m_place->plugin(); + + QGeoServiceProvider *serviceProvider = plugin->sharedGeoServiceProvider(); + if (!serviceProvider) + return; + + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (!placeManager) + return; + + if (m_nextRequest == QPlaceContentRequest()) { + QPlaceContentRequest request; + request.setContentType(m_type); + request.setPlaceId(m_place->place().placeId()); + request.setLimit(m_batchSize); + + m_reply = placeManager->getPlaceContent(request); + } else { + m_reply = placeManager->getPlaceContent(m_nextRequest); + } + + connect(m_reply, SIGNAL(finished()), this, SLOT(fetchFinished()), Qt::QueuedConnection); +} + +/*! + \internal +*/ +void QDeclarativePlaceContentModel::classBegin() +{ +} + +/*! + \internal +*/ +void QDeclarativePlaceContentModel::componentComplete() +{ + m_complete = true; + fetchMore(QModelIndex()); +} + +/*! + \internal +*/ +void QDeclarativePlaceContentModel::fetchFinished() +{ + if (!m_reply) + return; + + QPlaceContentReply *reply = m_reply; + m_reply = 0; + + m_nextRequest = reply->nextPageRequest(); + + if (m_contentCount != reply->totalCount()) { + m_contentCount = reply->totalCount(); + emit totalCountChanged(); + } + + if (!reply->content().isEmpty()) { + QPlaceContent::Collection contents = reply->content(); + + //find out which indexes are new and which ones have changed. + QMapIterator it(contents); + QList changedIndexes; + QList newIndexes; + while (it.hasNext()) { + it.next(); + if (!m_content.contains(it.key())) + newIndexes.append(it.key()); + else if (it.value() != m_content.value(it.key())) + changedIndexes.append(it.key()); + } + + //insert new indexes in blocks where within each + //block, the indexes are consecutive. + QListIterator newIndexesIter(newIndexes); + int startIndex = -1; + while (newIndexesIter.hasNext()) { + int currentIndex = newIndexesIter.next(); + if (startIndex == -1) + startIndex = currentIndex; + + if (!newIndexesIter.hasNext() || (newIndexesIter.hasNext() && (newIndexesIter.peekNext() > (currentIndex + 1)))) { + beginInsertRows(QModelIndex(),startIndex,currentIndex); + for (int i = startIndex; i <= currentIndex; ++i) { + const QPlaceContent &content = contents.value(i); + + m_content.insert(i, content); + if (!m_suppliers.contains(content.supplier().supplierId())) { + m_suppliers.insert(content.supplier().supplierId(), + new QDeclarativeSupplier(content.supplier(), m_place->plugin(), this)); + } + if (!m_users.contains(content.user().userId())) { + m_users.insert(content.user().userId(), + new QDeclarativePlaceUser(content.user(), this)); + } + } + endInsertRows(); + startIndex = -1; + } + } + + //modify changed indexes in blocks where within each + //block, the indexes are consecutive. + startIndex = -1; + QListIterator changedIndexesIter(changedIndexes); + while (changedIndexesIter.hasNext()) { + int currentIndex = changedIndexesIter.next(); + if (startIndex == -1) + startIndex = currentIndex; + + if (!changedIndexesIter.hasNext() || (changedIndexesIter.hasNext() && changedIndexesIter.peekNext() > (currentIndex + 1))) { + for (int i = startIndex; i <= currentIndex; ++i) { + const QPlaceContent &content = contents.value(i); + m_content.insert(i, content); + if (!m_suppliers.contains(content.supplier().supplierId())) { + m_suppliers.insert(content.supplier().supplierId(), + new QDeclarativeSupplier(content.supplier(), m_place->plugin(), this)); + } + if (!m_users.contains(content.user().userId())) { + m_users.insert(content.user().userId(), + new QDeclarativePlaceUser(content.user(), this)); + } + } + emit dataChanged(index(startIndex),index(currentIndex)); + startIndex = -1; + } + } + + // The fetch didn't add any new content and we haven't fetched all content yet. This is + // likely due to the model being prepopulated by Place::getDetails(). Keep fetching more + // data until new content is available. + if (newIndexes.isEmpty() && m_content.count() != m_contentCount) + fetchMore(QModelIndex()); + } + + reply->deleteLater(); +} + +QT_END_NAMESPACE diff --git a/src/imports/location/declarativeplaces/qdeclarativeplacecontentmodel.h b/src/imports/location/declarativeplaces/qdeclarativeplacecontentmodel.h new file mode 100644 index 0000000..d839b92 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplacecontentmodel.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPLACECONTENTMODEL_H +#define QDECLARATIVEPLACECONTENTMODEL_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativePlace; +class QDeclarativeGeoServiceProvider; +class QGeoServiceProvider; +class QDeclarativeSupplier; +class QDeclarativePlaceUser; + +class QDeclarativePlaceContentModel : public QAbstractListModel, public QQmlParserStatus +{ + Q_OBJECT + + Q_PROPERTY(QDeclarativePlace *place READ place WRITE setPlace NOTIFY placeChanged) + Q_PROPERTY(int batchSize READ batchSize WRITE setBatchSize NOTIFY batchSizeChanged) + Q_PROPERTY(int totalCount READ totalCount NOTIFY totalCountChanged) + + Q_INTERFACES(QQmlParserStatus) + +public: + explicit QDeclarativePlaceContentModel(QPlaceContent::Type type, QObject *parent = 0); + ~QDeclarativePlaceContentModel(); + + QDeclarativePlace *place() const; + void setPlace(QDeclarativePlace *place); + + int batchSize() const; + void setBatchSize(int batchSize); + + int totalCount() const; + + void clearData(); + + void initializeCollection(int totalCount, const QPlaceContent::Collection &collection); + + // from QAbstractListModel + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QHash roleNames() const; + + enum Roles { + SupplierRole = Qt::UserRole, + PlaceUserRole, + AttributionRole, + UserRole //indicator for next conten type specific role + }; + + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); + + // from QQmlParserStatus + void classBegin(); + void componentComplete(); + +Q_SIGNALS: + void placeChanged(); + void batchSizeChanged(); + void totalCountChanged(); + +private Q_SLOTS: + void fetchFinished(); + +protected: + QPlaceContent::Collection m_content; + QMap m_suppliers; + QMapm_users; + +private: + QDeclarativePlace *m_place; + QPlaceContent::Type m_type; + int m_batchSize; + int m_contentCount; + + QPlaceContentReply *m_reply; + QPlaceContentRequest m_nextRequest; + + bool m_complete; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVEPLACECONTENTMODEL_H diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceeditorialmodel.cpp b/src/imports/location/declarativeplaces/qdeclarativeplaceeditorialmodel.cpp new file mode 100644 index 0000000..ebde46c --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceeditorialmodel.cpp @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativeplaceeditorialmodel.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype EditorialModel + \instantiates QDeclarativePlaceEditorialModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-models + \since Qt Location 5.5 + + \brief The EditorialModel type provides a model of place editorials. + + The EditorialModel is a read-only model used to fetch editorials related to a \l Place. + Binding a \l Place via \l EditorialModel::place initiates an initial fetch of editorials. + The model performs fetches incrementally and is intended to be used in conjunction + with a View such as a \l ListView. When the View reaches the last of the editorials + currently in the model, a fetch is performed to retrieve more if they are available. + The View is automatically updated as the editorials are received. The number of + editorials which are fetched at a time is specified by the \l batchSize property. + The total number of editorials available can be accessed via the \l totalCount property. + + The model returns data for the following roles: + + \table + \header + \li Role + \li Type + \li Description + \row + \li text + \li string + \li The editorial's textual description of the place. It can be either rich (HTML based) text or plain text + depending upon the provider. + \row + \li title + \li string + \li The title of the editorial. + \row + \li language + \li string + \li The language that the editorial is written in. + \row + \li supplier + \li \l Supplier + \li The supplier of the editorial. + \row + \li user + \li \l {QtLocation::User}{User} + \li The user who contributed the editorial. + \row + \li attribution + \li string + \li Attribution text which must be displayed when displaying the editorial. + \endtable + + \section1 Example + + The following example shows how to display editorials for a place: + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml EditorialModel + +*/ + +/*! + \qmlproperty Place EditorialModel::place + + This property holds the Place that the editorials are for. +*/ + +/*! + \qmlproperty int EditorialModel::batchSize + + This property holds the batch size to use when fetching more editorials items. +*/ + +/*! + \qmlproperty int EditorialModel::totalCount + + This property holds the total number of editorial items for the place. +*/ + +QDeclarativePlaceEditorialModel::QDeclarativePlaceEditorialModel(QObject *parent) +: QDeclarativePlaceContentModel(QPlaceContent::EditorialType, parent) +{ +} + +QDeclarativePlaceEditorialModel::~QDeclarativePlaceEditorialModel() +{ +} + +/*! + \internal +*/ +QVariant QDeclarativePlaceEditorialModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= rowCount(index.parent()) || index.row() < 0) + return QVariant(); + + const QPlaceEditorial &description = m_content.value(index.row()); + + switch (role) { + case TextRole: + return description.text(); + case TitleRole: + return description.title(); + case LanguageRole: + return description.language(); + } + + return QDeclarativePlaceContentModel::data(index, role); +} + +QHash QDeclarativePlaceEditorialModel::roleNames() const +{ + QHash roleNames = QDeclarativePlaceContentModel::roleNames(); + roleNames.insert(TextRole, "text"); + roleNames.insert(TitleRole, "title"); + roleNames.insert(LanguageRole, "language"); + return roleNames; +} + +QT_END_NAMESPACE diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceeditorialmodel.h b/src/imports/location/declarativeplaces/qdeclarativeplaceeditorialmodel.h new file mode 100644 index 0000000..eb2faf6 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceeditorialmodel.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPLACEEDITORIALMODEL_H +#define QDECLARATIVEPLACEEDITORIALMODEL_H + +#include "qdeclarativeplacecontentmodel.h" + +QT_BEGIN_NAMESPACE + +class QDeclarativePlaceEditorialModel : public QDeclarativePlaceContentModel +{ + Q_OBJECT + +public: + explicit QDeclarativePlaceEditorialModel(QObject *parent = 0); + ~QDeclarativePlaceEditorialModel(); + + QVariant data(const QModelIndex &index, int role) const; + QHash roleNames() const; + + enum Roles { + TextRole = UserRole, + TitleRole, + LanguageRole + }; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVEPLACEEDITORIALMODEL_H diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceicon.cpp b/src/imports/location/declarativeplaces/qdeclarativeplaceicon.cpp new file mode 100644 index 0000000..d10b2b0 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceicon.cpp @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativeplaceicon_p.h" +#include "error_messages.h" + +#include +#include +#include +#include + +QT_USE_NAMESPACE + +/*! + \qmltype Icon + \instantiates QDeclarativePlaceIcon + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + \since Qt Location 5.5 + + \brief The Icon type represents an icon image source which can have multiple sizes. + + The Icon type can be used in conjunction with an \l Image type to display an icon. + The \l url() function is used to construct an icon URL of a requested size, + the icon which most closely matches the requested size is returned. + + The Icon type also has a parameters map which is a set of key value pairs. The precise + keys to use depend on the + \l {Qt Location#Plugin References and Parameters}{plugin} being used. + The parameters map is used by the \l Plugin to determine which URL to return. + + In the case where an icon can only possibly have one image URL, the + parameter key of \c "singleUrl" can be used with a QUrl value. Any Icon with this + parameter will always return the specified URL regardless of the requested icon + size and not defer to any Plugin. + + The following code shows how to display a 64x64 pixel icon: + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml Icon + + Alternatively, a default sized icon can be specified like so: + \snippet declarative/places.qml Icon default +*/ + +QDeclarativePlaceIcon::QDeclarativePlaceIcon(QObject *parent) +: QObject(parent), m_plugin(0), m_parameters(new QQmlPropertyMap(this)) +{ +} + +QDeclarativePlaceIcon::QDeclarativePlaceIcon(const QPlaceIcon &icon, QDeclarativeGeoServiceProvider *plugin, QObject *parent) +: QObject(parent), m_parameters(new QQmlPropertyMap(this)) +{ + if (icon.isEmpty()) + m_plugin = 0; + else + m_plugin = plugin; + + initParameters(icon.parameters()); +} + +QDeclarativePlaceIcon::~QDeclarativePlaceIcon() +{ +} + +/*! + \qmlproperty QPlaceIcon Icon::icon + + For details on how to use this property to interface between C++ and QML see + "\l {Icon - QPlaceIcon} {Interfaces between C++ and QML Code}". +*/ +QPlaceIcon QDeclarativePlaceIcon::icon() const +{ + QPlaceIcon result; + + if (m_plugin) + result.setManager(manager()); + else + result.setManager(0); + + QVariantMap params; + foreach (const QString &key, m_parameters->keys()) { + const QVariant value = m_parameters->value(key); + if (value.isValid()) { + params.insert(key, value); + } + } + + result.setParameters(params); + + return result; +} + +void QDeclarativePlaceIcon::setIcon(const QPlaceIcon &src) +{ + initParameters(src.parameters()); +} + +/*! + \qmlmethod url Icon::url(size size) + + Returns a URL for the icon image that most closely matches the given \a size. + + If no plugin has been assigned to the icon, and the parameters do not contain the 'singleUrl' key, a default constructed URL + is returned. + +*/ +QUrl QDeclarativePlaceIcon::url(const QSize &size) const +{ + return icon().url(size); +} + +/*! + \qmlproperty Object Icon::parameters + + This property holds the parameters of the icon and is a map. These parameters + are used by the plugin to return the appropriate URL when url() is called and to + specify locations to save to when saving icons. + + Consult the \l {Qt Location#Plugin References and Parameters}{plugin documentation} + for what parameters are supported and how they should be used. + + Note, due to limitations of the QQmlPropertyMap, it is not possible + to declaratively specify the parameters in QML, assignment of parameters keys + and values can only be accomplished by JavaScript. + +*/ +QQmlPropertyMap *QDeclarativePlaceIcon::parameters() const +{ + return m_parameters; +} + +/*! + \qmlproperty Plugin Icon::plugin + + The property holds the plugin that is responsible for managing this icon. +*/ +QDeclarativeGeoServiceProvider *QDeclarativePlaceIcon::plugin() const +{ + return m_plugin; +} + +void QDeclarativePlaceIcon::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (m_plugin == plugin) + return; + + m_plugin = plugin; + emit pluginChanged(); + + if (!m_plugin) + return; + + if (m_plugin->isAttached()) { + pluginReady(); + } else { + connect(m_plugin, SIGNAL(attached()), + this, SLOT(pluginReady())); + } +} + +/*! + \internal +*/ +void QDeclarativePlaceIcon::pluginReady() +{ + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (!placeManager || serviceProvider->error() != QGeoServiceProvider::NoError) { + qmlInfo(this) << QCoreApplication::translate(CONTEXT_NAME, PLUGIN_ERROR) + .arg(m_plugin->name()).arg(serviceProvider->errorString()); + return; + } +} + +/*! + \internal + Helper function to return the manager from the plugin +*/ +QPlaceManager *QDeclarativePlaceIcon::manager() const +{ + if (!m_plugin) { + qmlInfo(this) << QStringLiteral("Plugin is not assigned to place."); + return 0; + } + + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (!serviceProvider) + return 0; + + QPlaceManager *placeManager = serviceProvider->placeManager(); + + if (!placeManager) + return 0; + + return placeManager; +} + +/*! + \internal +*/ +void QDeclarativePlaceIcon::initParameters(const QVariantMap ¶meterMap) +{ + //clear out old parameters + foreach (const QString &key, m_parameters->keys()) + m_parameters->clear(key); + + foreach (const QString &key, parameterMap.keys()) { + QVariant value = parameterMap.value(key); + m_parameters->insert(key, value); + } +} diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceicon_p.h b/src/imports/location/declarativeplaces/qdeclarativeplaceicon_p.h new file mode 100644 index 0000000..23acb36 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceicon_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPLACEICON_P_H +#define QDECLARATIVEPLACEICON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeoserviceprovider_p.h" + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QQmlPropertyMap; + +class QDeclarativePlaceIcon : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPlaceIcon icon READ icon WRITE setIcon) + Q_PROPERTY(QObject *parameters READ parameters NOTIFY parametersChanged) + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + +public: + explicit QDeclarativePlaceIcon(QObject *parent = 0); + QDeclarativePlaceIcon(const QPlaceIcon &src, QDeclarativeGeoServiceProvider *plugin, QObject *parent = 0); + ~QDeclarativePlaceIcon(); + + QPlaceIcon icon() const; + void setIcon(const QPlaceIcon &src); + + Q_INVOKABLE QUrl url(const QSize &size = QSize()) const; + + QQmlPropertyMap *parameters() const; + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + +Q_SIGNALS: + void pluginChanged(); + void parametersChanged(); //in practice is never emitted since parameters cannot be re-assigned + //the declaration is needed to avoid warnings about non-notifyable properties + +private Q_SLOTS: + void pluginReady(); + +private: + QPlaceManager *manager() const; + void initParameters(const QVariantMap ¶meterMap); + QDeclarativeGeoServiceProvider *m_plugin; + QQmlPropertyMap *m_parameters; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceimagemodel.cpp b/src/imports/location/declarativeplaces/qdeclarativeplaceimagemodel.cpp new file mode 100644 index 0000000..4da3708 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceimagemodel.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativeplaceimagemodel_p.h" +#include "qdeclarativesupplier_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ImageModel + \instantiates QDeclarativePlaceImageModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-models + \since Qt Location 5.5 + + \brief The ImageModel type provides a model of place images. + + The ImageModel is a read-only model used to fetch images related to a \l Place. + Binding a \l Place via \l ImageModel::place initiates an initial fetch of images. + The model performs fetches incrementally and is intended to be used in conjunction + with a View such as a \l ListView. When the View reaches the last of the images + currently in the model, a fetch is performed to retrieve more if they are available. + The View is automatically updated as the images are received. The number of images + which are fetched at a time is specified by the \l batchSize property. The total number + of images available can be accessed via the \l totalCount property. + + The model returns data for the following roles: + + \table + \header + \li Role + \li Type + \li Description + \row + \li url + \li url + \li The URL of the image. + \row + \li imageId + \li string + \li The identifier of the image. + \row + \li mimeType + \li string + \li The MIME type of the image. + \row + \li supplier + \li \l Supplier + \li The supplier of the image. + \row + \li user + \li \l {QtLocation::User}{User} + \li The user who contributed the image. + \row + \li attribution + \li string + \li Attribution text which must be displayed when displaying the image. + \endtable + + + \section1 Example + + The following example shows how to display images for a place: + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml ImageModel +*/ + +/*! + \qmlproperty Place ImageModel::place + + This property holds the Place that the images are for. +*/ + +/*! + \qmlproperty int ImageModel::batchSize + + This property holds the batch size to use when fetching more image items. +*/ + +/*! + \qmlproperty int ImageModel::totalCount + + This property holds the total number of image items for the place. +*/ + +QDeclarativePlaceImageModel::QDeclarativePlaceImageModel(QObject *parent) +: QDeclarativePlaceContentModel(QPlaceContent::ImageType, parent) +{ +} + +QDeclarativePlaceImageModel::~QDeclarativePlaceImageModel() +{ + qDeleteAll(m_suppliers); +} + +/*! + \internal +*/ +QVariant QDeclarativePlaceImageModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= rowCount(index.parent()) || index.row() < 0) + return QVariant(); + + const QPlaceImage &image = m_content.value(index.row()); + + switch (role) { + case UrlRole: + return image.url(); + case ImageIdRole: + return image.imageId(); + case MimeTypeRole: + return image.mimeType(); + } + + return QDeclarativePlaceContentModel::data(index, role); +} + +QHash QDeclarativePlaceImageModel::roleNames() const +{ + QHash roles = QDeclarativePlaceContentModel::roleNames(); + roles.insert(UrlRole, "url"); + roles.insert(ImageIdRole, "imageId"); + roles.insert(MimeTypeRole, "mimeType"); + return roles; +} + +QT_END_NAMESPACE diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceimagemodel_p.h b/src/imports/location/declarativeplaces/qdeclarativeplaceimagemodel_p.h new file mode 100644 index 0000000..8f8f3f4 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceimagemodel_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPLACEIMAGEMODEL_P_H +#define QDECLARATIVEPLACEIMAGEMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativeplacecontentmodel.h" + +QT_BEGIN_NAMESPACE + +class QDeclarativeSupplier; + +class QDeclarativePlaceImageModel : public QDeclarativePlaceContentModel +{ + Q_OBJECT + +public: + explicit QDeclarativePlaceImageModel(QObject *parent = 0); + ~QDeclarativePlaceImageModel(); + + QVariant data(const QModelIndex &index, int role) const; + QHash roleNames() const; + + enum Roles { + UrlRole = UserRole, + ImageIdRole, + MimeTypeRole + }; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceuser.cpp b/src/imports/location/declarativeplaces/qdeclarativeplaceuser.cpp new file mode 100644 index 0000000..d8bae3f --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceuser.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativeplaceuser_p.h" + +QT_USE_NAMESPACE + +/*! + \qmltype User + \instantiates QDeclarativePlaceUser + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + \since Qt Location 5.5 + + \brief The User type identifies a user who contributed a particular \l Place content item. + + Each \l Place content item has an associated user who contributed the content. This type + provides information about that user. + + \sa ImageModel, ReviewModel, EditorialModel + + \section1 Example + + The following example shows how to display information about the user who + submitted an editorial: + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml EditorialModel +*/ + +QDeclarativePlaceUser::QDeclarativePlaceUser(QObject *parent) + : QObject(parent) {} + +QDeclarativePlaceUser::QDeclarativePlaceUser(const QPlaceUser &user, + QObject *parent) + : QObject(parent), + m_user(user) {} + +QDeclarativePlaceUser::~QDeclarativePlaceUser() {} + +/*! + \qmlproperty QPlaceUser QtLocation::User::user + + For details on how to use this property to interface between C++ and QML see + "\l {User - QPlaceUser} {Interfaces between C++ and QML Code}". +*/ +void QDeclarativePlaceUser::setUser(const QPlaceUser &user) +{ + QPlaceUser previousUser = m_user; + m_user = user; + + if (m_user.userId() != previousUser.userId()) + emit userIdChanged(); + + if (m_user.name() != previousUser.name()) + emit nameChanged(); +} + +QPlaceUser QDeclarativePlaceUser::user() const +{ + return m_user; +} + +/*! + \qmlproperty string QtLocation::User::userId + + This property holds the unique identifier of the user. +*/ + +void QDeclarativePlaceUser::setUserId(const QString &id) +{ + if (m_user.userId() == id) + return; + + m_user.setUserId(id); + emit userIdChanged(); +} + +QString QDeclarativePlaceUser::userId() const +{ + return m_user.userId(); +} + +/*! + \qmlproperty string QtLocation::User::name + + This property holds the name of a user. +*/ +void QDeclarativePlaceUser::setName(const QString &name) +{ + if (m_user.name() == name) + return; + + m_user.setName(name); + emit nameChanged(); +} + +QString QDeclarativePlaceUser::name() const +{ + return m_user.name(); +} + diff --git a/src/imports/location/declarativeplaces/qdeclarativeplaceuser_p.h b/src/imports/location/declarativeplaces/qdeclarativeplaceuser_p.h new file mode 100644 index 0000000..6c6ecec --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeplaceuser_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPLACEUSER_P_H +#define QDECLARATIVEPLACEUSER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativePlaceUser : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPlaceUser user READ user WRITE setUser) + Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + +public: + explicit QDeclarativePlaceUser(QObject *parent = 0); + explicit QDeclarativePlaceUser(const QPlaceUser &src, QObject *parent = 0); + ~QDeclarativePlaceUser(); + + QPlaceUser user() const; + void setUser(const QPlaceUser &src); + + QString userId() const; + void setUserId(const QString &id); + + QString name() const; + void setName(const QString &name); + +Q_SIGNALS: + void userIdChanged(); + void nameChanged(); + +private: + QPlaceUser m_user; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativePlaceUser) + +#endif diff --git a/src/imports/location/declarativeplaces/qdeclarativeratings.cpp b/src/imports/location/declarativeplaces/qdeclarativeratings.cpp new file mode 100644 index 0000000..ff04412 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeratings.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativeratings_p.h" + +QT_USE_NAMESPACE + +/*! + \qmltype Ratings + \instantiates QDeclarativeRatings + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + \since Qt Location 5.5 + + \brief The Ratings type holds place rating information. + + Rating information is used to describe how \e good a place is conceived to be. Typically this + information is visualized as a number of stars. The \l average property gives an aggregated + ratings value out of a possible maximum as given by the \l maximum property. + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml Ratings +*/ + +QDeclarativeRatings::QDeclarativeRatings(QObject *parent) + : QObject(parent) {} + +QDeclarativeRatings::QDeclarativeRatings(const QPlaceRatings &rating, + QObject *parent) + : QObject(parent), + m_ratings(rating) {} + +QDeclarativeRatings::~QDeclarativeRatings() {} + +/*! + \qmlproperty QPlaceRatings Ratings::ratings + + For details on how to use this property to interface between C++ and QML see + "\l {Ratings - QPlaceRatings} {Interfaces between C++ and QML Code}". +*/ +void QDeclarativeRatings::setRatings(const QPlaceRatings &ratings) +{ + QPlaceRatings previous = m_ratings; + m_ratings = ratings; + + if (ratings.average() != previous.average()) { + emit averageChanged(); + } + if (ratings.count() != previous.count()) { + emit countChanged(); + } +} + +QPlaceRatings QDeclarativeRatings::ratings() const +{ + return m_ratings; +} + +/*! + \qmlproperty real Ratings::average + + This property holds the average of the individual ratings. + + \sa maximum +*/ +void QDeclarativeRatings::setAverage(qreal average) +{ + if (m_ratings.average() != average) { + m_ratings.setAverage(average); + emit averageChanged(); + } +} + +qreal QDeclarativeRatings::average() const +{ + return m_ratings.average(); +} + +/*! + \qmlproperty real Ratings::maximum + + This property holds the maximum rating value. +*/ +void QDeclarativeRatings::setMaximum(qreal max) +{ + if (m_ratings.maximum() == max) + return; + + m_ratings.setMaximum(max); + emit maximumChanged(); +} + +qreal QDeclarativeRatings::maximum() const +{ + return m_ratings.maximum(); +} + +/*! + \qmlproperty int Ratings::count + + This property holds the total number of individual user ratings + used in determining the overall ratings \l average. +*/ +void QDeclarativeRatings::setCount(int count) +{ + if (m_ratings.count() != count) { + m_ratings.setCount(count); + emit countChanged(); + } +} + +int QDeclarativeRatings::count() const +{ + return m_ratings.count(); +} + diff --git a/src/imports/location/declarativeplaces/qdeclarativeratings_p.h b/src/imports/location/declarativeplaces/qdeclarativeratings_p.h new file mode 100644 index 0000000..7583e70 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativeratings_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVERATINGS_P_H +#define QDECLARATIVERATINGS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeRatings : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPlaceRatings ratings READ ratings WRITE setRatings) + Q_PROPERTY(qreal average READ average WRITE setAverage NOTIFY averageChanged) + Q_PROPERTY(qreal maximum READ maximum WRITE setMaximum NOTIFY maximumChanged) + Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged) + +public: + explicit QDeclarativeRatings(QObject *parent = 0); + explicit QDeclarativeRatings(const QPlaceRatings &src, QObject *parent = 0); + ~QDeclarativeRatings(); + + QPlaceRatings ratings() const; + void setRatings(const QPlaceRatings &src); + + qreal average() const; + void setAverage(qreal average); + + qreal maximum() const; + void setMaximum(qreal max); + + int count() const; + void setCount(int count); + +Q_SIGNALS: + void averageChanged(); + void maximumChanged(); + void countChanged(); + +private: + QPlaceRatings m_ratings; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeRatings) + +#endif // QDECLARATIVERATING_P_H diff --git a/src/imports/location/declarativeplaces/qdeclarativereviewmodel.cpp b/src/imports/location/declarativeplaces/qdeclarativereviewmodel.cpp new file mode 100644 index 0000000..b7237bc --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativereviewmodel.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativereviewmodel_p.h" +#include "qdeclarativesupplier_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ReviewModel + \instantiates QDeclarativeReviewModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-models + \since Qt Location 5.5 + + \brief Provides access to reviews of a \l Place. + + The ReviewModel is a read-only model used to fetch reviews about a \l Place. The model + incrementally fetches. The number of reviews which are fetched at a time is specified + by the \l batchSize property. The total number of reviews available can be accessed via the + \l totalCount property. + + To use the ReviewModel we need a view and a delegate. In this snippet we + see the setting up of a ListView with a ReviewModel model and a delegate. + + \snippet places/views/ReviewView.qml ReviewModel delegate + + The model returns data for the following roles: + + \table + \header + \li Role + \li Type + \li Description + \row + \li dateTime + \li datetime + \li The date and time that the review was posted. + \row + \li text + \li string + \li The review's textual description of the place. It can be either rich (HTML based) text or plain text + depending on the provider. + \row + \li language + \li string + \li The language that the review is written in. + \row + \li rating + \li real + \li The rating that the reviewer gave to the place. + \row + \li reviewId + \li string + \li The identifier of the review. + \row + \li title + \li string + \li The title of the review. + \row + \li supplier + \li \l Supplier + \li The supplier of the review. + \row + \li user + \li \l {QtLocation::User}{User} + \li The user who contributed the review. + \row + \li attribution + \li string + \li Attribution text which must be displayed when displaying the review. + \endtable +*/ + +/*! + \qmlproperty Place QtLocation::ReviewModel::place + + This property holds the Place that the reviews are for. +*/ + +/*! + \qmlproperty int QtLocation::ReviewModel::batchSize + + This property holds the batch size to use when fetching more reviews. +*/ + +/*! + \qmlproperty int QtLocation::ReviewModel::totalCount + + This property holds the total number of reviews for the place. +*/ + +QDeclarativeReviewModel::QDeclarativeReviewModel(QObject *parent) +: QDeclarativePlaceContentModel(QPlaceContent::ReviewType, parent) +{ +} + +QDeclarativeReviewModel::~QDeclarativeReviewModel() +{ + qDeleteAll(m_suppliers); +} + +/*! + \internal +*/ +QVariant QDeclarativeReviewModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= rowCount(index.parent()) || index.row() < 0) + return QVariant(); + + const QPlaceReview &review = m_content.value(index.row()); + + switch (role) { + case DateTimeRole: + return review.dateTime(); + case TextRole: + return review.text(); + case LanguageRole: + return review.language(); + case RatingRole: + return review.rating(); + case ReviewIdRole: + return review.reviewId(); + case TitleRole: + return review.title(); + } + + return QDeclarativePlaceContentModel::data(index, role); +} + +QHash QDeclarativeReviewModel::roleNames() const +{ + QHash roles = QDeclarativePlaceContentModel::roleNames(); + roles.insert(DateTimeRole, "dateTime"); + roles.insert(TextRole, "text"); + roles.insert(LanguageRole, "language"); + roles.insert(RatingRole, "rating"); + roles.insert(ReviewIdRole, "reviewId"); + roles.insert(TitleRole, "title"); + return roles; +} + +QT_END_NAMESPACE diff --git a/src/imports/location/declarativeplaces/qdeclarativereviewmodel_p.h b/src/imports/location/declarativeplaces/qdeclarativereviewmodel_p.h new file mode 100644 index 0000000..06bf7aa --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativereviewmodel_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEREVIEWMODEL_P_H +#define QDECLARATIVEREVIEWMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativeplacecontentmodel.h" + +QT_BEGIN_NAMESPACE + +class QDeclarativeReviewModel : public QDeclarativePlaceContentModel +{ + Q_OBJECT + +public: + explicit QDeclarativeReviewModel(QObject *parent = 0); + ~QDeclarativeReviewModel(); + + QVariant data(const QModelIndex &index, int role) const; + QHash roleNames() const; + enum Roles { + DateTimeRole = UserRole, + TextRole, + LanguageRole, + RatingRole, + ReviewIdRole, + TitleRole + }; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVEREVIEWMODEL_P_H diff --git a/src/imports/location/declarativeplaces/qdeclarativesearchmodelbase.cpp b/src/imports/location/declarativeplaces/qdeclarativesearchmodelbase.cpp new file mode 100644 index 0000000..acfb54d --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesearchmodelbase.cpp @@ -0,0 +1,358 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativesearchmodelbase.h" +#include "qdeclarativeplace_p.h" +#include "error_messages.h" + +#include +#include +#include +#include +#include +#include +#include + +QDeclarativeSearchModelBase::QDeclarativeSearchModelBase(QObject *parent) +: QAbstractListModel(parent), m_plugin(0), m_reply(0), m_complete(false), m_status(Null) +{ +} + +QDeclarativeSearchModelBase::~QDeclarativeSearchModelBase() +{ +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider *QDeclarativeSearchModelBase::plugin() const +{ + return m_plugin; +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (m_plugin == plugin) + return; + + initializePlugin(plugin); + + if (m_complete) + emit pluginChanged(); +} + +/*! + \internal +*/ +QVariant QDeclarativeSearchModelBase::searchArea() const +{ + QGeoShape s = m_request.searchArea(); + if (s.type() == QGeoShape::RectangleType) + return QVariant::fromValue(QGeoRectangle(s)); + else if (s.type() == QGeoShape::CircleType) + return QVariant::fromValue(QGeoCircle(s)); + else + return QVariant::fromValue(s); +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::setSearchArea(const QVariant &searchArea) +{ + QGeoShape s; + + if (searchArea.userType() == qMetaTypeId()) + s = searchArea.value(); + else if (searchArea.userType() == qMetaTypeId()) + s = searchArea.value(); + else if (searchArea.userType() == qMetaTypeId()) + s = searchArea.value(); + + if (m_request.searchArea() == s) + return; + + m_request.setSearchArea(s); + emit searchAreaChanged(); +} + +/*! + \internal +*/ +int QDeclarativeSearchModelBase::limit() const +{ + return m_request.limit(); +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::setLimit(int limit) +{ + if (m_request.limit() == limit) + return; + + m_request.setLimit(limit); + emit limitChanged(); +} + +/*! + \internal +*/ +bool QDeclarativeSearchModelBase::previousPagesAvailable() const +{ + return m_previousPageRequest != QPlaceSearchRequest(); +} + +/*! + \internal +*/ +bool QDeclarativeSearchModelBase::nextPagesAvailable() const +{ + return m_nextPageRequest != QPlaceSearchRequest(); +} + +/*! + \internal +*/ +QDeclarativeSearchModelBase::Status QDeclarativeSearchModelBase::status() const +{ + return m_status; +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::setStatus(Status status, const QString &errorString) +{ + Status prevStatus = m_status; + + m_status = status; + m_errorString = errorString; + + if (prevStatus != m_status) + emit statusChanged(); +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::update() +{ + if (m_reply) + return; + + setStatus(Loading); + + if (!m_plugin) { + clearData(); + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_PROPERTY_NOT_SET)); + return; + } + + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (!serviceProvider) { + clearData(); + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_PROVIDER_ERROR) + .arg(m_plugin->name())); + return; + } + + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (!placeManager) { + clearData(); + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_ERROR) + .arg(m_plugin->name()).arg(serviceProvider->errorString())); + return; + } + + m_reply = sendQuery(placeManager, m_request); + if (!m_reply) { + clearData(); + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, UNABLE_TO_MAKE_REQUEST)); + return; + } + + m_reply->setParent(this); + connect(m_reply, SIGNAL(finished()), this, SLOT(queryFinished())); +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::cancel() +{ + if (!m_reply) + return; + + if (!m_reply->isFinished()) + m_reply->abort(); + + if (m_reply) { + m_reply->deleteLater(); + m_reply = 0; + } + + setStatus(Ready); +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::reset() +{ + beginResetModel(); + clearData(); + setStatus(Null); + endResetModel(); +} + +/*! + \internal +*/ +QString QDeclarativeSearchModelBase::errorString() const +{ + return m_errorString; +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::previousPage() +{ + if (m_previousPageRequest == QPlaceSearchRequest()) + return; + + m_request = m_previousPageRequest; + update(); +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::nextPage() +{ + if (m_nextPageRequest == QPlaceSearchRequest()) + return; + + m_request = m_nextPageRequest; + update(); +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::clearData(bool suppressSignal) +{ + Q_UNUSED(suppressSignal) +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::classBegin() +{ +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::componentComplete() +{ + m_complete = true; +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::initializePlugin(QDeclarativeGeoServiceProvider *plugin) +{ + beginResetModel(); + if (plugin != m_plugin) { + if (m_plugin) + disconnect(m_plugin, SIGNAL(nameChanged(QString)), this, SLOT(pluginNameChanged())); + if (plugin) + connect(plugin, SIGNAL(nameChanged(QString)), this, SLOT(pluginNameChanged())); + m_plugin = plugin; + } + + if (m_plugin) { + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (serviceProvider) { + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (placeManager) { + if (placeManager->childCategoryIds().isEmpty()) { + QPlaceReply *reply = placeManager->initializeCategories(); + connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater())); + } + } + } + } + + endResetModel(); +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::pluginNameChanged() +{ + initializePlugin(m_plugin); +} + +/*! + \internal +*/ +void QDeclarativeSearchModelBase::setPreviousPageRequest(const QPlaceSearchRequest &previous) +{ + if (m_previousPageRequest == previous) + return; + + m_previousPageRequest = previous; + emit previousPagesAvailableChanged(); +} + +void QDeclarativeSearchModelBase::setNextPageRequest(const QPlaceSearchRequest &next) +{ + if (m_nextPageRequest == next) + return; + + m_nextPageRequest = next; + emit nextPagesAvailableChanged(); +} diff --git a/src/imports/location/declarativeplaces/qdeclarativesearchmodelbase.h b/src/imports/location/declarativeplaces/qdeclarativesearchmodelbase.h new file mode 100644 index 0000000..4547045 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesearchmodelbase.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVESEARCHMODELBASE_H +#define QDECLARATIVESEARCHMODELBASE_H + +#include +#include +#include +#include +#include + +#include "qdeclarativegeoserviceprovider_p.h" + +QT_BEGIN_NAMESPACE + +class QPlaceManager; +class QPlaceSearchRequest; +class QPlaceSearchReply; +class QDeclarativePlace; + +class QDeclarativeSearchModelBase : public QAbstractListModel, public QQmlParserStatus +{ + Q_OBJECT + + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(QVariant searchArea READ searchArea WRITE setSearchArea NOTIFY searchAreaChanged) + Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged) + Q_PROPERTY(bool previousPagesAvailable READ previousPagesAvailable NOTIFY previousPagesAvailableChanged) + Q_PROPERTY(bool nextPagesAvailable READ nextPagesAvailable NOTIFY nextPagesAvailableChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + + Q_ENUMS(Status) + + Q_INTERFACES(QQmlParserStatus) + +public: + enum Status { + Null, + Ready, + Loading, + Error + }; + + explicit QDeclarativeSearchModelBase(QObject *parent = 0); + ~QDeclarativeSearchModelBase(); + + QDeclarativeGeoServiceProvider *plugin() const; + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + + QVariant searchArea() const; + void setSearchArea(const QVariant &searchArea); + + int limit() const; + void setLimit(int limit); + + bool previousPagesAvailable() const; + bool nextPagesAvailable() const; + + Status status() const; + void setStatus(Status status, const QString &errorString = QString()); + + Q_INVOKABLE void update(); + + Q_INVOKABLE void cancel(); + Q_INVOKABLE void reset(); + + Q_INVOKABLE QString errorString() const; + + Q_INVOKABLE void previousPage(); + Q_INVOKABLE void nextPage(); + + virtual void clearData(bool suppressSignal = false); + + // From QQmlParserStatus + virtual void classBegin(); + virtual void componentComplete(); + +Q_SIGNALS: + void pluginChanged(); + void searchAreaChanged(); + void limitChanged(); + void previousPagesAvailableChanged(); + void nextPagesAvailableChanged(); + void statusChanged(); + +protected: + virtual void initializePlugin(QDeclarativeGeoServiceProvider *plugin); + +protected Q_SLOTS: + virtual void queryFinished() = 0; + +private Q_SLOTS: + void pluginNameChanged(); + +protected: + virtual QPlaceReply *sendQuery(QPlaceManager *manager, const QPlaceSearchRequest &request) = 0; + void setPreviousPageRequest(const QPlaceSearchRequest &previous); + void setNextPageRequest(const QPlaceSearchRequest &next); + + QPlaceSearchRequest m_request; + QDeclarativeGeoServiceProvider *m_plugin; + QPlaceReply *m_reply; + +private: + bool m_complete; + Status m_status; + QString m_errorString; + QPlaceSearchRequest m_previousPageRequest; + QPlaceSearchRequest m_nextPageRequest; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVESEARCHMODELBASE_H diff --git a/src/imports/location/declarativeplaces/qdeclarativesearchresultmodel.cpp b/src/imports/location/declarativeplaces/qdeclarativesearchresultmodel.cpp new file mode 100644 index 0000000..3c7c261 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesearchresultmodel.cpp @@ -0,0 +1,916 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativesearchresultmodel_p.h" +#include "qdeclarativeplace_p.h" +#include "qdeclarativeplaceicon_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +/*! + \qmltype PlaceSearchModel + \instantiates QDeclarativeSearchResultModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-models + \since Qt Location 5.5 + + \brief Provides access to place search results. + + PlaceSearchModel provides a model of place search results within the \l searchArea. The + \l searchTerm and \l categories properties can be set to restrict the search results to + places matching those criteria. + + The PlaceSearchModel returns both sponsored and + \l {http://en.wikipedia.org/wiki/Organic_search}{organic search results}. Sponsored search + results will have the \c sponsored role set to true. + + \target PlaceSearchModel Roles + The model returns data for the following roles: + + \table + \header + \li Role + \li Type + \li Description + \row + \li type + \li enum + \li The type of search result. + \row + \li title + \li string + \li A string describing the search result. + \row + \li icon + \li PlaceIcon + \li Icon representing the search result. + \row + \li distance + \li real + \li Valid only when the \c type role is \c PlaceResult, the distance to the place + from the center of the \l searchArea. If no \l searchArea + has been specified, the distance is NaN. + \row + \li place + \li \l Place + \li Valid only when the \c type role is \c PlaceResult, an object representing the + place. + \row + \li sponsored + \li bool + \li Valid only when the \c type role is \c PlaceResult, true if the search result is a + sponsored result. + \endtable + + \section2 Search Result Types + + The \c type role can take on the following values: + + \table + \row + \li PlaceSearchModel.UnknownSearchResult + \li The contents of the search result are unknown. + \row + \li PlaceSearchModel.PlaceResult + \li The search result contains a place. + \row + \li PlaceSearchModel.ProposedSearchResult + \li The search result contains a proposed search which may be relevant. + \endtable + + + It can often be helpful to use a \l Loader to create a delegate + that will choose different \l {Component}s based on the search result type. + + \snippet declarative/places_loader.qml Handle Result Types + + \section1 Detection of Updated and Removed Places + + The PlaceSearchModel listens for places that have been updated or removed from its plugin's backend. + If it detects that a place has been updated and that place is currently present in the model, then + it will call \l Place::getDetails to refresh the details. If it detects that a place has been + removed, then correspondingly the place will be removed from the model if it is currently + present. + + \section1 Example + + The following example shows how to use the PlaceSearchModel to search for Pizza restaurants in + close proximity of a given position. A \l searchTerm and \l searchArea are provided to the model + and \l update() is used to perform a lookup query. Note that the model does not incrementally + fetch search results, but rather performs a single fetch when \l update() is run. The \l count + is set to the number of search results returned during the fetch. + + \snippet places_list/places_list.qml Imports + \codeline + \snippet places_list/places_list.qml PlaceSearchModel + + \sa CategoryModel, {QPlaceManager} + + \section1 Paging + The PlaceSearchModel API has some limited support + for paging. The \l nextPage() and \l previousPage() functions as well as + the \l limit property can be used to access + paged search results. When the \l limit property is set + the search result page contains at most \l limit entries (of type place result). + For example, if the backend has 5 search results in total + [a,b,c,d,e], and assuming the first page is shown and limit of 3 has been set + then a,b,c is returned. The \l nextPage() would return d,e. The + \l nextPagesAvailable and \l previousPagesAvailable properties + can be used to check for further pages. At the moment the API does not + support the means to retrieve the total number of items available from the + backed. Note that support for \l nextPage(), previousPage() and \l limit can vary + according to the \l plugin. +*/ + +/*! + \qmlproperty Plugin PlaceSearchModel::plugin + + This property holds the \l Plugin which will be used to perform the search. +*/ + +/*! + \qmlproperty Plugin PlaceSearchModel::favoritesPlugin + + This property holds the \l Plugin which will be used to search for favorites. + Any places from the search which can be cross-referenced or matched + in the favoritesPlugin will have their \l {Place::favorite}{favorite} property + set to the corresponding \l Place from the favoritesPlugin. + + If the favoritesPlugin is not set, the \l {Place::favorite}{favorite} property + of the places in the results will always be null. + + \sa Favorites +*/ + +/*! + \qmlproperty VariantMap PlaceSearchModel::favoritesMatchParameters + + This property holds a set of parameters used to specify how search result places + are matched to favorites in the favoritesPlugin. + + By default the parameter map is empty and implies that the favorites plugin + matches by \l {Alternative Identifier Cross-Referencing}{alternative identifiers}. Generally, + an application developer will not need to set this property. + + In cases where the favorites plugin does not support matching by alternative identifiers, + then the \l {Qt Location#Plugin References and Parameters}{plugin documentation} should + be consulted to see precisely what key-value parameters to set. +*/ + +/*! + \qmlproperty variant PlaceSearchModel::searchArea + + This property holds the search area. The search result returned by the model will be within + the search area. + + If this property is set to a \l {geocircle} its + \l {geocircle}{radius} property may be left unset, in which case the \l Plugin + will choose an appropriate radius for the search. + + Support for specifying a search area can vary according to the \l plugin backend + implementation. For example, some may support a search center only while others may only + support geo rectangles. +*/ + +/*! + \qmlproperty int PlaceSearchModel::limit + + This property holds the limit of the number of items that will be returned. +*/ + +/*! + \qmlproperty bool PlaceSearchModel::previousPagesAvailable + + This property holds whether there is one or more previous pages of search results available. + + \sa previousPage() +*/ + +/*! + \qmlproperty bool PlaceSearchModel::nextPagesAvailable + + This property holds whether there is one or more additional pages of search results available. + + \sa nextPage() +*/ + +/*! + \qmlproperty enum PlaceSearchModel::status + + This property holds the status of the model. It can be one of: + + \table + \row + \li PlaceSearchModel.Null + \li No search query has been executed. The model is empty. + \row + \li PlaceSearchModel.Ready + \li The search query has completed, and the results are available. + \row + \li PlaceSearchModel.Loading + \li A search query is currently being executed. + \row + \li PlaceSearchModel.Error + \li An error occurred when executing the previous search query. + \endtable +*/ + +/*! + \qmlmethod void PlaceSearchModel::update() + + Updates the model based on the provided query parameters. The model will be populated with a + list of places matching the search parameters specified by the type's properties. Search + criteria is specified by setting properties such as the \l searchTerm, \l categories, \l searchArea and \l limit. + Support for these properties may vary according to \l plugin. \c update() then + submits the set of criteria to the \l plugin to process. + + While the model is updating the \l status of the model is set to + \c PlaceSearchModel.Loading. If the model is successfully updated the \l status is set to + \c PlaceSearchModel.Ready, while if it unsuccessfully completes, the \l status is set to + \c PlaceSearchModel.Error and the model cleared. + + \code + PlaceSearchModel { + id: model + plugin: backendPlugin + searchArea: QtPositioning.circle(QtPositioning.coordinate(10, 10)) + ... + } + + MouseArea { + ... + onClicked: { + model.searchTerm = "pizza"; + model.categories = null; //not searching by any category + model.searchArea.center.latitude = -27.5; + model.searchArea.center.longitude = 153; + model.update(); + } + } + \endcode + + \sa cancel(), status +*/ + +/*! + \qmlmethod void PlaceSearchModel::cancel() + + Cancels an ongoing search operation immediately and sets the model + status to PlaceSearchModel.Ready. The model retains any search + results it had before the operation was started. + + If an operation is not ongoing, invoking cancel() has no effect. + + \sa update(), status +*/ + +/*! + \qmlmethod void PlaceSearchModel::reset() + + Resets the model. All search results are cleared, any outstanding requests are aborted and + possible errors are cleared. Model status will be set to PlaceSearchModel.Null. +*/ + +/*! + \qmlmethod string PlaceSearchModel::errorString() const + + This read-only property holds the textual presentation of the latest place search model error. + If no error has occurred or if the model was cleared, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. +*/ + +/*! + \qmlmethod void PlaceSearchModel::previousPage() + + Updates the model to display the previous page of search results. If there is no previous page + then this method does nothing. +*/ + +/*! + \qmlmethod void PlaceSearchModel::nextPage() + + Updates the model to display the next page of search results. If there is no next page then + this method does nothing. +*/ + +QDeclarativeSearchResultModel::QDeclarativeSearchResultModel(QObject *parent) + : QDeclarativeSearchModelBase(parent), m_favoritesPlugin(0) +{ +} + +QDeclarativeSearchResultModel::~QDeclarativeSearchResultModel() +{ +} + +/*! + \qmlproperty string PlaceSearchModel::searchTerm + + This property holds search term used in query. The search term is a free-form text string. +*/ +QString QDeclarativeSearchResultModel::searchTerm() const +{ + return m_request.searchTerm(); +} + +void QDeclarativeSearchResultModel::setSearchTerm(const QString &searchTerm) +{ + m_request.setSearchContext(QVariant()); + + if (m_request.searchTerm() == searchTerm) + return; + + m_request.setSearchTerm(searchTerm); + emit searchTermChanged(); +} + +/*! + \qmlproperty list PlaceSearchModel::categories + + This property holds a list of categories to be used when searching. Returned search results + will be for places that match at least one of the categories. +*/ +QQmlListProperty QDeclarativeSearchResultModel::categories() +{ + return QQmlListProperty(this, + 0, // opaque data parameter + categories_append, + categories_count, + category_at, + categories_clear); +} + +void QDeclarativeSearchResultModel::categories_append(QQmlListProperty *list, + QDeclarativeCategory *declCategory) +{ + QDeclarativeSearchResultModel *searchModel = qobject_cast(list->object); + if (searchModel && declCategory) { + searchModel->m_request.setSearchContext(QVariant()); + searchModel->m_categories.append(declCategory); + QList categories = searchModel->m_request.categories(); + categories.append(declCategory->category()); + searchModel->m_request.setCategories(categories); + emit searchModel->categoriesChanged(); + } +} + +int QDeclarativeSearchResultModel::categories_count(QQmlListProperty *list) +{ + QDeclarativeSearchResultModel *searchModel = qobject_cast(list->object); + if (searchModel) + return searchModel->m_categories.count(); + else + return -1; +} + +QDeclarativeCategory *QDeclarativeSearchResultModel::category_at(QQmlListProperty *list, + int index) +{ + QDeclarativeSearchResultModel *searchModel = qobject_cast(list->object); + if (searchModel && (searchModel->m_categories.count() > index) && (index > -1)) + return searchModel->m_categories.at(index); + else + return 0; +} + +void QDeclarativeSearchResultModel::categories_clear(QQmlListProperty *list) +{ + QDeclarativeSearchResultModel *searchModel = qobject_cast(list->object); + if (searchModel) { + //note: we do not need to delete each of the objects in m_categories since the search model + //should never be the parent of the categories anyway. + searchModel->m_request.setSearchContext(QVariant()); + searchModel->m_categories.clear(); + searchModel->m_request.setCategories(QList()); + emit searchModel->categoriesChanged(); + } +} + +/*! + \qmlproperty string PlaceSearchModel::recommendationId + + This property holds the placeId to be used in order to find recommendations + for similar places. +*/ +QString QDeclarativeSearchResultModel::recommendationId() const +{ + return m_request.recommendationId(); +} + +void QDeclarativeSearchResultModel::setRecommendationId(const QString &placeId) +{ + if (m_request.recommendationId() == placeId) + return; + + m_request.setRecommendationId(placeId); + emit recommendationIdChanged(); +} + +/*! + \qmlproperty enumeration PlaceSearchModel::relevanceHint + + This property holds a relevance hint used in the search query. The hint is given to the + provider to help but not dictate the ranking of results. For example, the distance hint may + give closer places a higher ranking but it does not necessarily mean the results will be + strictly ordered according to distance. A provider may ignore the hint altogether. + + \table + \row + \li SearchResultModel.UnspecifiedHint + \li No relevance hint is given to the provider. + \row + \li SearchResultModel.DistanceHint + \li The distance of the place from the user's current location is important to the user. + This hint is only meaningful when a circular search area is used. + \row + \li SearchResultModel.LexicalPlaceNameHint + \li The lexical ordering of place names (in ascending alphabetical order) is relevant to + the user. This hint is useful for providers based on a local data store. + \endtable +*/ +QDeclarativeSearchResultModel::RelevanceHint QDeclarativeSearchResultModel::relevanceHint() const +{ + return static_cast(m_request.relevanceHint()); +} + +void QDeclarativeSearchResultModel::setRelevanceHint(QDeclarativeSearchResultModel::RelevanceHint hint) +{ + if (m_request.relevanceHint() != static_cast(hint)) { + m_request.setRelevanceHint(static_cast(hint)); + emit relevanceHintChanged(); + } +} + +/*! + \qmlproperty enum PlaceSearchModel::visibilityScope + + This property holds the visibility scope of the places to search. Only places with the + specified visibility will be returned in the search results. + + The visibility scope can be one of: + + \table + \row + \li Place.UnspecifiedVisibility + \li No explicit visibility scope specified, places with any visibility may be part of + search results. + \row + \li Place.DeviceVisibility + \li Only places stored on the local device will be part of the search results. + \row + \li Place.PrivateVisibility + \li Only places that are private to the current user will be part of the search results. + \row + \li Place.PublicVisibility + \li Only places that are public will be part of the search results. + \endtable +*/ +QDeclarativePlace::Visibility QDeclarativeSearchResultModel::visibilityScope() const +{ + return QDeclarativePlace::Visibility(int(m_visibilityScope)); +} + +void QDeclarativeSearchResultModel::setVisibilityScope(QDeclarativePlace::Visibility visibilityScope) +{ + QLocation::VisibilityScope scope = QLocation::VisibilityScope(visibilityScope); + + if (m_visibilityScope == scope) + return; + + m_visibilityScope = scope; + emit visibilityScopeChanged(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider *QDeclarativeSearchResultModel::favoritesPlugin() const +{ + return m_favoritesPlugin; +} + +/*! + \internal +*/ +void QDeclarativeSearchResultModel::setFavoritesPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + + if (m_favoritesPlugin == plugin) + return; + + m_favoritesPlugin = plugin; + + if (m_favoritesPlugin) { + QGeoServiceProvider *serviceProvider = m_favoritesPlugin->sharedGeoServiceProvider(); + if (serviceProvider) { + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (placeManager) { + if (placeManager->childCategoryIds().isEmpty()) { + QPlaceReply *reply = placeManager->initializeCategories(); + connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater())); + } + } + } + } + + emit favoritesPluginChanged(); +} + +/*! + \internal +*/ +QVariantMap QDeclarativeSearchResultModel::favoritesMatchParameters() const +{ + return m_matchParameters; +} + +/*! + \internal +*/ +void QDeclarativeSearchResultModel::setFavoritesMatchParameters(const QVariantMap ¶meters) +{ + if (m_matchParameters == parameters) + return; + + m_matchParameters = parameters; + emit favoritesMatchParametersChanged(); +} + +/*! + \internal +*/ +int QDeclarativeSearchResultModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return m_results.count(); +} + +void QDeclarativeSearchResultModel::clearData(bool suppressSignal) +{ + QDeclarativeSearchModelBase::clearData(suppressSignal); + + qDeleteAll(m_places); + m_places.clear(); + qDeleteAll(m_icons); + m_icons.clear(); + if (!m_results.isEmpty()) { + m_results.clear(); + + if (!suppressSignal) + emit rowCountChanged(); + } +} + +QVariant QDeclarativeSearchResultModel::data(const QModelIndex &index, int role) const +{ + if (index.row() > m_results.count()) + return QVariant(); + + const QPlaceSearchResult &result = m_results.at(index.row()); + + switch (role) { + case SearchResultTypeRole: + return result.type(); + case Qt::DisplayRole: + case TitleRole: + return result.title(); + case IconRole: + return QVariant::fromValue(static_cast(m_icons.at(index.row()))); + case DistanceRole: + if (result.type() == QPlaceSearchResult::PlaceResult) { + QPlaceResult placeResult = result; + return placeResult.distance(); + } + break; + case PlaceRole: + if (result.type() == QPlaceSearchResult::PlaceResult) + return QVariant::fromValue(static_cast(m_places.at(index.row()))); + case SponsoredRole: + if (result.type() == QPlaceSearchResult::PlaceResult) { + QPlaceResult placeResult = result; + return placeResult.isSponsored(); + } + break; + } + return QVariant(); +} + +/*! + \internal +*/ +QVariant QDeclarativeSearchResultModel::data(int index, const QString &role) const +{ + QModelIndex modelIndex = createIndex(index, 0); + return data(modelIndex, roleNames().key(role.toLatin1())); +} + +QHash QDeclarativeSearchResultModel::roleNames() const +{ + QHash roles = QDeclarativeSearchModelBase::roleNames(); + roles.insert(SearchResultTypeRole, "type"); + roles.insert(TitleRole, "title"); + roles.insert(IconRole, "icon"); + roles.insert(DistanceRole, "distance"); + roles.insert(PlaceRole, "place"); + roles.insert(SponsoredRole, "sponsored"); + + return roles; +} + +/*! + \qmlmethod void PlaceSearchModel::updateWith(int proposedSearchIndex) + + Updates the model based on the ProposedSearchResult at index \a proposedSearchIndex. The model + will be populated with a list of places matching the proposed search. Model status will be set + to PlaceSearchModel.Loading. If the model is updated successfully status will be set to + PlaceSearchModel.Ready. If an error occurs status will be set to PlaceSearchModel.Error and the + model cleared. + + If \a proposedSearchIndex does not reference a ProposedSearchResult this method does nothing. +*/ +void QDeclarativeSearchResultModel::updateWith(int proposedSearchIndex) +{ + if (m_results.at(proposedSearchIndex).type() != QPlaceSearchResult::ProposedSearchResult) + return; + + m_request = QPlaceProposedSearchResult(m_results.at(proposedSearchIndex)).searchRequest(); + update(); +} + +QPlaceReply *QDeclarativeSearchResultModel::sendQuery(QPlaceManager *manager, + const QPlaceSearchRequest &request) +{ + Q_ASSERT(manager); + return manager->search(request); +} + +/*! + \internal +*/ +void QDeclarativeSearchResultModel::initializePlugin(QDeclarativeGeoServiceProvider *plugin) +{ + //disconnect the manager of the old plugin if we have one + if (m_plugin) { + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (serviceProvider) { + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (placeManager) { + disconnect(placeManager, SIGNAL(placeUpdated(QString)), this, SLOT(placeUpdated(QString))); + disconnect(placeManager, SIGNAL(placeRemoved(QString)), this, SLOT(placeRemoved(QString))); + connect(placeManager, SIGNAL(dataChanged()), this, SIGNAL(dataChanged())); + } + } + } + + //connect to the manager of the new plugin. + if (plugin) { + QGeoServiceProvider *serviceProvider = plugin->sharedGeoServiceProvider(); + if (serviceProvider) { + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (placeManager) { + connect(placeManager, SIGNAL(placeUpdated(QString)), this, SLOT(placeUpdated(QString))); + connect(placeManager, SIGNAL(placeRemoved(QString)), this, SLOT(placeRemoved(QString))); + disconnect(placeManager, SIGNAL(dataChanged()), this, SIGNAL(dataChanged())); + } + } + } + QDeclarativeSearchModelBase::initializePlugin(plugin); +} + +/*! + \internal +*/ +void QDeclarativeSearchResultModel::queryFinished() +{ + if (!m_reply) + return; + QPlaceReply *reply = m_reply; + m_reply = 0; + if (reply->error() != QPlaceReply::NoError) { + m_resultsBuffer.clear(); + updateLayout(); + setStatus(Error, reply->errorString()); + reply->deleteLater(); + return; + } + + if (reply->type() == QPlaceReply::SearchReply) { + QPlaceSearchReply *searchReply = qobject_cast(reply); + Q_ASSERT(searchReply); + + m_resultsBuffer = searchReply->results(); + setPreviousPageRequest(searchReply->previousPageRequest()); + setNextPageRequest(searchReply->nextPageRequest()); + + reply->deleteLater(); + + if (!m_favoritesPlugin) { + updateLayout(); + setStatus(Ready); + } else { + QGeoServiceProvider *serviceProvider = m_favoritesPlugin->sharedGeoServiceProvider(); + if (!serviceProvider) { + updateLayout(); + setStatus(Error, QStringLiteral("Favorites plugin returns a null QGeoServiceProvider instance")); + return; + } + + QPlaceManager *favoritesManager = serviceProvider->placeManager(); + if (!favoritesManager) { + updateLayout(); + setStatus(Error, QStringLiteral("Favorites plugin returns a null QPlaceManager")); + return; + } + + QPlaceMatchRequest request; + if (m_matchParameters.isEmpty()) { + if (!m_plugin) { + reply->deleteLater(); + setStatus(Error, QStringLiteral("Plugin not assigned")); + return; + } + + QVariantMap params; + params.insert(QPlaceMatchRequest::AlternativeId, QString::fromLatin1("x_id_") + m_plugin->name()); + request.setParameters(params); + } else { + request.setParameters(m_matchParameters); + } + + request.setResults(m_resultsBuffer); + m_reply = favoritesManager->matchingPlaces(request); + connect(m_reply, SIGNAL(finished()), this, SLOT(queryFinished())); + } + } else if (reply->type() == QPlaceReply::MatchReply) { + QPlaceMatchReply *matchReply = qobject_cast(reply); + Q_ASSERT(matchReply); + updateLayout(matchReply->places()); + setStatus(Ready); + reply->deleteLater(); + } else { + setStatus(Error, QStringLiteral("Unknown reply type")); + reply->deleteLater(); + } +} + +/*! + \qmlmethod void PlaceSearchModel::data(int index, string role) + + Returns the data for a given \a role at the specified row \a index. +*/ + +/*! + \qmlproperty int PlaceSearchModel::count + + This property holds the number of results the model has. + + Note that it does not refer to the total number of search results + available in the backend. The total number of search results + is not currently supported by the API. +*/ + +/*! + \internal + Note: m_results buffer should be correctly populated before + calling this function +*/ +void QDeclarativeSearchResultModel::updateLayout(const QList &favoritePlaces) +{ + int oldRowCount = rowCount(); + + beginResetModel(); + clearData(true); + m_results = m_resultsBuffer; + m_resultsBuffer.clear(); + + for (int i = 0; i < m_results.count(); ++i) { + const QPlaceSearchResult &result = m_results.at(i); + + if (result.type() == QPlaceSearchResult::PlaceResult) { + QPlaceResult placeResult = result; + QDeclarativePlace *place = new QDeclarativePlace(placeResult.place(), plugin(), this); + m_places.append(place); + + if ((favoritePlaces.count() == m_results.count()) && favoritePlaces.at(i) != QPlace()) + m_places[i]->setFavorite(new QDeclarativePlace(favoritePlaces.at(i), + m_favoritesPlugin, m_places[i])); + } else if (result.type() == QPlaceSearchResult::ProposedSearchResult) { + m_places.append(0); + } + + QDeclarativePlaceIcon *icon = 0; + if (!result.icon().isEmpty()) + icon = new QDeclarativePlaceIcon(result.icon(), plugin(), this); + m_icons.append(icon); + } + + endResetModel(); + if (m_results.count() != oldRowCount) + emit rowCountChanged(); +} + +/*! + \internal +*/ +void QDeclarativeSearchResultModel::placeUpdated(const QString &placeId) +{ + int row = getRow(placeId); + if (row < 0 || row > m_places.count()) + return; + + if (m_places.at(row)) + m_places.at(row)->getDetails(); +} + +/*! + \internal +*/ +void QDeclarativeSearchResultModel::placeRemoved(const QString &placeId) +{ + int row = getRow(placeId); + if (row < 0 || row > m_places.count()) + return; + + beginRemoveRows(QModelIndex(), row, row); + delete m_places.at(row); + m_places.removeAt(row); + m_results.removeAt(row); + endRemoveRows(); + + emit rowCountChanged(); +} + +/*! + \internal +*/ +int QDeclarativeSearchResultModel::getRow(const QString &placeId) const +{ + for (int i = 0; i < m_places.count(); ++i) { + if (!m_places.at(i)) + continue; + else if (m_places.at(i)->placeId() == placeId) + return i; + } + + return -1; +} + +/*! + \qmlsignal PlaceSearchResultModel::dataChanged() + + This signal is emitted when significant changes have been made to the underlying datastore. + + Applications should act on this signal at their own discretion. The data + provided by the model could be out of date and so the model should be reupdated + sometime, however an immediate reupdate may be disconcerting to users if the results + change without any action on their part. + + The corresponding handler is \c onDataChanged. +*/ + diff --git a/src/imports/location/declarativeplaces/qdeclarativesearchresultmodel_p.h b/src/imports/location/declarativeplaces/qdeclarativesearchresultmodel_p.h new file mode 100644 index 0000000..73bf16d --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesearchresultmodel_p.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVESEARCHRESULTMODEL_P_H +#define QDECLARATIVESEARCHRESULTMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativesearchmodelbase.h" +#include "qdeclarativecategory_p.h" +#include "qdeclarativeplace_p.h" +#include "qdeclarativeplaceicon_p.h" + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoServiceProvider; + +class QDeclarativeSearchResultModel : public QDeclarativeSearchModelBase +{ + Q_OBJECT + + Q_PROPERTY(QString searchTerm READ searchTerm WRITE setSearchTerm NOTIFY searchTermChanged) + Q_PROPERTY(QQmlListProperty categories READ categories NOTIFY categoriesChanged) + Q_PROPERTY(QString recommendationId READ recommendationId WRITE setRecommendationId NOTIFY recommendationIdChanged) + Q_PROPERTY(RelevanceHint relevanceHint READ relevanceHint WRITE setRelevanceHint NOTIFY relevanceHintChanged) + Q_PROPERTY(QDeclarativePlace::Visibility visibilityScope READ visibilityScope WRITE setVisibilityScope NOTIFY visibilityScopeChanged) + + Q_PROPERTY(int count READ rowCount NOTIFY rowCountChanged) + Q_PROPERTY(QDeclarativeGeoServiceProvider *favoritesPlugin READ favoritesPlugin WRITE setFavoritesPlugin NOTIFY favoritesPluginChanged) + Q_PROPERTY(QVariantMap favoritesMatchParameters READ favoritesMatchParameters WRITE setFavoritesMatchParameters NOTIFY favoritesMatchParametersChanged) + + Q_ENUMS(SearchResultType RelevanceHint) + +public: + enum SearchResultType { + UnknownSearchResult = QPlaceSearchResult::UnknownSearchResult, + PlaceResult = QPlaceSearchResult::PlaceResult, + ProposedSearchResult = QPlaceSearchResult::ProposedSearchResult + }; + + enum RelevanceHint { + UnspecifiedHint = QPlaceSearchRequest::UnspecifiedHint, + DistanceHint = QPlaceSearchRequest::DistanceHint, + LexicalPlaceNameHint = QPlaceSearchRequest::LexicalPlaceNameHint + }; + + explicit QDeclarativeSearchResultModel(QObject *parent = 0); + ~QDeclarativeSearchResultModel(); + + QString searchTerm() const; + void setSearchTerm(const QString &searchTerm); + + QQmlListProperty categories(); + static void categories_append(QQmlListProperty *list, + QDeclarativeCategory *category); + static int categories_count(QQmlListProperty *list); + static QDeclarativeCategory *category_at(QQmlListProperty *list, int index); + static void categories_clear(QQmlListProperty *list); + + QString recommendationId() const; + void setRecommendationId(const QString &recommendationId); + + QDeclarativeSearchResultModel::RelevanceHint relevanceHint() const; + void setRelevanceHint(QDeclarativeSearchResultModel::RelevanceHint hint); + + QDeclarativePlace::Visibility visibilityScope() const; + void setVisibilityScope(QDeclarativePlace::Visibility visibilityScope); + + QDeclarativeGeoServiceProvider *favoritesPlugin() const; + void setFavoritesPlugin(QDeclarativeGeoServiceProvider *plugin); + + QVariantMap favoritesMatchParameters() const; + void setFavoritesMatchParameters(const QVariantMap ¶meters); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + virtual void clearData(bool suppressSignal = false); + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + Q_INVOKABLE QVariant data(int index, const QString &roleName) const; + QHash roleNames() const; + + Q_INVOKABLE void updateWith(int proposedSearchIndex); + + void updateSearchRequest(); + +Q_SIGNALS: + void searchTermChanged(); + void categoriesChanged(); + void recommendationIdChanged(); + void relevanceHintChanged(); + void visibilityScopeChanged(); + + void rowCountChanged(); + void favoritesPluginChanged(); + void favoritesMatchParametersChanged(); + void dataChanged(); + +protected: + QPlaceReply *sendQuery(QPlaceManager *manager, const QPlaceSearchRequest &request); + virtual void initializePlugin(QDeclarativeGeoServiceProvider *plugin); + +protected Q_SLOTS: + virtual void queryFinished(); + +private Q_SLOTS: + void updateLayout(const QList &favoritePlaces = QList()); + + void placeUpdated(const QString &placeId); + void placeRemoved(const QString &placeId); + +private: + enum Roles { + SearchResultTypeRole = Qt::UserRole, + TitleRole, + IconRole, + DistanceRole, + PlaceRole, + SponsoredRole + }; + + int getRow(const QString &placeId) const; + QList m_categories; + QLocation::VisibilityScope m_visibilityScope; + + QList m_results; + QList m_resultsBuffer; + QList m_places; + QList m_icons; + + QDeclarativeGeoServiceProvider *m_favoritesPlugin; + QVariantMap m_matchParameters; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVESEARCHRESULTMODEL_P_H diff --git a/src/imports/location/declarativeplaces/qdeclarativesearchsuggestionmodel.cpp b/src/imports/location/declarativeplaces/qdeclarativesearchsuggestionmodel.cpp new file mode 100644 index 0000000..abc845f --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesearchsuggestionmodel.cpp @@ -0,0 +1,351 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativesearchsuggestionmodel_p.h" +#include "qdeclarativegeoserviceprovider_p.h" + +#include +#include + +#include +#include + +QT_USE_NAMESPACE + +/*! + \qmltype PlaceSearchSuggestionModel + \instantiates QDeclarativeSearchSuggestionModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-models + \since Qt Location 5.5 + + \brief Provides access to search term suggestions. + + The PlaceSearchSuggestionModel can be used to provide search term suggestions as the user enters their + search term. The properties of this model should match that of the \l PlaceSearchModel, which + will be used to perform the actual search query, to ensure that the search suggestion results are + relevant. + + There are two ways of accessing the data provided by this model, either through the + \l suggestions property or through views and delegates. The latter is the preferred + method. + + The \l offset and \l limit properties can be used to access paged suggestions. When the + \l offset and \l limit properties are set the suggestions between \l offset and + (\l offset + \l limit - 1) will be returned. Support for paging may vary + from plugin to plugin. + + The model returns data for the following roles: + + \table + \header + \li Role + \li Type + \li Description + \row + \li suggestion + \li string + \li Suggested search term. + \endtable + + The following example shows how to use the PlaceSearchSuggestionModel to get suggested search terms + from a partial search term. The \l searchArea is set to match what would be used to perform the + actual place search with \l PlaceSearchModel. + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml SearchSuggestionModel + + \sa PlaceSearchModel, {QPlaceManager} +*/ + +/*! + \qmlproperty Plugin PlaceSearchSuggestionModel::plugin + + This property holds the provider \l Plugin which will be used to perform the search. +*/ + +/*! + \qmlproperty geoshape PlaceSearchSuggestionModel::searchArea + + This property holds the search area. Search suggestion results returned by the model will be + relevant to the given search area. + + If this property is set to a \l {geocircle} its + \l {geocircle}{radius} property may be left unset, in which case the \l Plugin + will choose an appropriate radius for the search. +*/ + +/*! + \qmlproperty int PlaceSearchSuggestionModel::offset + + This property holds the index of the first item in the model. + + \sa limit +*/ + +/*! + \qmlproperty int PlaceSearchSuggestionModel::limit + + This property holds the limit of the number of items that will be returned. + + \sa offset +*/ + +/*! + \qmlproperty enum PlaceSearchSuggestionModel::status + + This property holds the status of the model. It can be one of: + + \table + \row + \li PlaceSearchSuggestionModel.Null + \li No search query has been executed. The model is empty. + \row + \li PlaceSearchSuggestionModel.Ready + \li The search query has completed, and the results are available. + \row + \li PlaceSearchSuggestionModel.Loading + \li A search query is currently being executed. + \row + \li PlaceSearchSuggestionModel.Error + \li An error occurred when executing the previous search query. + \endtable +*/ + +/*! + \qmlmethod void PlaceSearchSuggestionModel::update() + + Updates the model based on the provided query parameters. The model will be populated with a + list of search suggestions for the partial \l searchTerm and \l searchArea. If the \l plugin + supports it, other parameters such as \l limit and \l offset may be specified. \c update() + submits the set of parameters to the \l plugin to process. + + + While the model is updating the \l status of the model is set to + \c PlaceSearchSuggestionModel.Loading. If the model is successfully updated, the \l status is + set to \c PlaceSearchSuggestionModel.Ready, while if it unsuccessfully completes, the \l status + is set to \c PlaceSearchSuggestionModel.Error and the model cleared. + + This example shows use of the model + \code + PlaceSeachSuggestionModel { + id: model + plugin: backendPlugin + searchArea: QtPositioning.circle(QtPositioning.coordinate(10, 10)) + ... + } + + MouseArea { + ... + onClicked: { + model.searchTerm = "piz" + model.searchArea.center.latitude = -27.5; + model.searchArea.cetner.longitude = 153; + model.update(); + } + } + \endcode + + A more detailed example can be found in the in + \l {Places (QML)#Presenting-Search-Suggestions}{Places (QML)} example. + + \sa cancel(), status +*/ + +/*! + \qmlmethod void PlaceSearchSuggestionModel::cancel() + + Cancels an ongoing search suggestion operation immediately and sets the model + status to PlaceSearchSuggestionModel.Ready. The model retains any search + suggestions it had before the operation was started. + + If an operation is not ongoing, invoking cancel() has no effect. + + \sa update(), status +*/ + +/*! + \qmlmethod void PlaceSearchSuggestionModel::reset() + + Resets the model. All search suggestions are cleared, any outstanding requests are aborted and + possible errors are cleared. Model status will be set to PlaceSearchSuggestionModel.Null. +*/ + + +/*! + \qmlmethod string QtLocation::PlaceSearchSuggestionModel::errorString() const + + This read-only property holds the textual presentation of the latest search suggestion model error. + If no error has occurred, or if the model was cleared, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. +*/ + +QDeclarativeSearchSuggestionModel::QDeclarativeSearchSuggestionModel(QObject *parent) +: QDeclarativeSearchModelBase(parent) +{ +} + +QDeclarativeSearchSuggestionModel::~QDeclarativeSearchSuggestionModel() +{ +} + +/*! + \qmlproperty string PlaceSearchSuggestionModel::searchTerm + + This property holds the partial search term used in query. +*/ +QString QDeclarativeSearchSuggestionModel::searchTerm() const +{ + return m_request.searchTerm(); +} + +void QDeclarativeSearchSuggestionModel::setSearchTerm(const QString &searchTerm) +{ + if (m_request.searchTerm() == searchTerm) + return; + + m_request.setSearchTerm(searchTerm); + emit searchTermChanged(); +} + +/*! + \qmlproperty stringlist PlaceSearchSuggestionModel::suggestions + + This property holds the list of predicted search terms that the model currently has. +*/ +QStringList QDeclarativeSearchSuggestionModel::suggestions() const +{ + return m_suggestions; +} + +/*! + \internal +*/ +void QDeclarativeSearchSuggestionModel::clearData(bool suppressSignal) +{ + QDeclarativeSearchModelBase::clearData(suppressSignal); + + if (!m_suggestions.isEmpty()) { + m_suggestions.clear(); + + if (!suppressSignal) + emit suggestionsChanged(); + } +} + +/*! + \internal +*/ +int QDeclarativeSearchSuggestionModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_suggestions.count(); +} + +/*! + \internal +*/ +QVariant QDeclarativeSearchSuggestionModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= rowCount(index.parent()) || index.row() < 0) + return QVariant(); + + switch (role) { + case Qt::DisplayRole: + case SearchSuggestionRole: + return m_suggestions.at(index.row()); + } + + return QVariant(); +} + +QHash QDeclarativeSearchSuggestionModel::roleNames() const +{ + QHash roleNames = QDeclarativeSearchModelBase::roleNames(); + roleNames.insert(SearchSuggestionRole, "suggestion"); + return roleNames; +} + +/*! + \internal +*/ +void QDeclarativeSearchSuggestionModel::queryFinished() +{ + if (!m_reply) + return; + + QPlaceReply *reply = m_reply; + m_reply = 0; + + int initialCount = m_suggestions.count(); + beginResetModel(); + + clearData(true); + + QPlaceSearchSuggestionReply *suggestionReply = qobject_cast(reply); + m_suggestions = suggestionReply->suggestions(); + + if (initialCount != m_suggestions.count()) + emit suggestionsChanged(); + + endResetModel(); + + if (suggestionReply->error() != QPlaceReply::NoError) + setStatus(Error, suggestionReply->errorString()); + else + setStatus(Ready); + + + reply->deleteLater(); +} + +/*! + \internal +*/ +QPlaceReply *QDeclarativeSearchSuggestionModel::sendQuery(QPlaceManager *manager, + const QPlaceSearchRequest &request) +{ + return manager->searchSuggestions(request); +} diff --git a/src/imports/location/declarativeplaces/qdeclarativesearchsuggestionmodel_p.h b/src/imports/location/declarativeplaces/qdeclarativesearchsuggestionmodel_p.h new file mode 100644 index 0000000..49e0998 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesearchsuggestionmodel_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVESEARCHSUGGESTIONMODEL_P_H +#define QDECLARATIVESEARCHSUGGESTIONMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativesearchmodelbase.h" + +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoServiceProvider; +class QGeoServiceProvider; + +class QDeclarativeSearchSuggestionModel : public QDeclarativeSearchModelBase +{ + Q_OBJECT + + Q_PROPERTY(QString searchTerm READ searchTerm WRITE setSearchTerm NOTIFY searchTermChanged) + Q_PROPERTY(QStringList suggestions READ suggestions NOTIFY suggestionsChanged) + +public: + explicit QDeclarativeSearchSuggestionModel(QObject *parent = 0); + ~QDeclarativeSearchSuggestionModel(); + + QString searchTerm() const; + void setSearchTerm(const QString &searchTerm); + + QStringList suggestions() const; + + void clearData(bool suppressSignal = false); + + // From QAbstractListModel + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QHash roleNames() const; + + enum Roles { + SearchSuggestionRole = Qt::UserRole + }; + +protected Q_SLOTS: + virtual void queryFinished(); + +Q_SIGNALS: + void searchTermChanged(); + void suggestionsChanged(); + +protected: + QPlaceReply *sendQuery(QPlaceManager *manager, const QPlaceSearchRequest &request); + +private: + QStringList m_suggestions; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/declarativeplaces/qdeclarativesupplier.cpp b/src/imports/location/declarativeplaces/qdeclarativesupplier.cpp new file mode 100644 index 0000000..8fca9e8 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesupplier.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativesupplier_p.h" + +#include + +QT_USE_NAMESPACE + +/*! + \qmltype Supplier + \instantiates QDeclarativeSupplier + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-data + \since Qt Location 5.5 + + \brief Holds data regarding the supplier of a place, a place's image, review, or editorial. + + Each instance represents a set of data about a supplier, which can include + supplier's name, url and icon. The supplier is typically a business or organization. + + Note: The Places API only supports suppliers as 'retrieve-only' objects. Submitting + suppliers to a provider is not a supported use case. + + \sa ImageModel, ReviewModel, EditorialModel + + \section1 Example + + The following example shows how to create and display a supplier in QML: + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml Supplier +*/ + +QDeclarativeSupplier::QDeclarativeSupplier(QObject *parent) + : QObject(parent), m_icon(0) +{ +} + +QDeclarativeSupplier::QDeclarativeSupplier(const QPlaceSupplier &src, + QDeclarativeGeoServiceProvider *plugin, + QObject *parent) + : QObject(parent), + m_src(src), + m_icon(0) +{ + setSupplier(src, plugin); +} + +QDeclarativeSupplier::~QDeclarativeSupplier() +{ +} + +/*! + \internal +*/ +void QDeclarativeSupplier::componentComplete() +{ + // delayed instantiation of QObject based properties. + if (!m_icon) + m_icon = new QDeclarativePlaceIcon(this); +} + +/*! + \qmlproperty QPlaceSupplier Supplier::supplier + + For details on how to use this property to interface between C++ and QML see + "\l {Supplier - QPlaceSupplier} {Interfaces between C++ and QML Code}". +*/ +void QDeclarativeSupplier::setSupplier(const QPlaceSupplier &src, QDeclarativeGeoServiceProvider *plugin) +{ + QPlaceSupplier previous = m_src; + m_src = src; + + if (previous.name() != m_src.name()) + emit nameChanged(); + + if (previous.supplierId() != m_src.supplierId()) + emit supplierIdChanged(); + + if (previous.url() != m_src.url()) + emit urlChanged(); + + if (m_icon && m_icon->parent() == this) { + m_icon->setPlugin(plugin); + m_icon->setIcon(m_src.icon()); + } else if (!m_icon || m_icon->parent() != this) { + m_icon = new QDeclarativePlaceIcon(m_src.icon(), plugin, this); + emit iconChanged(); + } +} + +QPlaceSupplier QDeclarativeSupplier::supplier() +{ + m_src.setIcon(m_icon ? m_icon->icon() : QPlaceIcon()); + return m_src; +} + +/*! + \qmlproperty string Supplier::supplierId + + This property holds the identifier of the supplier. The identifier is unique + to the Plugin backend which provided the supplier and is generally + not suitable for displaying to the user. +*/ + +void QDeclarativeSupplier::setSupplierId(const QString &supplierId) +{ + if (m_src.supplierId() != supplierId) { + m_src.setSupplierId(supplierId); + emit supplierIdChanged(); + } +} + +QString QDeclarativeSupplier::supplierId() const +{ + return m_src.supplierId(); +} + +/*! + \qmlproperty string Supplier::name + + This property holds the name of the supplier which can be displayed + to the user. + + The name can potentially be localized. The language is dependent on the + entity that sets it, typically this is the \l Plugin. The \l {Plugin::locales} + property defines what language is used. +*/ + +void QDeclarativeSupplier::setName(const QString &name) +{ + if (m_src.name() != name) { + m_src.setName(name); + emit nameChanged(); + } +} + +QString QDeclarativeSupplier::name() const +{ + return m_src.name(); +} + +/*! + \qmlproperty url Supplier::url + + This property holds the URL of the supplier's website. +*/ + +void QDeclarativeSupplier::setUrl(const QUrl &url) +{ + if (m_src.url() != url) { + m_src.setUrl(url); + emit urlChanged(); + } +} + +QUrl QDeclarativeSupplier::url() const +{ + return m_src.url(); +} + +/*! + \qmlproperty PlaceIcon Supplier::icon + + This property holds the icon of the supplier. +*/ +QDeclarativePlaceIcon *QDeclarativeSupplier::icon() const +{ + return m_icon; +} + +void QDeclarativeSupplier::setIcon(QDeclarativePlaceIcon *icon) +{ + if (m_icon == icon) + return; + + if (m_icon && m_icon->parent() == this) + delete m_icon; + + m_icon = icon; + emit iconChanged(); +} diff --git a/src/imports/location/declarativeplaces/qdeclarativesupplier_p.h b/src/imports/location/declarativeplaces/qdeclarativesupplier_p.h new file mode 100644 index 0000000..35b1dbd --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesupplier_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVESUPPLIER_P_H +#define QDECLARATIVESUPPLIER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +#include "qdeclarativeplaceicon_p.h" + +QT_BEGIN_NAMESPACE + +class QDeclarativeSupplier : public QObject, public QQmlParserStatus +{ + Q_OBJECT + + Q_PROPERTY(QPlaceSupplier supplier READ supplier WRITE setSupplier) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QString supplierId READ supplierId WRITE setSupplierId NOTIFY supplierIdChanged) + Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) + Q_PROPERTY(QDeclarativePlaceIcon *icon READ icon WRITE setIcon NOTIFY iconChanged) + + Q_INTERFACES(QQmlParserStatus) + +public: + explicit QDeclarativeSupplier(QObject *parent = 0); + explicit QDeclarativeSupplier(const QPlaceSupplier &src, QDeclarativeGeoServiceProvider *plugin, QObject *parent = 0); + ~QDeclarativeSupplier(); + + // From QQmlParserStatus + void classBegin() { } + void componentComplete(); + + QPlaceSupplier supplier(); + void setSupplier(const QPlaceSupplier &src, QDeclarativeGeoServiceProvider *plugin = 0); + + QString name() const; + void setName(const QString &data); + QString supplierId() const; + void setSupplierId(const QString &data); + QUrl url() const; + void setUrl(const QUrl &data); + + QDeclarativePlaceIcon *icon() const; + void setIcon(QDeclarativePlaceIcon *icon); + +Q_SIGNALS: + void nameChanged(); + void supplierIdChanged(); + void urlChanged(); + void iconChanged(); + +private: + QPlaceSupplier m_src; + QDeclarativePlaceIcon *m_icon; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeSupplier) + +#endif // QDECLARATIVESUPPLIER_P_H diff --git a/src/imports/location/declarativeplaces/qdeclarativesupportedcategoriesmodel.cpp b/src/imports/location/declarativeplaces/qdeclarativesupportedcategoriesmodel.cpp new file mode 100644 index 0000000..2865957 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesupportedcategoriesmodel.cpp @@ -0,0 +1,687 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Aaron McCarthy +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativesupportedcategoriesmodel_p.h" +#include "qdeclarativeplaceicon_p.h" +#include "qgeoserviceprovider.h" +#include "error_messages.h" + +#include +#include +#include +#include + +QT_USE_NAMESPACE + +/*! + \qmltype CategoryModel + \instantiates QDeclarativeSupportedCategoriesModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-places + \ingroup qml-QtLocation5-places-models + \since Qt Location 5.5 + + \brief The CategoryModel type provides a model of the categories supported by a \l Plugin. + + The CategoryModel type provides a model of the categories that are available from the + current \l Plugin. The model supports both a flat list of categories and a hierarchical tree + representing category groupings. This can be controlled by the \l hierarchical property. + + The model supports the following roles: + + \table + \header + \li Role + \li Type + \li Description + \row + \li category + \li \l Category + \li Category object for the current item. + \row + \li parentCategory + \li \l Category + \li Parent category object for the current item. + If there is no parent, null is returned. + \endtable + + The following example displays a flat list of all available categories: + + \snippet declarative/places.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/places.qml CategoryView + + To access the hierarchical category model it is necessary to use a \l DelegateModel to access + the child items. +*/ + +/*! + \qmlproperty Plugin CategoryModel::plugin + + This property holds the provider \l Plugin used by this model. +*/ + +/*! + \qmlproperty bool CategoryModel::hierarchical + + This property holds whether the model provides a hierarchical tree of categories or a flat + list. The default is true. +*/ + +/*! + \qmlmethod string QtLocation::CategoryModel::data(ModelIndex index, int role) + \internal + + This method retrieves the model's data per \a index and \a role. +*/ + +/*! + \qmlmethod string QtLocation::CategoryModel::errorString() const + + This read-only property holds the textual presentation of the latest category model error. + If no error has occurred, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. +*/ + +/*! + \internal + \enum QDeclarativeSupportedCategoriesModel::Roles +*/ + +QDeclarativeSupportedCategoriesModel::QDeclarativeSupportedCategoriesModel(QObject *parent) +: QAbstractItemModel(parent), m_response(0), m_plugin(0), m_hierarchical(true), + m_complete(false), m_status(Null) +{ +} + +QDeclarativeSupportedCategoriesModel::~QDeclarativeSupportedCategoriesModel() +{ + qDeleteAll(m_categoriesTree); +} + +/*! + \internal +*/ +// From QQmlParserStatus +void QDeclarativeSupportedCategoriesModel::componentComplete() +{ + m_complete = true; +} + +/*! + \internal +*/ +int QDeclarativeSupportedCategoriesModel::rowCount(const QModelIndex &parent) const +{ + if (m_categoriesTree.keys().isEmpty()) + return 0; + + PlaceCategoryNode *node = static_cast(parent.internalPointer()); + if (!node) + node = m_categoriesTree.value(QString()); + else if (m_categoriesTree.keys(node).isEmpty()) + return 0; + + return node->childIds.count(); +} + +/*! + \internal +*/ +int QDeclarativeSupportedCategoriesModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return 1; +} + +/*! + \internal +*/ +QModelIndex QDeclarativeSupportedCategoriesModel::index(int row, int column, const QModelIndex &parent) const +{ + if (column != 0 || row < 0) + return QModelIndex(); + + PlaceCategoryNode *node = static_cast(parent.internalPointer()); + + if (!node) + node = m_categoriesTree.value(QString()); + else if (m_categoriesTree.keys(node).isEmpty()) //return root index if parent is non-existent + return QModelIndex(); + + if (row > node->childIds.count()) + return QModelIndex(); + + QString id = node->childIds.at(row); + Q_ASSERT(m_categoriesTree.contains(id)); + + return createIndex(row, 0, m_categoriesTree.value(id)); +} + +/*! + \internal +*/ +QModelIndex QDeclarativeSupportedCategoriesModel::parent(const QModelIndex &child) const +{ + PlaceCategoryNode *childNode = static_cast(child.internalPointer()); + if (m_categoriesTree.keys(childNode).isEmpty()) + return QModelIndex(); + + return index(childNode->parentId); +} + +/*! + \internal +*/ +QVariant QDeclarativeSupportedCategoriesModel::data(const QModelIndex &index, int role) const +{ + PlaceCategoryNode *node = static_cast(index.internalPointer()); + if (!node) + node = m_categoriesTree.value(QString()); + else if (m_categoriesTree.keys(node).isEmpty()) + return QVariant(); + + QDeclarativeCategory *category = node->declCategory.data(); + + switch (role) { + case Qt::DisplayRole: + return category->name(); + case CategoryRole: + return QVariant::fromValue(category); + case ParentCategoryRole: { + if (!m_categoriesTree.keys().contains(node->parentId)) + return QVariant(); + else + return QVariant::fromValue(m_categoriesTree.value(node->parentId)->declCategory.data()); + } + default: + return QVariant(); + } +} + +QHash QDeclarativeSupportedCategoriesModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + roles.insert(CategoryRole, "category"); + roles.insert(ParentCategoryRole, "parentCategory"); + return roles; +} + +/*! + \internal +*/ +void QDeclarativeSupportedCategoriesModel::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (m_plugin == plugin) + return; + + //disconnect the manager of the old plugin if we have one + if (m_plugin) { + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (serviceProvider) { + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (placeManager) { + disconnect(placeManager, SIGNAL(categoryAdded(QPlaceCategory,QString)), + this, SLOT(addedCategory(QPlaceCategory,QString))); + disconnect(placeManager, SIGNAL(categoryUpdated(QPlaceCategory,QString)), + this, SLOT(updatedCategory(QPlaceCategory,QString))); + disconnect(placeManager, SIGNAL(categoryRemoved(QString,QString)), + this, SLOT(removedCategory(QString,QString))); + disconnect(placeManager, SIGNAL(dataChanged()), + this, SIGNAL(dataChanged())); + } + } + } + + m_plugin = plugin; + + // handle plugin name changes -> update categories + if (m_plugin) { + connect(m_plugin, SIGNAL(nameChanged(QString)), this, SLOT(connectNotificationSignals())); + connect(m_plugin, SIGNAL(nameChanged(QString)), this, SLOT(update())); + } + + connectNotificationSignals(); + + if (m_complete) + emit pluginChanged(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider *QDeclarativeSupportedCategoriesModel::plugin() const +{ + return m_plugin; +} + +/*! + \internal +*/ +void QDeclarativeSupportedCategoriesModel::setHierarchical(bool hierarchical) +{ + if (m_hierarchical == hierarchical) + return; + + m_hierarchical = hierarchical; + emit hierarchicalChanged(); + + updateLayout(); +} + +/*! + \internal +*/ +bool QDeclarativeSupportedCategoriesModel::hierarchical() const +{ + return m_hierarchical; +} + +/*! + \internal +*/ +void QDeclarativeSupportedCategoriesModel::replyFinished() +{ + if (!m_response) + return; + + m_response->deleteLater(); + + if (m_response->error() == QPlaceReply::NoError) { + m_errorString.clear(); + + m_response = 0; + + updateLayout(); + setStatus(QDeclarativeSupportedCategoriesModel::Ready); + } else { + const QString errorString = m_response->errorString(); + + m_response = 0; + + setStatus(Error, errorString); + } +} + +/*! + \internal +*/ +void QDeclarativeSupportedCategoriesModel::addedCategory(const QPlaceCategory &category, + const QString &parentId) +{ + if (m_response) + return; + + if (!m_categoriesTree.contains(parentId)) + return; + + if (category.categoryId().isEmpty()) + return; + + PlaceCategoryNode *parentNode = m_categoriesTree.value(parentId); + if (!parentNode) + return; + + int rowToBeAdded = rowToAddChild(parentNode, category); + QModelIndex parentIndex = index(parentId); + beginInsertRows(parentIndex, rowToBeAdded, rowToBeAdded); + PlaceCategoryNode *categoryNode = new PlaceCategoryNode; + categoryNode->parentId = parentId; + categoryNode->declCategory = QSharedPointer(new QDeclarativeCategory(category, m_plugin, this)); + + m_categoriesTree.insert(category.categoryId(), categoryNode); + parentNode->childIds.insert(rowToBeAdded,category.categoryId()); + endInsertRows(); + + //this is a workaround to deal with the fact that the hasModelChildren field of DelegateModel + //does not get updated when a child is added to a model + beginResetModel(); + endResetModel(); +} + +/*! + \internal +*/ +void QDeclarativeSupportedCategoriesModel::updatedCategory(const QPlaceCategory &category, + const QString &parentId) +{ + if (m_response) + return; + + QString categoryId = category.categoryId(); + + if (!m_categoriesTree.contains(parentId)) + return; + + if (category.categoryId().isEmpty() || !m_categoriesTree.contains(categoryId)) + return; + + PlaceCategoryNode *newParentNode = m_categoriesTree.value(parentId); + if (!newParentNode) + return; + + PlaceCategoryNode *categoryNode = m_categoriesTree.value(categoryId); + if (!categoryNode) + return; + + categoryNode->declCategory->setCategory(category); + + if (categoryNode->parentId == parentId) { //reparenting to same parent + QModelIndex parentIndex = index(parentId); + int rowToBeAdded = rowToAddChild(newParentNode, category); + int oldRow = newParentNode->childIds.indexOf(categoryId); + + //check if we are changing the position of the category + if (qAbs(rowToBeAdded - newParentNode->childIds.indexOf(categoryId)) > 1) { + //if the position has changed we are moving rows + beginMoveRows(parentIndex, oldRow, oldRow, + parentIndex, rowToBeAdded); + + newParentNode->childIds.removeAll(categoryId); + newParentNode->childIds.insert(rowToBeAdded, categoryId); + endMoveRows(); + } else {// if the position has not changed we modifying an existing row + QModelIndex categoryIndex = index(categoryId); + emit dataChanged(categoryIndex, categoryIndex); + } + } else { //reparenting to different parents + QPlaceCategory oldCategory = categoryNode->declCategory->category(); + PlaceCategoryNode *oldParentNode = m_categoriesTree.value(categoryNode->parentId); + if (!oldParentNode) + return; + QModelIndex oldParentIndex = index(categoryNode->parentId); + QModelIndex newParentIndex = index(parentId); + + int rowToBeAdded = rowToAddChild(newParentNode, category); + beginMoveRows(oldParentIndex, oldParentNode->childIds.indexOf(categoryId), + oldParentNode->childIds.indexOf(categoryId), newParentIndex, rowToBeAdded); + oldParentNode->childIds.removeAll(oldCategory.categoryId()); + newParentNode->childIds.insert(rowToBeAdded, categoryId); + categoryNode->parentId = parentId; + endMoveRows(); + + //this is a workaround to deal with the fact that the hasModelChildren field of DelegateModel + //does not get updated when an index is updated to contain children + beginResetModel(); + endResetModel(); + } +} + +/*! + \internal +*/ +void QDeclarativeSupportedCategoriesModel::removedCategory(const QString &categoryId, const QString &parentId) +{ + if (m_response) + return; + + if (!m_categoriesTree.contains(categoryId) || !m_categoriesTree.contains(parentId)) + return; + + QModelIndex parentIndex = index(parentId); + QModelIndex categoryIndex = index(categoryId); + + beginRemoveRows(parentIndex, categoryIndex.row(), categoryIndex.row()); + PlaceCategoryNode *parentNode = m_categoriesTree.value(parentId); + parentNode->childIds.removeAll(categoryId); + delete m_categoriesTree.take(categoryId); + endRemoveRows(); +} + +/*! + \internal +*/ +void QDeclarativeSupportedCategoriesModel::connectNotificationSignals() +{ + if (!m_plugin) + return; + + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (!serviceProvider || serviceProvider->error() != QGeoServiceProvider::NoError) + return; + + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (!placeManager) + return; + + // listen for any category notifications so that we can reupdate the categories + // model. + connect(placeManager, SIGNAL(categoryAdded(QPlaceCategory,QString)), + this, SLOT(addedCategory(QPlaceCategory,QString))); + connect(placeManager, SIGNAL(categoryUpdated(QPlaceCategory,QString)), + this, SLOT(updatedCategory(QPlaceCategory,QString))); + connect(placeManager, SIGNAL(categoryRemoved(QString,QString)), + this, SLOT(removedCategory(QString,QString))); + connect(placeManager, SIGNAL(dataChanged()), + this, SIGNAL(dataChanged())); +} + +/*! + \internal +*/ +void QDeclarativeSupportedCategoriesModel::update() +{ + if (m_response) + return; + + setStatus(Loading); + + if (!m_plugin) { + updateLayout(); + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_PROPERTY_NOT_SET)); + return; + } + + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (!serviceProvider || serviceProvider->error() != QGeoServiceProvider::NoError) { + updateLayout(); + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_PROVIDER_ERROR) + .arg(m_plugin->name())); + return; + } + + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (!placeManager) { + updateLayout(); + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_ERROR) + .arg(m_plugin->name()).arg(serviceProvider->errorString())); + return; + } + + m_response = placeManager->initializeCategories(); + if (m_response) { + connect(m_response, SIGNAL(finished()), this, SLOT(replyFinished())); + } else { + updateLayout(); + setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, + CATEGORIES_NOT_INITIALIZED)); + } +} + +/*! + \internal +*/ +void QDeclarativeSupportedCategoriesModel::updateLayout() +{ + beginResetModel(); + qDeleteAll(m_categoriesTree); + m_categoriesTree.clear(); + + if (m_plugin) { + QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider(); + if (serviceProvider && serviceProvider->error() == QGeoServiceProvider::NoError) { + QPlaceManager *placeManager = serviceProvider->placeManager(); + if (placeManager) { + PlaceCategoryNode *node = new PlaceCategoryNode; + node->childIds = populateCategories(placeManager, QPlaceCategory()); + m_categoriesTree.insert(QString(), node); + node->declCategory = QSharedPointer + (new QDeclarativeCategory(QPlaceCategory(), m_plugin, this)); + } + } + } + + endResetModel(); +} + +QString QDeclarativeSupportedCategoriesModel::errorString() const +{ + return m_errorString; +} + +/*! + \qmlproperty enumeration CategoryModel::status + + This property holds the status of the model. It can be one of: + + \table + \row + \li CategoryModel.Null + \li No category fetch query has been executed. The model is empty. + \row + \li CategoryModel.Ready + \li No error occurred during the last operation, further operations may be performed on + the model. + \row + \li CategoryModel.Loading + \li The model is being updated, no other operations may be performed until complete. + \row + \li CategoryModel.Error + \li An error occurred during the last operation, further operations can still be + performed on the model. + \endtable +*/ +void QDeclarativeSupportedCategoriesModel::setStatus(Status status, const QString &errorString) +{ + Status originalStatus = m_status; + m_status = status; + m_errorString = errorString; + + if (originalStatus != m_status) + emit statusChanged(); +} + +QDeclarativeSupportedCategoriesModel::Status QDeclarativeSupportedCategoriesModel::status() const +{ + return m_status; +} + +/*! + \internal +*/ +QStringList QDeclarativeSupportedCategoriesModel::populateCategories(QPlaceManager *manager, const QPlaceCategory &parent) +{ + Q_ASSERT(manager); + + QStringList childIds; + PlaceCategoryNode *node; + + QMap sortedCategories; + foreach ( const QPlaceCategory &category, manager->childCategories(parent.categoryId())) + sortedCategories.insert(category.name(), category); + + QMapIterator iter(sortedCategories); + while (iter.hasNext()) { + iter.next(); + node = new PlaceCategoryNode; + node->parentId = parent.categoryId(); + node->declCategory = QSharedPointer(new QDeclarativeCategory(iter.value(), m_plugin ,this)); + + if (m_hierarchical) + node->childIds = populateCategories(manager, iter.value()); + + m_categoriesTree.insert(node->declCategory->categoryId(), node); + childIds.append(iter.value().categoryId()); + + if (!m_hierarchical) { + childIds.append(populateCategories(manager,node->declCategory->category())); + } + } + return childIds; +} + +/*! + \internal +*/ +QModelIndex QDeclarativeSupportedCategoriesModel::index(const QString &categoryId) const +{ + if (categoryId.isEmpty()) + return QModelIndex(); + + if (!m_categoriesTree.contains(categoryId)) + return QModelIndex(); + + PlaceCategoryNode *categoryNode = m_categoriesTree.value(categoryId); + if (!categoryNode) + return QModelIndex(); + + QString parentCategoryId = categoryNode->parentId; + + PlaceCategoryNode *parentNode = m_categoriesTree.value(parentCategoryId); + + return createIndex(parentNode->childIds.indexOf(categoryId), 0, categoryNode); +} + +/*! + \internal +*/ +int QDeclarativeSupportedCategoriesModel::rowToAddChild(PlaceCategoryNode *node, const QPlaceCategory &category) +{ + Q_ASSERT(node); + for (int i = 0; i < node->childIds.count(); ++i) { + if (category.name() < m_categoriesTree.value(node->childIds.at(i))->declCategory->name()) + return i; + } + return node->childIds.count(); +} + +/*! + \qmlsignal CategoryModel::dataChanged() + + This signal is emitted when significant changes have been made to the underlying datastore. + + Applications should act on this signal at their own discretion. The data + provided by the model could be out of date and so the model should be reupdated + sometime, however an immediate reupdate may be disconcerting to users if the categories + change without any action on their part. + + The corresponding handler is \c onDataChanged. +*/ diff --git a/src/imports/location/declarativeplaces/qdeclarativesupportedcategoriesmodel_p.h b/src/imports/location/declarativeplaces/qdeclarativesupportedcategoriesmodel_p.h new file mode 100644 index 0000000..9816789 --- /dev/null +++ b/src/imports/location/declarativeplaces/qdeclarativesupportedcategoriesmodel_p.h @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVESUPPORTEDCATEGORIESMODEL_H +#define QDECLARATIVESUPPORTEDCATEGORIESMODEL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "qdeclarativecategory_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoServiceProvider; +class QPlaceManager; +class QPlaceReply; + +class PlaceCategoryNode +{ +public: + QString parentId; + QStringList childIds; + QSharedPointer declCategory; +}; + +class QDeclarativeSupportedCategoriesModel : public QAbstractItemModel, public QQmlParserStatus +{ + Q_OBJECT + + Q_ENUMS(Status) + + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(bool hierarchical READ hierarchical WRITE setHierarchical NOTIFY hierarchicalChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + + Q_INTERFACES(QQmlParserStatus) + Q_ENUMS(Roles) //The Roles enum is for internal usage only. + +public: + explicit QDeclarativeSupportedCategoriesModel(QObject *parent = 0); + virtual ~QDeclarativeSupportedCategoriesModel(); + + // From QQmlParserStatus + virtual void classBegin() {} + virtual void componentComplete(); + + // From QAbstractItemModel + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + + Q_INVOKABLE QVariant data(const QModelIndex &index, int role) const; + QHash roleNames() const; + + enum Roles { + CategoryRole = Qt::UserRole, + ParentCategoryRole + }; //for internal usage only + + enum Status {Null, Ready, Loading, Error}; + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + + void setHierarchical(bool hierarchical); + bool hierarchical() const; + + Q_INVOKABLE QString errorString() const; + + Status status() const; + void setStatus(Status status, const QString &errorString = QString()); + + using QAbstractItemModel::dataChanged; +Q_SIGNALS: + void pluginChanged(); + void hierarchicalChanged(); + void statusChanged(); + void dataChanged(); + +public Q_SLOTS: + void update(); + +private Q_SLOTS: + void replyFinished(); + void addedCategory(const QPlaceCategory &category, const QString &parentId); + void updatedCategory(const QPlaceCategory &category, const QString &parentId); + void removedCategory(const QString &categoryId, const QString &parentId); + void connectNotificationSignals(); + +private: + QStringList populateCategories(QPlaceManager *, const QPlaceCategory &parent); + QModelIndex index(const QString &categoryId) const; + int rowToAddChild(PlaceCategoryNode *, const QPlaceCategory &category); + void updateLayout(); + + QPlaceReply *m_response; + + QDeclarativeGeoServiceProvider *m_plugin; + bool m_hierarchical; + bool m_complete; + Status m_status; + QString m_errorString; + + QHash m_categoriesTree; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeSupportedCategoriesModel) + +#endif // QDECLARATIVESUPPORTEDCATEGORIESMODEL_H diff --git a/src/imports/location/error_messages.cpp b/src/imports/location/error_messages.cpp new file mode 100644 index 0000000..a2557f7 --- /dev/null +++ b/src/imports/location/error_messages.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "error_messages.h" + +QT_BEGIN_NAMESPACE + +const char CONTEXT_NAME[] = "QtLocationQML"; + +//to-be-translated error string + +const char PLUGIN_PROPERTY_NOT_SET[] = QT_TRANSLATE_NOOP("QtLocationQML", "Plugin property is not set."); +const char PLUGIN_ERROR[] = QT_TRANSLATE_NOOP("QtLocationQML", "Plugin Error (%1): %2"); +const char PLUGIN_PROVIDER_ERROR[] = QT_TRANSLATE_NOOP("QtLocationQML", "Plugin Error (%1): Could not instantiate provider"); +const char PLUGIN_NOT_VALID[] = QT_TRANSLATE_NOOP("QtLocationQML", "Plugin is not valid"); +const char CATEGORIES_NOT_INITIALIZED[] = QT_TRANSLATE_NOOP("QtLocationQML", "Unable to initialize categories"); +const char UNABLE_TO_MAKE_REQUEST[] = QT_TRANSLATE_NOOP("QtLocationQML", "Unable to create request"); +const char INDEX_OUT_OF_RANGE[] = QT_TRANSLATE_NOOP("QtLocationQML", "Index '%1' out of range"); + +QT_END_NAMESPACE diff --git a/src/imports/location/error_messages.h b/src/imports/location/error_messages.h new file mode 100644 index 0000000..81c43b3 --- /dev/null +++ b/src/imports/location/error_messages.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef ERROR_MESSAGES_H +#define ERROR_MESSAGES_H + +#include + +QT_BEGIN_NAMESPACE + +extern const char CONTEXT_NAME[]; +extern const char PLUGIN_PROPERTY_NOT_SET[]; +extern const char PLUGIN_ERROR[]; +extern const char PLUGIN_PROVIDER_ERROR[]; +extern const char PLUGIN_NOT_VALID[]; +extern const char CATEGORIES_NOT_INITIALIZED[]; +extern const char UNABLE_TO_MAKE_REQUEST[]; +extern const char INDEX_OUT_OF_RANGE[]; + +QT_END_NAMESPACE + +#endif // ERROR_MESSAGES_H diff --git a/src/imports/location/location.cpp b/src/imports/location/location.cpp new file mode 100644 index 0000000..b42933f --- /dev/null +++ b/src/imports/location/location.cpp @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeoserviceprovider_p.h" +#include "qdeclarativegeomap_p.h" + +#include "qdeclarativegeoroute_p.h" +#include "qdeclarativegeoroutemodel_p.h" +#include "qdeclarativegeocodemodel_p.h" +#include "qdeclarativegeomaneuver_p.h" +#include "qdeclarativegeomapquickitem_p.h" +#include "qdeclarativegeomapitemview_p.h" +#include "qdeclarativegeomaptype_p.h" +#include "qdeclarativerectanglemapitem_p.h" +#include "qdeclarativecirclemapitem_p.h" +#include "qdeclarativeroutemapitem_p.h" +#include "qdeclarativepolylinemapitem_p.h" +#include "qdeclarativepolygonmapitem_p.h" + +//Place includes +#include "qdeclarativecategory_p.h" +#include "qdeclarativeplace_p.h" +#include "qdeclarativeplaceattribute_p.h" +#include "qdeclarativeplaceicon_p.h" +#include "qdeclarativeratings_p.h" +#include "qdeclarativesupplier_p.h" +#include "qdeclarativeplaceuser_p.h" +#include "qdeclarativecontactdetail_p.h" + +#include "qdeclarativesupportedcategoriesmodel_p.h" +#include "qdeclarativesearchresultmodel_p.h" +#include "qdeclarativesearchsuggestionmodel_p.h" +#include "error_messages.h" + +#include + +#include + +static void initResources() +{ +#ifdef QT_STATIC + Q_INIT_RESOURCE(qmake_QtLocation); +#endif +} + +QT_BEGIN_NAMESPACE + + +class QtLocationDeclarativeModule: public QQmlExtensionPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0" + FILE "plugin.json") + +public: + QtLocationDeclarativeModule(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); } + virtual void registerTypes(const char *uri) + { + if (QLatin1String(uri) == QLatin1String("QtLocation")) { + + // @uri QtLocation + int major = 5; + int minor = 0; + + // Register the 5.0 types + // 5.0 is siltent and not advertised + + qmlRegisterType(uri, major, minor, "Plugin"); + qmlRegisterType(uri, major, minor, "PluginParameter"); + qmlRegisterUncreatableType(uri, major, minor, "PluginRequirements", + QStringLiteral("PluginRequirements is not intended instantiable by developer.")); + qmlRegisterType(uri, major, minor, "Map"); + + qmlRegisterUncreatableType(uri, major, minor, "GeoMapItemBase", + QStringLiteral("GeoMapItemBase is not intended instantiable by developer.")); + qmlRegisterType(uri, major, minor, "MapQuickItem"); + qmlRegisterType(uri, major, minor, "MapItemView"); + + qmlRegisterType(uri, major, minor, "GeocodeModel"); // geocoding and reverse geocoding + qmlRegisterType(uri, major, minor, "RouteModel"); + qmlRegisterType(uri, major, minor, "RouteQuery"); + qmlRegisterType(uri, major, minor, "Route"); // data type + qmlRegisterType(uri, major, minor, "RouteSegment"); + qmlRegisterType(uri, major, minor, "RouteManeuver"); + qmlRegisterUncreatableType(uri, major, minor, "MapPinchEvent", + QStringLiteral("(Map)PinchEvent is not intended instantiable by developer.")); + qmlRegisterUncreatableType(uri, major, minor, "MapGestureArea", + QStringLiteral("(Map)GestureArea is not intended instantiable by developer.")); + qmlRegisterUncreatableType(uri, major, minor, "MapType", + QStringLiteral("MapType is not intended instantiable by developer.")); + qmlRegisterType(uri, major, minor, "Category"); + qmlRegisterType(uri, major, minor, "EditorialModel"); + qmlRegisterType(uri, major, minor, "ImageModel"); + qmlRegisterType(uri, major, minor, "Place"); + qmlRegisterType(uri, major, minor, "Icon"); + qmlRegisterType(uri, major, minor, "Ratings"); + qmlRegisterType(uri, major, minor, "ReviewModel"); + qmlRegisterType(uri, major, minor, "Supplier"); + qmlRegisterType(uri, major, minor, "User"); + qmlRegisterType(uri, major, minor, "MapRectangle"); + qmlRegisterType(uri, major, minor, "MapCircle"); + qmlRegisterType(); + qmlRegisterType(uri, major, minor, "MapPolyline"); + qmlRegisterType(uri, major, minor, "MapPolygon"); + qmlRegisterType(uri, major, minor, "MapRoute"); + + qmlRegisterType(uri, major, minor, "CategoryModel"); + qmlRegisterType(uri, major, minor, "PlaceSearchModel"); + qmlRegisterType(uri, major, minor, "PlaceSearchSuggestionModel"); + qmlRegisterType(uri, major, minor, "PlaceAttribute"); + qmlRegisterUncreatableType(uri, major, minor, "ExtendedAttributes", "ExtendedAttributes instances cannot be instantiated. " + "Only Place types have ExtendedAttributes and they cannot be re-assigned " + "(but can be modified)."); + qmlRegisterType(uri, major, minor, "ContactDetail"); + qmlRegisterUncreatableType(uri, major, minor, "ContactDetails", "ContactDetails instances cannot be instantiated. " + "Only Place types have ContactDetails and they cannot " + "be re-assigned (but can be modified)."); + + // Introduction of 5.3 version; existing 5.0 exports automatically become available under 5.3 as well + // 5.3 is committed QML API despite missing release of QtLocation 5.3 + + minor = 5; + //TODO: this is broken QTBUG-50990 + qmlRegisterUncreatableType(uri, major, minor, "MapType", + QStringLiteral("MapType is not intended instantiable by developer.")); + minor = 6; + //TODO: this is broken QTBUG-50990 + qmlRegisterUncreatableType(uri, major, minor, "MapGestureArea", + QStringLiteral("(Map)GestureArea is not intended instantiable by developer.")); + + // Register the 5.7 types + minor = 7; + qmlRegisterType(uri, major, minor, "RouteManeuver"); + + //registrations below are version independent + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + } else { + qDebug() << "Unsupported URI given to load location QML plugin: " << QLatin1String(uri); + } + } +}; + +#include "location.moc" + +QT_END_NAMESPACE diff --git a/src/imports/location/location.pro b/src/imports/location/location.pro new file mode 100644 index 0000000..57172ad --- /dev/null +++ b/src/imports/location/location.pro @@ -0,0 +1,69 @@ +QT += quick-private network positioning-private location-private qml-private core-private gui-private + +INCLUDEPATH += ../../location +INCLUDEPATH += ../../location/maps +INCLUDEPATH += ../../positioning +INCLUDEPATH += ../positioning +INCLUDEPATH *= $$PWD + +HEADERS += \ + qdeclarativegeomapitemview_p.h \ + qdeclarativegeoserviceprovider_p.h \ + qdeclarativegeocodemodel_p.h \ + qdeclarativegeoroutemodel_p.h \ + qdeclarativegeoroute_p.h \ + qdeclarativegeoroutesegment_p.h \ + qdeclarativegeomaneuver_p.h \ + qdeclarativegeomap_p.h \ + qdeclarativegeomaptype_p.h \ + qdeclarativegeomapitembase_p.h \ + qdeclarativegeomapquickitem_p.h \ + qdeclarativecirclemapitem_p.h \ + qdeclarativerectanglemapitem_p.h \ + qdeclarativepolygonmapitem_p.h \ + qdeclarativepolylinemapitem_p.h \ + qdeclarativeroutemapitem_p.h \ + qgeomapitemgeometry_p.h \ + qdeclarativegeomapcopyrightsnotice_p.h \ + error_messages.h \ + locationvaluetypehelper_p.h\ + qquickgeomapgesturearea_p.h\ + ../positioning/qquickgeocoordinateanimation_p.h \ + mapitemviewdelegateincubator.h \ + qdeclarativegeomapitemview_p_p.h + +SOURCES += \ + location.cpp \ + qdeclarativegeomapitemview.cpp \ + qdeclarativegeoserviceprovider.cpp \ + qdeclarativegeocodemodel.cpp \ + qdeclarativegeoroutemodel.cpp \ + qdeclarativegeoroute.cpp \ + qdeclarativegeoroutesegment.cpp \ + qdeclarativegeomaneuver.cpp \ + qdeclarativegeomap.cpp \ + qdeclarativegeomaptype.cpp \ + qdeclarativegeomapitembase.cpp \ + qdeclarativegeomapquickitem.cpp \ + qdeclarativecirclemapitem.cpp \ + qdeclarativerectanglemapitem.cpp \ + qdeclarativepolygonmapitem.cpp \ + qdeclarativepolylinemapitem.cpp \ + qdeclarativeroutemapitem.cpp \ + qgeomapitemgeometry.cpp \ + qdeclarativegeomapcopyrightsnotice.cpp \ + error_messages.cpp \ + locationvaluetypehelper.cpp \ + qquickgeomapgesturearea.cpp \ + ../positioning/qquickgeocoordinateanimation.cpp \ + mapitemviewdelegateincubator.cpp + +include(declarativeplaces/declarativeplaces.pri) + +load(qml_plugin) + +LIBS_PRIVATE += -L$$MODULE_BASE_OUTDIR/lib -lpoly2tri$$qtPlatformTargetSuffix() -lclip2tri$$qtPlatformTargetSuffix() + +OTHER_FILES += \ + plugin.json \ + qmldir diff --git a/src/imports/location/locationvaluetypehelper.cpp b/src/imports/location/locationvaluetypehelper.cpp new file mode 100644 index 0000000..4f39e0b --- /dev/null +++ b/src/imports/location/locationvaluetypehelper.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "locationvaluetypehelper_p.h" + + +QGeoCoordinate parseCoordinate(const QJSValue &value, bool *ok) +{ + QGeoCoordinate c; + + if (value.isObject()) { + if (value.hasProperty(QStringLiteral("latitude"))) + c.setLatitude(value.property(QStringLiteral("latitude")).toNumber()); + if (value.hasProperty(QStringLiteral("longitude"))) + c.setLongitude(value.property(QStringLiteral("longitude")).toNumber()); + if (value.hasProperty(QStringLiteral("altitude"))) + c.setAltitude(value.property(QStringLiteral("altitude")).toNumber()); + + if (ok) + *ok = true; + } + + return c; +} + +QGeoRectangle parseRectangle(const QJSValue &value, bool *ok) +{ + QGeoRectangle r; + + *ok = false; + + if (value.isObject()) { + if (value.hasProperty(QStringLiteral("bottomLeft"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("bottomLeft")), ok); + if (*ok) + r.setBottomLeft(c); + } + if (value.hasProperty(QStringLiteral("bottomRight"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("bottomRight")), ok); + if (*ok) + r.setBottomRight(c); + } + if (value.hasProperty(QStringLiteral("topLeft"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("topLeft")), ok); + if (*ok) + r.setTopLeft(c); + } + if (value.hasProperty(QStringLiteral("topRight"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("topRight")), ok); + if (*ok) + r.setTopRight(c); + } + if (value.hasProperty(QStringLiteral("center"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("center")), ok); + if (*ok) + r.setCenter(c); + } + if (value.hasProperty(QStringLiteral("height"))) + r.setHeight(value.property(QStringLiteral("height")).toNumber()); + if (value.hasProperty(QStringLiteral("width"))) + r.setWidth(value.property(QStringLiteral("width")).toNumber()); + } + + return r; +} + +QGeoCircle parseCircle(const QJSValue &value, bool *ok) +{ + QGeoCircle c; + + *ok = false; + + if (value.isObject()) { + if (value.hasProperty(QStringLiteral("center"))) { + QGeoCoordinate coord = parseCoordinate(value.property(QStringLiteral("center")), ok); + if (*ok) + c.setCenter(coord); + } + if (value.hasProperty(QStringLiteral("radius"))) + c.setRadius(value.property(QStringLiteral("radius")).toNumber()); + } + + return c; +} diff --git a/src/imports/location/locationvaluetypehelper_p.h b/src/imports/location/locationvaluetypehelper_p.h new file mode 100644 index 0000000..50038e8 --- /dev/null +++ b/src/imports/location/locationvaluetypehelper_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef LOCATION_VALUE_TYPE_HELPER +#define LOCATION_VALUE_TYPE_HELPER + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QGeoCoordinate parseCoordinate(const QJSValue &value, bool *ok); +QGeoRectangle parseRectangle(const QJSValue &value, bool *ok); +QGeoCircle parseCircle(const QJSValue &value, bool *ok); + +#endif diff --git a/src/imports/location/mapitemviewdelegateincubator.cpp b/src/imports/location/mapitemviewdelegateincubator.cpp new file mode 100644 index 0000000..06dee7b --- /dev/null +++ b/src/imports/location/mapitemviewdelegateincubator.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Jolla Ltd, author: Aaron McCarthy +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mapitemviewdelegateincubator.h" +#include "qdeclarativegeomapitemview_p.h" +#include "qdeclarativegeomapitemview_p_p.h" + +QT_BEGIN_NAMESPACE + +MapItemViewDelegateIncubator::MapItemViewDelegateIncubator(QDeclarativeGeoMapItemView *view, QDeclarativeGeoMapItemViewItemData *itemData, bool batched) +: m_view(view), m_itemData(itemData), m_batched(batched) +{ +} + +void MapItemViewDelegateIncubator::statusChanged(QQmlIncubator::Status status) +{ + m_view->incubatorStatusChanged(this, status, m_batched); +} + +QT_END_NAMESPACE diff --git a/src/imports/location/mapitemviewdelegateincubator.h b/src/imports/location/mapitemviewdelegateincubator.h new file mode 100644 index 0000000..94c7325 --- /dev/null +++ b/src/imports/location/mapitemviewdelegateincubator.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Jolla Ltd, author: Aaron McCarthy +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef MAPITEMVIEWDELEGATEINCUBATOR_H +#define MAPITEMVIEWDELEGATEINCUBATOR_H + +#include +#include "qdeclarativegeomapitemview_p_p.h" + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoMapItemView; + +class MapItemViewDelegateIncubator : public QQmlIncubator +{ +public: + MapItemViewDelegateIncubator(QDeclarativeGeoMapItemView *view, QDeclarativeGeoMapItemViewItemData *itemData, bool batched = true); + +protected: + void statusChanged(Status status) Q_DECL_OVERRIDE; + +private: + QDeclarativeGeoMapItemView *m_view; + QDeclarativeGeoMapItemViewItemData *m_itemData; + bool m_batched; + + friend class QDeclarativeGeoMapItemView; +}; + +QT_END_NAMESPACE + +#endif // MAPITEMVIEWDELEGATEINCUBATOR_H diff --git a/src/imports/location/plugin.json b/src/imports/location/plugin.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/src/imports/location/plugin.json @@ -0,0 +1,2 @@ +{ +} diff --git a/src/imports/location/plugins.qmltypes b/src/imports/location/plugins.qmltypes new file mode 100644 index 0000000..4e8749d --- /dev/null +++ b/src/imports/location/plugins.qmltypes @@ -0,0 +1,1205 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -nonrelocatable QtLocation 5.7' + +Module { + dependencies: ["QtQuick 2.0"] + Component { + name: "QDeclarativeCategory" + prototype: "QObject" + exports: ["QtLocation/Category 5.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "Visibility" + values: { + "UnspecifiedVisibility": 0, + "DeviceVisibility": 1, + "PrivateVisibility": 2, + "PublicVisibility": 4 + } + } + Enum { + name: "Status" + values: { + "Ready": 0, + "Saving": 1, + "Removing": 2, + "Error": 3 + } + } + Property { name: "category"; type: "QPlaceCategory" } + Property { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + Property { name: "categoryId"; type: "string" } + Property { name: "name"; type: "string" } + Property { name: "visibility"; type: "Visibility" } + Property { name: "icon"; type: "QDeclarativePlaceIcon"; isPointer: true } + Property { name: "status"; type: "Status"; isReadonly: true } + Method { name: "errorString"; type: "string" } + Method { + name: "save" + Parameter { name: "parentId"; type: "string" } + } + Method { name: "save" } + Method { name: "remove" } + } + Component { + name: "QDeclarativeCircleMapItem" + defaultProperty: "data" + prototype: "QDeclarativeGeoMapItemBase" + exports: ["QtLocation/MapCircle 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "center"; type: "QGeoCoordinate" } + Property { name: "radius"; type: "double" } + Property { name: "color"; type: "QColor" } + Property { + name: "border" + type: "QDeclarativeMapLineProperties" + isReadonly: true + isPointer: true + } + Signal { + name: "centerChanged" + Parameter { name: "center"; type: "QGeoCoordinate" } + } + Signal { + name: "radiusChanged" + Parameter { name: "radius"; type: "double" } + } + Signal { + name: "colorChanged" + Parameter { name: "color"; type: "QColor" } + } + } + Component { + name: "QDeclarativeContactDetail" + prototype: "QObject" + exports: ["QtLocation/ContactDetail 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "contactDetail"; type: "QPlaceContactDetail" } + Property { name: "label"; type: "string" } + Property { name: "value"; type: "string" } + } + Component { + name: "QDeclarativeContactDetails" + prototype: "QQmlPropertyMap" + exports: ["QtLocation/ContactDetails 5.0"] + isCreatable: false + exportMetaObjectRevisions: [0] + } + Component { + name: "QDeclarativeGeoManeuver" + prototype: "QObject" + exports: [ + "QtLocation/RouteManeuver 5.0", + "QtLocation/RouteManeuver 5.7" + ] + exportMetaObjectRevisions: [0, 0] + Enum { + name: "Direction" + values: { + "NoDirection": 0, + "DirectionForward": 1, + "DirectionBearRight": 2, + "DirectionLightRight": 3, + "DirectionRight": 4, + "DirectionHardRight": 5, + "DirectionUTurnRight": 6, + "DirectionUTurnLeft": 7, + "DirectionHardLeft": 8, + "DirectionLeft": 9, + "DirectionLightLeft": 10, + "DirectionBearLeft": 11 + } + } + Property { name: "valid"; type: "bool"; isReadonly: true } + Property { name: "position"; type: "QGeoCoordinate"; isReadonly: true } + Property { name: "instructionText"; type: "string"; isReadonly: true } + Property { name: "direction"; type: "Direction"; isReadonly: true } + Property { name: "timeToNextInstruction"; type: "int"; isReadonly: true } + Property { name: "distanceToNextInstruction"; type: "double"; isReadonly: true } + Property { name: "waypoint"; type: "QGeoCoordinate"; isReadonly: true } + Property { name: "waypointValid"; type: "bool"; isReadonly: true } + } + Component { + name: "QDeclarativeGeoMap" + defaultProperty: "data" + prototype: "QQuickItem" + exports: ["QtLocation/Map 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "gesture"; type: "QQuickGeoMapGestureArea"; isReadonly: true; isPointer: true } + Property { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + Property { name: "minimumZoomLevel"; type: "double" } + Property { name: "maximumZoomLevel"; type: "double" } + Property { name: "zoomLevel"; type: "double" } + Property { name: "activeMapType"; type: "QDeclarativeGeoMapType"; isPointer: true } + Property { + name: "supportedMapTypes" + type: "QDeclarativeGeoMapType" + isList: true + isReadonly: true + } + Property { name: "center"; type: "QGeoCoordinate" } + Property { name: "mapItems"; type: "QList"; isReadonly: true } + Property { name: "error"; type: "QGeoServiceProvider::Error"; isReadonly: true } + Property { name: "errorString"; type: "string"; isReadonly: true } + Property { name: "visibleRegion"; type: "QGeoShape" } + Property { name: "copyrightsVisible"; type: "bool" } + Property { name: "color"; type: "QColor" } + Signal { + name: "pluginChanged" + Parameter { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + } + Signal { + name: "zoomLevelChanged" + Parameter { name: "zoomLevel"; type: "double" } + } + Signal { + name: "centerChanged" + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + Signal { + name: "copyrightLinkActivated" + Parameter { name: "link"; type: "string" } + } + Signal { + name: "copyrightsVisibleChanged" + Parameter { name: "visible"; type: "bool" } + } + Signal { + name: "colorChanged" + Parameter { name: "color"; type: "QColor" } + } + Method { + name: "removeMapItem" + Parameter { name: "item"; type: "QDeclarativeGeoMapItemBase"; isPointer: true } + } + Method { + name: "addMapItem" + Parameter { name: "item"; type: "QDeclarativeGeoMapItemBase"; isPointer: true } + } + Method { name: "clearMapItems" } + Method { + name: "toCoordinate" + type: "QGeoCoordinate" + Parameter { name: "position"; type: "QPointF" } + Parameter { name: "clipToViewPort"; type: "bool" } + } + Method { + name: "toCoordinate" + type: "QGeoCoordinate" + Parameter { name: "position"; type: "QPointF" } + } + Method { + name: "fromCoordinate" + type: "QPointF" + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + Parameter { name: "clipToViewPort"; type: "bool" } + } + Method { + name: "fromCoordinate" + type: "QPointF" + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + Method { name: "fitViewportToMapItems" } + Method { + name: "pan" + Parameter { name: "dx"; type: "int" } + Parameter { name: "dy"; type: "int" } + } + Method { name: "prefetchData" } + Method { name: "clearData" } + } + Component { + name: "QDeclarativeGeoMapItemBase" + defaultProperty: "data" + prototype: "QQuickItem" + exports: ["QtLocation/GeoMapItemBase 5.0"] + isCreatable: false + exportMetaObjectRevisions: [0] + } + Component { + name: "QDeclarativeGeoMapItemView" + prototype: "QObject" + exports: ["QtLocation/MapItemView 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "model"; type: "QVariant" } + Property { name: "delegate"; type: "QQmlComponent"; isPointer: true } + Property { name: "autoFitViewport"; type: "bool" } + } + Component { + name: "QDeclarativeGeoMapQuickItem" + defaultProperty: "data" + prototype: "QDeclarativeGeoMapItemBase" + exports: ["QtLocation/MapQuickItem 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "coordinate"; type: "QGeoCoordinate" } + Property { name: "anchorPoint"; type: "QPointF" } + Property { name: "zoomLevel"; type: "double" } + Property { name: "sourceItem"; type: "QQuickItem"; isPointer: true } + } + Component { + name: "QDeclarativeGeoMapType" + prototype: "QObject" + exports: ["QtLocation/MapType 5.0", "QtLocation/MapType 5.5"] + isCreatable: false + exportMetaObjectRevisions: [0, 1] + Enum { + name: "MapStyle" + values: { + "NoMap": 0, + "StreetMap": 1, + "SatelliteMapDay": 2, + "SatelliteMapNight": 3, + "TerrainMap": 4, + "HybridMap": 5, + "TransitMap": 6, + "GrayStreetMap": 7, + "PedestrianMap": 8, + "CarNavigationMap": 9, + "CycleMap": 10, + "CustomMap": 100 + } + } + Property { name: "style"; type: "MapStyle"; isReadonly: true } + Property { name: "name"; type: "string"; isReadonly: true } + Property { name: "description"; type: "string"; isReadonly: true } + Property { name: "mobile"; type: "bool"; isReadonly: true } + Property { name: "night"; revision: 1; type: "bool"; isReadonly: true } + } + Component { + name: "QDeclarativeGeoRoute" + prototype: "QObject" + exports: ["QtLocation/Route 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "bounds"; type: "QGeoRectangle"; isReadonly: true } + Property { name: "travelTime"; type: "int"; isReadonly: true } + Property { name: "distance"; type: "double"; isReadonly: true } + Property { name: "path"; type: "QJSValue" } + Property { name: "segments"; type: "QDeclarativeGeoRouteSegment"; isList: true; isReadonly: true } + } + Component { + name: "QDeclarativeGeoRouteModel" + prototype: "QAbstractListModel" + exports: ["QtLocation/RouteModel 5.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "Status" + values: { + "Null": 0, + "Ready": 1, + "Loading": 2, + "Error": 3 + } + } + Enum { + name: "RouteError" + values: { + "NoError": 0, + "EngineNotSetError": 1, + "CommunicationError": 2, + "ParseError": 3, + "UnsupportedOptionError": 4, + "UnknownError": 5, + "UnknownParameterError": 100, + "MissingRequiredParameterError": 101 + } + } + Property { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + Property { name: "query"; type: "QDeclarativeGeoRouteQuery"; isPointer: true } + Property { name: "count"; type: "int"; isReadonly: true } + Property { name: "autoUpdate"; type: "bool" } + Property { name: "status"; type: "Status"; isReadonly: true } + Property { name: "errorString"; type: "string"; isReadonly: true } + Property { name: "error"; type: "RouteError"; isReadonly: true } + Property { name: "measurementSystem"; type: "QLocale::MeasurementSystem" } + Signal { name: "routesChanged" } + Method { name: "update" } + Method { + name: "get" + type: "QDeclarativeGeoRoute*" + Parameter { name: "index"; type: "int" } + } + Method { name: "reset" } + Method { name: "cancel" } + } + Component { + name: "QDeclarativeGeoRouteQuery" + prototype: "QObject" + exports: ["QtLocation/RouteQuery 5.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "TravelMode" + values: { + "CarTravel": 1, + "PedestrianTravel": 2, + "BicycleTravel": 4, + "PublicTransitTravel": 8, + "TruckTravel": 16 + } + } + Enum { + name: "TravelModes" + values: { + "CarTravel": 1, + "PedestrianTravel": 2, + "BicycleTravel": 4, + "PublicTransitTravel": 8, + "TruckTravel": 16 + } + } + Enum { + name: "FeatureType" + values: { + "NoFeature": 0, + "TollFeature": 1, + "HighwayFeature": 2, + "PublicTransitFeature": 4, + "FerryFeature": 8, + "TunnelFeature": 16, + "DirtRoadFeature": 32, + "ParksFeature": 64, + "MotorPoolLaneFeature": 128 + } + } + Enum { + name: "FeatureWeight" + values: { + "NeutralFeatureWeight": 0, + "PreferFeatureWeight": 1, + "RequireFeatureWeight": 2, + "AvoidFeatureWeight": 4, + "DisallowFeatureWeight": 8 + } + } + Enum { + name: "RouteOptimization" + values: { + "ShortestRoute": 1, + "FastestRoute": 2, + "MostEconomicRoute": 4, + "MostScenicRoute": 8 + } + } + Enum { + name: "RouteOptimizations" + values: { + "ShortestRoute": 1, + "FastestRoute": 2, + "MostEconomicRoute": 4, + "MostScenicRoute": 8 + } + } + Enum { + name: "SegmentDetail" + values: { + "NoSegmentData": 0, + "BasicSegmentData": 1 + } + } + Enum { + name: "SegmentDetails" + values: { + "NoSegmentData": 0, + "BasicSegmentData": 1 + } + } + Enum { + name: "ManeuverDetail" + values: { + "NoManeuvers": 0, + "BasicManeuvers": 1 + } + } + Enum { + name: "ManeuverDetails" + values: { + "NoManeuvers": 0, + "BasicManeuvers": 1 + } + } + Property { name: "numberAlternativeRoutes"; type: "int" } + Property { name: "travelModes"; type: "TravelModes" } + Property { name: "routeOptimizations"; type: "RouteOptimizations" } + Property { name: "segmentDetail"; type: "SegmentDetail" } + Property { name: "maneuverDetail"; type: "ManeuverDetail" } + Property { name: "waypoints"; type: "QJSValue" } + Property { name: "excludedAreas"; type: "QJSValue" } + Property { name: "featureTypes"; type: "QList"; isReadonly: true } + Signal { name: "queryDetailsChanged" } + Method { + name: "addWaypoint" + Parameter { name: "waypoint"; type: "QGeoCoordinate" } + } + Method { + name: "removeWaypoint" + Parameter { name: "waypoint"; type: "QGeoCoordinate" } + } + Method { name: "clearWaypoints" } + Method { + name: "addExcludedArea" + Parameter { name: "area"; type: "QGeoRectangle" } + } + Method { + name: "removeExcludedArea" + Parameter { name: "area"; type: "QGeoRectangle" } + } + Method { name: "clearExcludedAreas" } + Method { + name: "setFeatureWeight" + Parameter { name: "featureType"; type: "FeatureType" } + Parameter { name: "featureWeight"; type: "FeatureWeight" } + } + Method { + name: "featureWeight" + type: "int" + Parameter { name: "featureType"; type: "FeatureType" } + } + Method { name: "resetFeatureWeights" } + } + Component { + name: "QDeclarativeGeoRouteSegment" + prototype: "QObject" + exports: ["QtLocation/RouteSegment 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "travelTime"; type: "int"; isReadonly: true } + Property { name: "distance"; type: "double"; isReadonly: true } + Property { name: "path"; type: "QJSValue"; isReadonly: true } + Property { name: "maneuver"; type: "QDeclarativeGeoManeuver"; isReadonly: true; isPointer: true } + } + Component { + name: "QDeclarativeGeoServiceProvider" + defaultProperty: "parameters" + prototype: "QObject" + exports: ["QtLocation/Plugin 5.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "RoutingFeature" + values: { + "NoRoutingFeatures": 0, + "OnlineRoutingFeature": 1, + "OfflineRoutingFeature": 2, + "LocalizedRoutingFeature": 4, + "RouteUpdatesFeature": 8, + "AlternativeRoutesFeature": 16, + "ExcludeAreasRoutingFeature": 32, + "AnyRoutingFeatures": -1 + } + } + Enum { + name: "RoutingFeatures" + values: { + "NoRoutingFeatures": 0, + "OnlineRoutingFeature": 1, + "OfflineRoutingFeature": 2, + "LocalizedRoutingFeature": 4, + "RouteUpdatesFeature": 8, + "AlternativeRoutesFeature": 16, + "ExcludeAreasRoutingFeature": 32, + "AnyRoutingFeatures": -1 + } + } + Enum { + name: "GeocodingFeature" + values: { + "NoGeocodingFeatures": 0, + "OnlineGeocodingFeature": 1, + "OfflineGeocodingFeature": 2, + "ReverseGeocodingFeature": 4, + "LocalizedGeocodingFeature": 8, + "AnyGeocodingFeatures": -1 + } + } + Enum { + name: "GeocodingFeatures" + values: { + "NoGeocodingFeatures": 0, + "OnlineGeocodingFeature": 1, + "OfflineGeocodingFeature": 2, + "ReverseGeocodingFeature": 4, + "LocalizedGeocodingFeature": 8, + "AnyGeocodingFeatures": -1 + } + } + Enum { + name: "MappingFeature" + values: { + "NoMappingFeatures": 0, + "OnlineMappingFeature": 1, + "OfflineMappingFeature": 2, + "LocalizedMappingFeature": 4, + "AnyMappingFeatures": -1 + } + } + Enum { + name: "MappingFeatures" + values: { + "NoMappingFeatures": 0, + "OnlineMappingFeature": 1, + "OfflineMappingFeature": 2, + "LocalizedMappingFeature": 4, + "AnyMappingFeatures": -1 + } + } + Enum { + name: "PlacesFeature" + values: { + "NoPlacesFeatures": 0, + "OnlinePlacesFeature": 1, + "OfflinePlacesFeature": 2, + "SavePlaceFeature": 4, + "RemovePlaceFeature": 8, + "SaveCategoryFeature": 16, + "RemoveCategoryFeature": 32, + "PlaceRecommendationsFeature": 64, + "SearchSuggestionsFeature": 128, + "LocalizedPlacesFeature": 256, + "NotificationsFeature": 512, + "PlaceMatchingFeature": 1024, + "AnyPlacesFeatures": -1 + } + } + Enum { + name: "PlacesFeatures" + values: { + "NoPlacesFeatures": 0, + "OnlinePlacesFeature": 1, + "OfflinePlacesFeature": 2, + "SavePlaceFeature": 4, + "RemovePlaceFeature": 8, + "SaveCategoryFeature": 16, + "RemoveCategoryFeature": 32, + "PlaceRecommendationsFeature": 64, + "SearchSuggestionsFeature": 128, + "LocalizedPlacesFeature": 256, + "NotificationsFeature": 512, + "PlaceMatchingFeature": 1024, + "AnyPlacesFeatures": -1 + } + } + Property { name: "name"; type: "string" } + Property { name: "availableServiceProviders"; type: "QStringList"; isReadonly: true } + Property { + name: "parameters" + type: "QDeclarativeGeoServiceProviderParameter" + isList: true + isReadonly: true + } + Property { + name: "required" + type: "QDeclarativeGeoServiceProviderRequirements" + isReadonly: true + isPointer: true + } + Property { name: "locales"; type: "QStringList" } + Property { name: "preferred"; type: "QStringList" } + Property { name: "allowExperimental"; type: "bool" } + Property { name: "isAttached"; type: "bool"; isReadonly: true } + Signal { + name: "nameChanged" + Parameter { name: "name"; type: "string" } + } + Signal { name: "attached" } + Signal { + name: "preferredChanged" + Parameter { name: "preferences"; type: "QStringList" } + } + Signal { + name: "allowExperimentalChanged" + Parameter { name: "allow"; type: "bool" } + } + Method { + name: "supportsRouting" + type: "bool" + Parameter { name: "feature"; type: "RoutingFeatures" } + } + Method { name: "supportsRouting"; type: "bool" } + Method { + name: "supportsGeocoding" + type: "bool" + Parameter { name: "feature"; type: "GeocodingFeatures" } + } + Method { name: "supportsGeocoding"; type: "bool" } + Method { + name: "supportsMapping" + type: "bool" + Parameter { name: "feature"; type: "MappingFeatures" } + } + Method { name: "supportsMapping"; type: "bool" } + Method { + name: "supportsPlaces" + type: "bool" + Parameter { name: "feature"; type: "PlacesFeatures" } + } + Method { name: "supportsPlaces"; type: "bool" } + } + Component { + name: "QDeclarativeGeoServiceProviderParameter" + prototype: "QObject" + exports: ["QtLocation/PluginParameter 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "name"; type: "string" } + Property { name: "value"; type: "QVariant" } + Signal { + name: "nameChanged" + Parameter { name: "name"; type: "string" } + } + Signal { + name: "valueChanged" + Parameter { name: "value"; type: "QVariant" } + } + } + Component { + name: "QDeclarativeGeoServiceProviderRequirements" + prototype: "QObject" + exports: ["QtLocation/PluginRequirements 5.0"] + isCreatable: false + exportMetaObjectRevisions: [0] + Property { name: "mapping"; type: "QDeclarativeGeoServiceProvider::MappingFeatures" } + Property { name: "routing"; type: "QDeclarativeGeoServiceProvider::RoutingFeatures" } + Property { name: "geocoding"; type: "QDeclarativeGeoServiceProvider::GeocodingFeatures" } + Property { name: "places"; type: "QDeclarativeGeoServiceProvider::PlacesFeatures" } + Signal { + name: "mappingRequirementsChanged" + Parameter { name: "features"; type: "QDeclarativeGeoServiceProvider::MappingFeatures" } + } + Signal { + name: "routingRequirementsChanged" + Parameter { name: "features"; type: "QDeclarativeGeoServiceProvider::RoutingFeatures" } + } + Signal { + name: "geocodingRequirementsChanged" + Parameter { name: "features"; type: "QDeclarativeGeoServiceProvider::GeocodingFeatures" } + } + Signal { + name: "placesRequirementsChanged" + Parameter { name: "features"; type: "QDeclarativeGeoServiceProvider::PlacesFeatures" } + } + Signal { name: "requirementsChanged" } + Method { + name: "matches" + type: "bool" + Parameter { name: "provider"; type: "const QGeoServiceProvider"; isPointer: true } + } + } + Component { + name: "QDeclarativeGeocodeModel" + prototype: "QAbstractListModel" + exports: ["QtLocation/GeocodeModel 5.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "Status" + values: { + "Null": 0, + "Ready": 1, + "Loading": 2, + "Error": 3 + } + } + Enum { + name: "GeocodeError" + values: { + "NoError": 0, + "EngineNotSetError": 1, + "CommunicationError": 2, + "ParseError": 3, + "UnsupportedOptionError": 4, + "CombinationError": 5, + "UnknownError": 6, + "UnknownParameterError": 100, + "MissingRequiredParameterError": 101 + } + } + Property { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + Property { name: "autoUpdate"; type: "bool" } + Property { name: "status"; type: "Status"; isReadonly: true } + Property { name: "errorString"; type: "string"; isReadonly: true } + Property { name: "count"; type: "int"; isReadonly: true } + Property { name: "limit"; type: "int" } + Property { name: "offset"; type: "int" } + Property { name: "query"; type: "QVariant" } + Property { name: "bounds"; type: "QVariant" } + Property { name: "error"; type: "GeocodeError"; isReadonly: true } + Signal { name: "locationsChanged" } + Method { name: "update" } + Method { + name: "get" + type: "QDeclarativeGeoLocation*" + Parameter { name: "index"; type: "int" } + } + Method { name: "reset" } + Method { name: "cancel" } + } + Component { + name: "QDeclarativeMapLineProperties" + prototype: "QObject" + Property { name: "width"; type: "double" } + Property { name: "color"; type: "QColor" } + Signal { + name: "widthChanged" + Parameter { name: "width"; type: "double" } + } + Signal { + name: "colorChanged" + Parameter { name: "color"; type: "QColor" } + } + } + Component { + name: "QDeclarativePlace" + prototype: "QObject" + exports: ["QtLocation/Place 5.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "Status" + values: { + "Ready": 0, + "Saving": 1, + "Fetching": 2, + "Removing": 3, + "Error": 4 + } + } + Enum { + name: "Visibility" + values: { + "UnspecifiedVisibility": 0, + "DeviceVisibility": 1, + "PrivateVisibility": 2, + "PublicVisibility": 4 + } + } + Property { name: "place"; type: "QPlace" } + Property { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + Property { name: "categories"; type: "QDeclarativeCategory"; isList: true; isReadonly: true } + Property { name: "location"; type: "QDeclarativeGeoLocation"; isPointer: true } + Property { name: "ratings"; type: "QDeclarativeRatings"; isPointer: true } + Property { name: "supplier"; type: "QDeclarativeSupplier"; isPointer: true } + Property { name: "icon"; type: "QDeclarativePlaceIcon"; isPointer: true } + Property { name: "name"; type: "string" } + Property { name: "placeId"; type: "string" } + Property { name: "attribution"; type: "string" } + Property { + name: "reviewModel" + type: "QDeclarativeReviewModel" + isReadonly: true + isPointer: true + } + Property { + name: "imageModel" + type: "QDeclarativePlaceImageModel" + isReadonly: true + isPointer: true + } + Property { + name: "editorialModel" + type: "QDeclarativePlaceEditorialModel" + isReadonly: true + isPointer: true + } + Property { name: "extendedAttributes"; type: "QObject"; isReadonly: true; isPointer: true } + Property { name: "contactDetails"; type: "QObject"; isReadonly: true; isPointer: true } + Property { name: "detailsFetched"; type: "bool"; isReadonly: true } + Property { name: "status"; type: "Status"; isReadonly: true } + Property { name: "primaryPhone"; type: "string"; isReadonly: true } + Property { name: "primaryFax"; type: "string"; isReadonly: true } + Property { name: "primaryEmail"; type: "string"; isReadonly: true } + Property { name: "primaryWebsite"; type: "QUrl"; isReadonly: true } + Property { name: "visibility"; type: "Visibility" } + Property { name: "favorite"; type: "QDeclarativePlace"; isPointer: true } + Method { name: "getDetails" } + Method { name: "save" } + Method { name: "remove" } + Method { name: "errorString"; type: "string" } + Method { + name: "copyFrom" + Parameter { name: "original"; type: "QDeclarativePlace"; isPointer: true } + } + Method { + name: "initializeFavorite" + Parameter { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + } + } + Component { + name: "QDeclarativePlaceAttribute" + prototype: "QObject" + exports: ["QtLocation/PlaceAttribute 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "attribute"; type: "QPlaceAttribute" } + Property { name: "label"; type: "string" } + Property { name: "text"; type: "string" } + } + Component { + name: "QDeclarativePlaceContentModel" + prototype: "QAbstractListModel" + Property { name: "place"; type: "QDeclarativePlace"; isPointer: true } + Property { name: "batchSize"; type: "int" } + Property { name: "totalCount"; type: "int"; isReadonly: true } + } + Component { + name: "QDeclarativePlaceEditorialModel" + prototype: "QDeclarativePlaceContentModel" + exports: ["QtLocation/EditorialModel 5.0"] + exportMetaObjectRevisions: [0] + } + Component { + name: "QDeclarativePlaceIcon" + prototype: "QObject" + exports: ["QtLocation/Icon 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "icon"; type: "QPlaceIcon" } + Property { name: "parameters"; type: "QObject"; isReadonly: true; isPointer: true } + Property { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + Method { + name: "url" + type: "QUrl" + Parameter { name: "size"; type: "QSize" } + } + Method { name: "url"; type: "QUrl" } + } + Component { + name: "QDeclarativePlaceImageModel" + prototype: "QDeclarativePlaceContentModel" + exports: ["QtLocation/ImageModel 5.0"] + exportMetaObjectRevisions: [0] + } + Component { + name: "QDeclarativePlaceUser" + prototype: "QObject" + exports: ["QtLocation/User 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "user"; type: "QPlaceUser" } + Property { name: "userId"; type: "string" } + Property { name: "name"; type: "string" } + } + Component { + name: "QDeclarativePolygonMapItem" + defaultProperty: "data" + prototype: "QDeclarativeGeoMapItemBase" + exports: ["QtLocation/MapPolygon 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "path"; type: "QJSValue" } + Property { name: "color"; type: "QColor" } + Property { + name: "border" + type: "QDeclarativeMapLineProperties" + isReadonly: true + isPointer: true + } + Signal { + name: "colorChanged" + Parameter { name: "color"; type: "QColor" } + } + Method { + name: "addCoordinate" + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + Method { + name: "removeCoordinate" + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + } + Component { + name: "QDeclarativePolylineMapItem" + defaultProperty: "data" + prototype: "QDeclarativeGeoMapItemBase" + exports: ["QtLocation/MapPolyline 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "path"; type: "QJSValue" } + Property { + name: "line" + type: "QDeclarativeMapLineProperties" + isReadonly: true + isPointer: true + } + Method { name: "pathLength"; type: "int" } + Method { + name: "addCoordinate" + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + Method { + name: "insertCoordinate" + Parameter { name: "index"; type: "int" } + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + Method { + name: "replaceCoordinate" + Parameter { name: "index"; type: "int" } + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + Method { + name: "coordinateAt" + type: "QGeoCoordinate" + Parameter { name: "index"; type: "int" } + } + Method { + name: "containsCoordinate" + type: "bool" + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + Method { + name: "removeCoordinate" + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + Method { + name: "removeCoordinate" + Parameter { name: "index"; type: "int" } + } + } + Component { + name: "QDeclarativeRatings" + prototype: "QObject" + exports: ["QtLocation/Ratings 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "ratings"; type: "QPlaceRatings" } + Property { name: "average"; type: "double" } + Property { name: "maximum"; type: "double" } + Property { name: "count"; type: "int" } + } + Component { + name: "QDeclarativeRectangleMapItem" + defaultProperty: "data" + prototype: "QDeclarativeGeoMapItemBase" + exports: ["QtLocation/MapRectangle 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "topLeft"; type: "QGeoCoordinate" } + Property { name: "bottomRight"; type: "QGeoCoordinate" } + Property { name: "color"; type: "QColor" } + Property { + name: "border" + type: "QDeclarativeMapLineProperties" + isReadonly: true + isPointer: true + } + Signal { + name: "topLeftChanged" + Parameter { name: "topLeft"; type: "QGeoCoordinate" } + } + Signal { + name: "bottomRightChanged" + Parameter { name: "bottomRight"; type: "QGeoCoordinate" } + } + Signal { + name: "colorChanged" + Parameter { name: "color"; type: "QColor" } + } + } + Component { + name: "QDeclarativeReviewModel" + prototype: "QDeclarativePlaceContentModel" + exports: ["QtLocation/ReviewModel 5.0"] + exportMetaObjectRevisions: [0] + } + Component { + name: "QDeclarativeRouteMapItem" + defaultProperty: "data" + prototype: "QDeclarativePolylineMapItem" + exports: ["QtLocation/MapRoute 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "route"; type: "QDeclarativeGeoRoute"; isPointer: true } + Signal { + name: "routeChanged" + Parameter { name: "route"; type: "const QDeclarativeGeoRoute"; isPointer: true } + } + } + Component { + name: "QDeclarativeSearchModelBase" + prototype: "QAbstractListModel" + Enum { + name: "Status" + values: { + "Null": 0, + "Ready": 1, + "Loading": 2, + "Error": 3 + } + } + Property { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + Property { name: "searchArea"; type: "QVariant" } + Property { name: "limit"; type: "int" } + Property { name: "previousPagesAvailable"; type: "bool"; isReadonly: true } + Property { name: "nextPagesAvailable"; type: "bool"; isReadonly: true } + Property { name: "status"; type: "Status"; isReadonly: true } + Method { name: "update" } + Method { name: "cancel" } + Method { name: "reset" } + Method { name: "errorString"; type: "string" } + Method { name: "previousPage" } + Method { name: "nextPage" } + } + Component { + name: "QDeclarativeSearchResultModel" + prototype: "QDeclarativeSearchModelBase" + exports: ["QtLocation/PlaceSearchModel 5.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "SearchResultType" + values: { + "UnknownSearchResult": 0, + "PlaceResult": 1, + "ProposedSearchResult": 2 + } + } + Enum { + name: "RelevanceHint" + values: { + "UnspecifiedHint": 0, + "DistanceHint": 1, + "LexicalPlaceNameHint": 2 + } + } + Property { name: "searchTerm"; type: "string" } + Property { name: "categories"; type: "QDeclarativeCategory"; isList: true; isReadonly: true } + Property { name: "recommendationId"; type: "string" } + Property { name: "relevanceHint"; type: "RelevanceHint" } + Property { name: "visibilityScope"; type: "QDeclarativePlace::Visibility" } + Property { name: "count"; type: "int"; isReadonly: true } + Property { name: "favoritesPlugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + Property { name: "favoritesMatchParameters"; type: "QVariantMap" } + Signal { name: "rowCountChanged" } + Signal { name: "dataChanged" } + Method { + name: "data" + type: "QVariant" + Parameter { name: "index"; type: "int" } + Parameter { name: "roleName"; type: "string" } + } + Method { + name: "updateWith" + Parameter { name: "proposedSearchIndex"; type: "int" } + } + } + Component { + name: "QDeclarativeSearchSuggestionModel" + prototype: "QDeclarativeSearchModelBase" + exports: ["QtLocation/PlaceSearchSuggestionModel 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "searchTerm"; type: "string" } + Property { name: "suggestions"; type: "QStringList"; isReadonly: true } + } + Component { + name: "QDeclarativeSupplier" + prototype: "QObject" + exports: ["QtLocation/Supplier 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "supplier"; type: "QPlaceSupplier" } + Property { name: "name"; type: "string" } + Property { name: "supplierId"; type: "string" } + Property { name: "url"; type: "QUrl" } + Property { name: "icon"; type: "QDeclarativePlaceIcon"; isPointer: true } + } + Component { + name: "QDeclarativeSupportedCategoriesModel" + prototype: "QAbstractItemModel" + exports: ["QtLocation/CategoryModel 5.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "Roles" + values: { + "CategoryRole": 256, + "ParentCategoryRole": 257 + } + } + Enum { + name: "Status" + values: { + "Null": 0, + "Ready": 1, + "Loading": 2, + "Error": 3 + } + } + Property { name: "plugin"; type: "QDeclarativeGeoServiceProvider"; isPointer: true } + Property { name: "hierarchical"; type: "bool" } + Property { name: "status"; type: "Status"; isReadonly: true } + Signal { name: "dataChanged" } + Method { name: "update" } + Method { + name: "data" + type: "QVariant" + Parameter { name: "index"; type: "QModelIndex" } + Parameter { name: "role"; type: "int" } + } + Method { name: "errorString"; type: "string" } + } + Component { + name: "QGeoMapPinchEvent" + prototype: "QObject" + exports: ["QtLocation/MapPinchEvent 5.0"] + isCreatable: false + exportMetaObjectRevisions: [0] + Property { name: "center"; type: "QPointF"; isReadonly: true } + Property { name: "angle"; type: "double"; isReadonly: true } + Property { name: "point1"; type: "QPointF"; isReadonly: true } + Property { name: "point2"; type: "QPointF"; isReadonly: true } + Property { name: "pointCount"; type: "int"; isReadonly: true } + Property { name: "accepted"; type: "bool" } + } + Component { + name: "QQmlPropertyMap" + prototype: "QObject" + exports: ["QtLocation/ExtendedAttributes 5.0"] + isCreatable: false + exportMetaObjectRevisions: [0] + Signal { + name: "valueChanged" + Parameter { name: "key"; type: "string" } + Parameter { name: "value"; type: "QVariant" } + } + Method { name: "keys"; type: "QStringList" } + } + Component { + name: "QQuickGeoMapGestureArea" + defaultProperty: "data" + prototype: "QQuickItem" + exports: [ + "QtLocation/MapGestureArea 5.0", + "QtLocation/MapGestureArea 5.6" + ] + isCreatable: false + exportMetaObjectRevisions: [0, 1] + Enum { + name: "GeoMapGesture" + values: { + "NoGesture": 0, + "PinchGesture": 1, + "PanGesture": 2, + "FlickGesture": 4 + } + } + Enum { + name: "AcceptedGestures" + values: { + "NoGesture": 0, + "PinchGesture": 1, + "PanGesture": 2, + "FlickGesture": 4 + } + } + Property { name: "enabled"; type: "bool" } + Property { name: "pinchActive"; type: "bool"; isReadonly: true } + Property { name: "panActive"; type: "bool"; isReadonly: true } + Property { name: "acceptedGestures"; type: "AcceptedGestures" } + Property { name: "maximumZoomLevelChange"; type: "double" } + Property { name: "flickDeceleration"; type: "double" } + Property { name: "preventStealing"; revision: 1; type: "bool" } + Signal { + name: "pinchStarted" + Parameter { name: "pinch"; type: "QGeoMapPinchEvent"; isPointer: true } + } + Signal { + name: "pinchUpdated" + Parameter { name: "pinch"; type: "QGeoMapPinchEvent"; isPointer: true } + } + Signal { + name: "pinchFinished" + Parameter { name: "pinch"; type: "QGeoMapPinchEvent"; isPointer: true } + } + Signal { name: "panStarted" } + Signal { name: "panFinished" } + Signal { name: "flickStarted" } + Signal { name: "flickFinished" } + } +} diff --git a/src/imports/location/qdeclarativecirclemapitem.cpp b/src/imports/location/qdeclarativecirclemapitem.cpp new file mode 100644 index 0000000..f6b3c14 --- /dev/null +++ b/src/imports/location/qdeclarativecirclemapitem.cpp @@ -0,0 +1,657 @@ +/*************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativecirclemapitem_p.h" +#include "qdeclarativepolygonmapitem_p.h" +#include "qgeocameracapabilities_p.h" +#include "qgeoprojection_p.h" + +#include + +#include +#include +#include + +#include "qdoublevector2d_p.h" + +/* poly2tri triangulator includes */ +#include "../../3rdparty/poly2tri/common/shapes.h" +#include "../../3rdparty/poly2tri/sweep/cdt.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapCircle + \instantiates QDeclarativeCircleMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapCircle type displays a geographic circle on a Map. + + The MapCircle type displays a geographic circle on a Map, which + consists of all points that are within a set distance from one + central point. Depending on map projection, a geographic circle + may not always be a perfect circle on the screen: for instance, in + the Mercator projection, circles become ovoid in shape as they near + the poles. To display a perfect screen circle around a point, use a + MapQuickItem containing a relevant Qt Quick type instead. + + By default, the circle is displayed as a 1 pixel black border with + no fill. To change its appearance, use the color, border.color + and border.width properties. + + Internally, a MapCircle is implemented as a many-sided polygon. To + calculate the radius points it uses a spherical model of the Earth, + similar to the atDistanceAndAzimuth method of the \l {coordinate} + type. These two things can occasionally have implications for the + accuracy of the circle's shape, depending on position and map + projection. + + \note Dragging a MapCircle (through the use of \l MouseArea) + causes new points to be generated at the same distance (in meters) + from the center. This is in contrast to other map items which store + their dimensions in terms of latitude and longitude differences between + vertices. + + \section2 Performance + + MapCircle performance is almost equivalent to that of a MapPolygon with + the same number of vertices. There is a small amount of additional + overhead with respect to calculating the vertices first. + + Like the other map objects, MapCircle is normally drawn without a smooth + appearance. Setting the opacity property will force the object to be + blended, which decreases performance considerably depending on the graphics + hardware in use. + + \section2 Example Usage + + The following snippet shows a map containing a MapCircle, centered at + the coordinate (-27, 153) with a radius of 5km. The circle is + filled in green, with a 3 pixel black border. + + \code + Map { + MapCircle { + center { + latitude: -27.5 + longitude: 153.0 + } + radius: 5000.0 + color: 'green' + border.width: 3 + } + } + \endcode + + \image api-mapcircle.png +*/ + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +static const int CircleSamples = 128; + +struct Vertex +{ + QVector2D position; +}; + +QGeoMapCircleGeometry::QGeoMapCircleGeometry() +{ +} + +/*! + \internal +*/ +void QGeoMapCircleGeometry::updateScreenPointsInvert(const QGeoMap &map) +{ + if (!screenDirty_) + return; + + if (map.width() == 0 || map.height() == 0) { + clear(); + return; + } + + QPointF origin = map.coordinateToItemPosition(srcOrigin_, false).toPointF(); + + QPainterPath ppi = srcPath_; + + clear(); + + // a circle requires at least 3 points; + if (ppi.elementCount() < 3) + return; + + // translate the path into top-left-centric coordinates + QRectF bb = ppi.boundingRect(); + ppi.translate(-bb.left(), -bb.top()); + firstPointOffset_ = -1 * bb.topLeft(); + + ppi.closeSubpath(); + + // calculate actual width of map on screen in pixels + QGeoCoordinate mapCenter(0, map.cameraData().center().longitude()); + QDoubleVector2D midPoint = map.coordinateToItemPosition(mapCenter, false); + QDoubleVector2D midPointPlusOne = QDoubleVector2D(midPoint.x() + 1.0, midPoint.y()); + QGeoCoordinate coord1 = map.itemPositionToCoordinate(midPointPlusOne, false); + double geoDistance = coord1.longitude() - map.cameraData().center().longitude(); + if ( geoDistance < 0 ) + geoDistance += 360.0; + double mapWidth = 360.0 / geoDistance; + + qreal leftOffset = origin.x() - (map.width()/2.0 - mapWidth/2.0) - firstPointOffset_.x(); + qreal topOffset = origin.y() - (midPoint.y() - mapWidth/2.0) - firstPointOffset_.y(); + QPainterPath ppiBorder; + ppiBorder.moveTo(QPointF(-leftOffset, -topOffset)); + ppiBorder.lineTo(QPointF(mapWidth - leftOffset, -topOffset)); + ppiBorder.lineTo(QPointF(mapWidth - leftOffset, mapWidth - topOffset)); + ppiBorder.lineTo(QPointF(-leftOffset, mapWidth - topOffset)); + + screenOutline_ = ppiBorder; + + std::vector borderPts; + borderPts.reserve(4); + + std::vector curPts; + curPts.reserve(ppi.elementCount()); + for (int i = 0; i < ppi.elementCount(); ++i) { + const QPainterPath::Element e = ppi.elementAt(i); + if (e.isMoveTo() || i == ppi.elementCount() - 1 + || (qAbs(e.x - curPts.front()->x) < 0.1 + && qAbs(e.y - curPts.front()->y) < 0.1)) { + if (curPts.size() > 2) { + for (int j = 0; j < 4; ++j) { + const QPainterPath::Element e2 = ppiBorder.elementAt(j); + borderPts.push_back(new p2t::Point(e2.x, e2.y)); + } + p2t::CDT *cdt = new p2t::CDT(borderPts); + cdt->AddHole(curPts); + cdt->Triangulate(); + std::vector tris = cdt->GetTriangles(); + screenVertices_.reserve(screenVertices_.size() + int(tris.size())); + for (size_t i = 0; i < tris.size(); ++i) { + p2t::Triangle *t = tris.at(i); + for (int j = 0; j < 3; ++j) { + p2t::Point *p = t->GetPoint(j); + screenVertices_ << QPointF(p->x, p->y); + } + } + delete cdt; + } + curPts.clear(); + curPts.reserve(ppi.elementCount() - i); + curPts.push_back(new p2t::Point(e.x, e.y)); + } else if (e.isLineTo()) { + curPts.push_back(new p2t::Point(e.x, e.y)); + } else { + qWarning("Unhandled element type in circle painterpath"); + } + } + + if (curPts.size() > 0) { + qDeleteAll(curPts.begin(), curPts.end()); + curPts.clear(); + } + + if (borderPts.size() > 0) { + qDeleteAll(borderPts.begin(), borderPts.end()); + borderPts.clear(); + } + + screenBounds_ = ppiBorder.boundingRect(); + +} + +static const qreal qgeocoordinate_EARTH_MEAN_RADIUS = 6371.0072; + +inline static qreal qgeocoordinate_degToRad(qreal deg) +{ + return deg * M_PI / 180; +} +inline static qreal qgeocoordinate_radToDeg(qreal rad) +{ + return rad * 180 / M_PI; +} + +static bool crossEarthPole(const QGeoCoordinate ¢er, qreal distance) +{ + qreal poleLat = 90; + QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude()); + QGeoCoordinate southPole = QGeoCoordinate(-poleLat, center.longitude()); + // approximate using great circle distance + qreal distanceToNorthPole = center.distanceTo(northPole); + qreal distanceToSouthPole = center.distanceTo(southPole); + if (distanceToNorthPole < distance || distanceToSouthPole < distance) + return true; + return false; +} + +static void calculatePeripheralPoints(QList &path, + const QGeoCoordinate ¢er, + qreal distance, + int steps, + QGeoCoordinate &leftBound ) +{ + // Calculate points based on great-circle distance + // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function + // but tweaked here for computing multiple points + + // pre-calculations + steps = qMax(steps, 3); + qreal centerLon = center.longitude(); + qreal minLon = centerLon; + qreal latRad = qgeocoordinate_degToRad(center.latitude()); + qreal lonRad = qgeocoordinate_degToRad(centerLon); + qreal cosLatRad = std::cos(latRad); + qreal sinLatRad = std::sin(latRad); + qreal ratio = (distance / (qgeocoordinate_EARTH_MEAN_RADIUS * 1000.0)); + qreal cosRatio = std::cos(ratio); + qreal sinRatio = std::sin(ratio); + qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio; + qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio; + int idx = 0; + for (int i = 0; i < steps; ++i) { + qreal azimuthRad = 2 * M_PI * i / steps; + qreal resultLatRad = std::asin(sinLatRad_x_cosRatio + + cosLatRad_x_sinRatio * std::cos(azimuthRad)); + qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio, + cosRatio - sinLatRad * std::sin(resultLatRad)); + qreal lat2 = qgeocoordinate_radToDeg(resultLatRad); + qreal lon2 = qgeocoordinate_radToDeg(resultLonRad); + if (lon2 < -180.0) { + lon2 += 360.0; + } else if (lon2 > 180.0) { + lon2 -= 360.0; + } + path << QGeoCoordinate(lat2, lon2, center.altitude()); + // Consider only points in the left half of the circle for the left bound. + if (azimuthRad > M_PI) { + if (lon2 > centerLon) // if point and center are on different hemispheres + lon2 -= 360; + if (lon2 < minLon) { + minLon = lon2; + idx = i; + } + } + } + leftBound = path.at(idx); +} + +QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), color_(Qt::transparent), radius_(0), dirtyMaterial_(true), + updatingGeometry_(false) +{ + setFlag(ItemHasContents, true); + QObject::connect(&border_, SIGNAL(colorChanged(QColor)), + this, SLOT(markSourceDirtyAndUpdate())); + QObject::connect(&border_, SIGNAL(widthChanged(qreal)), + this, SLOT(markSourceDirtyAndUpdate())); + + // assume that circles are not self-intersecting + // to speed up processing + // FIXME: unfortunately they self-intersect at the poles due to current drawing method + // so the line is commented out until fixed + //geometry_.setAssumeSimple(true); + +} + +QDeclarativeCircleMapItem::~QDeclarativeCircleMapItem() +{ +} + +/*! + \qmlpropertygroup Location::MapCircle::border + \qmlproperty int MapCircle::border.width + \qmlproperty color MapCircle::border.color + + This property is part of the border group property. + The border property holds the width and color used to draw the border of the circle. + The width is in pixels and is independent of the zoom level of the map. + + The default values correspond to a black border with a width of 1 pixel. + For no line, use a width of 0 or a transparent color. +*/ +QDeclarativeMapLineProperties *QDeclarativeCircleMapItem::border() +{ + return &border_; +} + +void QDeclarativeCircleMapItem::markSourceDirtyAndUpdate() +{ + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); +} + +void QDeclarativeCircleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (map) + markSourceDirtyAndUpdate(); +} + +/*! + \qmlproperty coordinate MapCircle::center + + This property holds the central point about which the circle is defined. + + \sa radius +*/ +void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate ¢er) +{ + if (center_ == center) + return; + + center_ = center; + markSourceDirtyAndUpdate(); + emit centerChanged(center_); +} + +QGeoCoordinate QDeclarativeCircleMapItem::center() +{ + return center_; +} + +/*! + \qmlproperty color MapCircle::color + + This property holds the fill color of the circle when drawn. For no fill, + use a transparent color. +*/ +void QDeclarativeCircleMapItem::setColor(const QColor &color) +{ + if (color_ == color) + return; + color_ = color; + dirtyMaterial_ = true; + update(); + emit colorChanged(color_); +} + +QColor QDeclarativeCircleMapItem::color() const +{ + return color_; +} + +/*! + \qmlproperty real MapCircle::radius + + This property holds the radius of the circle, in meters on the ground. + + \sa center +*/ +void QDeclarativeCircleMapItem::setRadius(qreal radius) +{ + if (radius_ == radius) + return; + + radius_ = radius; + markSourceDirtyAndUpdate(); + emit radiusChanged(radius); +} + +qreal QDeclarativeCircleMapItem::radius() const +{ + return radius_; +} + +/*! + \qmlproperty real MapCircle::opacity + + This property holds the opacity of the item. Opacity is specified as a + number between 0 (fully transparent) and 1 (fully opaque). The default is 1. + + An item with 0 opacity will still receive mouse events. To stop mouse events, set the + visible property of the item to false. +*/ + +/*! + \internal +*/ +QSGNode *QDeclarativeCircleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + MapPolygonNode *node = static_cast(oldNode); + + if (!node) + node = new MapPolygonNode(); + + //TODO: update only material + if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { + node->update(color_, border_.color(), &geometry_, &borderGeometry_); + geometry_.setPreserveGeometry(false); + borderGeometry_.setPreserveGeometry(false); + geometry_.markClean(); + borderGeometry_.markClean(); + dirtyMaterial_ = false; + } + return node; +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::updatePolish() +{ + if (!map() || !center().isValid() || qIsNaN(radius_) || radius_ <= 0.0) + return; + + QScopedValueRollback rollback(updatingGeometry_); + updatingGeometry_ = true; + + if (geometry_.isSourceDirty()) { + circlePath_.clear(); + calculatePeripheralPoints(circlePath_, center_, radius_, CircleSamples, geoLeftBound_); + } + + int pathCount = circlePath_.size(); + bool preserve = preserveCircleGeometry(circlePath_, center_, radius_); + geometry_.setPreserveGeometry(preserve, geoLeftBound_); + geometry_.updateSourcePoints(*map(), circlePath_); + if (crossEarthPole(center_, radius_) && circlePath_.size() == pathCount) + geometry_.updateScreenPointsInvert(*map()); // invert fill area for really huge circles + else geometry_.updateScreenPoints(*map()); + + if (border_.color() != Qt::transparent && border_.width() > 0) { + QList closedPath = circlePath_; + closedPath << closedPath.first(); + borderGeometry_.setPreserveGeometry(preserve, geoLeftBound_); + borderGeometry_.updateSourcePoints(*map(), closedPath, geoLeftBound_); + borderGeometry_.updateScreenPoints(*map(), border_.width()); + + QList geoms; + geoms << &geometry_ << &borderGeometry_; + QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); + + setWidth(combined.width()); + setHeight(combined.height()); + } else { + borderGeometry_.clear(); + setWidth(geometry_.screenBoundingBox().width()); + setHeight(geometry_.screenBoundingBox().height()); + } + + setPositionOnMap(circlePath_.at(0), geometry_.firstPointOffset()); +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + return; + + // if the scene is tilted, we must regenerate our geometry every frame + if (map()->cameraCapabilities().supportsTilting() + && (event.cameraData.tilt() > 0.1 + || event.cameraData.tilt() < -0.1)) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + + // if the scene is rolled, we must regen too + if (map()->cameraCapabilities().supportsRolling() + && (event.cameraData.roll() > 0.1 + || event.cameraData.roll() < -0.1)) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + + // otherwise, only regen on rotate, resize and zoom + if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + + if (event.centerChanged && crossEarthPole(center_, radius_)) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + + geometry_.markScreenDirty(); + borderGeometry_.markScreenDirty(); + polishAndUpdate(); +} + +/*! + \internal +*/ +bool QDeclarativeCircleMapItem::contains(const QPointF &point) const +{ + return (geometry_.contains(point) || borderGeometry_.contains(point)); +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (updatingGeometry_ || newGeometry == oldGeometry) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + + QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) / 2; + QGeoCoordinate newCoordinate = map()->itemPositionToCoordinate(newPoint, false); + if (newCoordinate.isValid()) + setCenter(newCoordinate); + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList &path, + const QGeoCoordinate ¢er, qreal distance) +{ + // if circle crosses north/south pole, then don't preserve circular shape, + if ( crossEarthPole(center, distance)) { + updateCirclePathForRendering(path, center, distance); + return false; + } + return true; + +} + + +// A workaround for circle path to be drawn correctly using a polygon geometry +void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList &path, + const QGeoCoordinate ¢er, + qreal distance) +{ + qreal poleLat = 90; + qreal distanceToNorthPole = center.distanceTo(QGeoCoordinate(poleLat, 0)); + qreal distanceToSouthPole = center.distanceTo(QGeoCoordinate(-poleLat, 0)); + bool crossNorthPole = distanceToNorthPole < distance; + bool crossSouthPole = distanceToSouthPole < distance; + if (!crossNorthPole && !crossSouthPole) + return; + QList wrapPathIndex; + // calculate actual width of map on screen in pixels + QDoubleVector2D midPoint = map()->coordinateToItemPosition(map()->cameraData().center(), false); + QDoubleVector2D midPointPlusOne(midPoint.x() + 1.0, midPoint.y()); + QGeoCoordinate coord1 = map()->itemPositionToCoordinate(midPointPlusOne, false); + qreal geoDistance = coord1.longitude() - map()->cameraData().center().longitude(); + if ( geoDistance < 0 ) + geoDistance += 360; + qreal mapWidth = 360.0 / geoDistance; + mapWidth = qMin(static_cast(mapWidth), map()->width()); + QDoubleVector2D prev = map()->coordinateToItemPosition(path.at(0), false); + // find the points in path where wrapping occurs + for (int i = 1; i <= path.count(); ++i) { + int index = i % path.count(); + QDoubleVector2D point = map()->coordinateToItemPosition(path.at(index), false); + if ( (qAbs(point.x() - prev.x())) >= mapWidth/2.0 ) { + wrapPathIndex << index; + if (wrapPathIndex.size() == 2 || !(crossNorthPole && crossSouthPole)) + break; + } + prev = point; + } + // insert two additional coords at top/bottom map corners of the map for shape + // to be drawn correctly + if (wrapPathIndex.size() > 0) { + qreal newPoleLat = 90; + QGeoCoordinate wrapCoord = path.at(wrapPathIndex[0]); + if (wrapPathIndex.size() == 2) { + QGeoCoordinate wrapCoord2 = path.at(wrapPathIndex[1]); + if (wrapCoord2.latitude() > wrapCoord.latitude()) + newPoleLat = -90; + } else if (center.latitude() < 0) { + newPoleLat = -90; + } + for (int i = 0; i < wrapPathIndex.size(); ++i) { + int index = wrapPathIndex[i] == 0 ? 0 : wrapPathIndex[i] + i*2; + int prevIndex = (index - 1) < 0 ? (path.count() - 1): index - 1; + QGeoCoordinate coord0 = path.at(prevIndex); + QGeoCoordinate coord1 = path.at(index); + coord0.setLatitude(newPoleLat); + coord1.setLatitude(newPoleLat); + path.insert(index ,coord1); + path.insert(index, coord0); + newPoleLat = -newPoleLat; + } + } +} + +////////////////////////////////////////////////////////////////////// + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativecirclemapitem_p.h b/src/imports/location/qdeclarativecirclemapitem_p.h new file mode 100644 index 0000000..c91d160 --- /dev/null +++ b/src/imports/location/qdeclarativecirclemapitem_p.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVECIRCLEMAPITEM_H +#define QDECLARATIVECIRCLEMAPITEM_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeomapitembase_p.h" +#include "qdeclarativepolylinemapitem_p.h" +#include "qdeclarativepolygonmapitem_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoMapCircleGeometry : public QGeoMapPolygonGeometry +{ +public: + QGeoMapCircleGeometry(); + + void updateScreenPointsInvert(const QGeoMap &map); +}; + +class QDeclarativeCircleMapItem : public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + Q_PROPERTY(QGeoCoordinate center READ center WRITE setCenter NOTIFY centerChanged) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QDeclarativeMapLineProperties *border READ border CONSTANT) + +public: + explicit QDeclarativeCircleMapItem(QQuickItem *parent = 0); + ~QDeclarativeCircleMapItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + + QGeoCoordinate center(); + void setCenter(const QGeoCoordinate ¢er); + + qreal radius() const; + void setRadius(qreal radius); + + QColor color() const; + void setColor(const QColor &color); + + QDeclarativeMapLineProperties *border(); + + bool contains(const QPointF &point) const Q_DECL_OVERRIDE; + +Q_SIGNALS: + void centerChanged(const QGeoCoordinate ¢er); + void radiusChanged(qreal radius); + void colorChanged(const QColor &color); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void updatePolish() Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void markSourceDirtyAndUpdate(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + bool preserveCircleGeometry(QList &path, const QGeoCoordinate ¢er, + qreal distance); + void updateCirclePathForRendering(QList &path, const QGeoCoordinate ¢er, + qreal distance); + +private: + QGeoCoordinate center_; + QDeclarativeMapLineProperties border_; + QColor color_; + qreal radius_; + QGeoCoordinate geoLeftBound_; + QList circlePath_; + bool dirtyMaterial_; + QGeoMapCircleGeometry geometry_; + QGeoMapPolylineGeometry borderGeometry_; + bool updatingGeometry_; +}; + +////////////////////////////////////////////////////////////////////// + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeCircleMapItem) + +#endif /* QDECLARATIVECIRCLEMAPITEM_H */ diff --git a/src/imports/location/qdeclarativegeocodemodel.cpp b/src/imports/location/qdeclarativegeocodemodel.cpp new file mode 100644 index 0000000..d8ff4e8 --- /dev/null +++ b/src/imports/location/qdeclarativegeocodemodel.cpp @@ -0,0 +1,727 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeocodemodel_p.h" +#include "error_messages.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype GeocodeModel + \instantiates QDeclarativeGeocodeModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-geocoding + \since Qt Location 5.5 + + \brief The GeocodeModel type provides support for searching operations + related to geographic information. + + The GeocodeModel type is used as part of a model/view grouping to + match addresses or search strings with geographic locations. How the + geographic locations generated are used or displayed is decided by any + Views attached to the GeocodeModel (for example a \l MapItemView or \l{ListView}). + + Like \l Map and \l RouteModel, all the data for a GeocodeModel to work + comes from a services plugin. This is contained in the \l{plugin} property, + and this must be set before the GeocodeModel can do any useful work. + + Once the plugin is set, the \l{query} property can be used to specify the + address or search string to match. If \l{autoUpdate} is enabled, the Model + will update its output automatically. Otherwise, the \l{update} method may + be used. By default, autoUpdate is disabled. + + The data stored and returned in the GeocodeModel consists of \l{Location} + objects, as a list with the role name "locationData". See the documentation + for \l{Location} for further details on its structure and contents. + + \section2 Example Usage + + The following snippet is two-part, showing firstly the declaration of + objects, and secondly a short piece of procedural code using it. We set + the geocodeModel's \l{autoUpdate} property to false, and call \l{update} once + the query is set up. In this case, as we use a string value in \l{query}, + only one update would occur, even with autoUpdate enabled. However, if we + provided an \l{Address} object we may inadvertently trigger multiple + requests whilst setting its properties. + + \code + Plugin { + id: aPlugin + } + + GeocodeModel { + id: geocodeModel + plugin: aPlugin + autoUpdate: false + } + \endcode + + \code + { + geocodeModel.query = "53 Brandl St, Eight Mile Plains, Australia" + geocodeModel.update() + } + \endcode +*/ + +/*! + \qmlsignal QtLocation::GeocodeModel::locationsChanged() + + This signal is emitted when locations in the model have changed. + + \sa count +*/ + + +QDeclarativeGeocodeModel::QDeclarativeGeocodeModel(QObject *parent) +: QAbstractListModel(parent), autoUpdate_(false), complete_(false), reply_(0), plugin_(0), + status_(QDeclarativeGeocodeModel::Null), error_(QDeclarativeGeocodeModel::NoError), + address_(0), limit_(-1), offset_(0) +{ +} + +QDeclarativeGeocodeModel::~QDeclarativeGeocodeModel() +{ + qDeleteAll(declarativeLocations_); + declarativeLocations_.clear(); + delete reply_; +} + +/*! + \internal + From QQmlParserStatus +*/ +void QDeclarativeGeocodeModel::componentComplete() +{ + complete_ = true; + if (autoUpdate_) + update(); +} + +/*! + \qmlmethod void QtLocation::GeocodeModel::update() + + Instructs the GeocodeModel to update its data. This is most useful + when \l autoUpdate is disabled, to force a refresh when the query + has been changed. +*/ +void QDeclarativeGeocodeModel::update() +{ + if (!complete_) + return; + + if (!plugin_) { + setError(EngineNotSetError, tr("Cannot geocode, plugin not set.")); + return; + } + + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + if (!serviceProvider) + return; + + QGeoCodingManager *geocodingManager = serviceProvider->geocodingManager(); + if (!geocodingManager) { + setError(EngineNotSetError, tr("Cannot geocode, geocode manager not set.")); + return; + } + if (!coordinate_.isValid() && (!address_ || address_->address().isEmpty()) && + (searchString_.isEmpty())) { + setError(ParseError, tr("Cannot geocode, valid query not set.")); + return; + } + abortRequest(); // abort possible previous requests + setError(NoError, QString()); + + if (coordinate_.isValid()) { + setStatus(QDeclarativeGeocodeModel::Loading); + reply_ = geocodingManager->reverseGeocode(coordinate_, boundingArea_); + if (reply_->isFinished()) { + if (reply_->error() == QGeoCodeReply::NoError) { + geocodeFinished(reply_); + } else { + geocodeError(reply_, reply_->error(), reply_->errorString()); + } + } + } else if (address_) { + setStatus(QDeclarativeGeocodeModel::Loading); + reply_ = geocodingManager->geocode(address_->address(), boundingArea_); + if (reply_->isFinished()) { + if (reply_->error() == QGeoCodeReply::NoError) { + geocodeFinished(reply_); + } else { + geocodeError(reply_, reply_->error(), reply_->errorString()); + } + } + } else if (!searchString_.isEmpty()) { + setStatus(QDeclarativeGeocodeModel::Loading); + reply_ = geocodingManager->geocode(searchString_, limit_, offset_, boundingArea_); + if (reply_->isFinished()) { + if (reply_->error() == QGeoCodeReply::NoError) { + geocodeFinished(reply_); + } else { + geocodeError(reply_, reply_->error(), reply_->errorString()); + } + } + } +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::abortRequest() +{ + if (reply_) { + reply_->abort(); + reply_->deleteLater(); + reply_ = 0; + } +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::queryContentChanged() +{ + if (autoUpdate_) + update(); +} + +/*! + \internal + From QAbstractListModel +*/ +int QDeclarativeGeocodeModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return declarativeLocations_.count(); +} + +/*! + \internal +*/ +QVariant QDeclarativeGeocodeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (index.row() >= declarativeLocations_.count()) + return QVariant(); + if (role == QDeclarativeGeocodeModel::LocationRole) { + QObject *locationObject = declarativeLocations_.at(index.row()); + Q_ASSERT(locationObject); + return QVariant::fromValue(locationObject); + } + return QVariant(); +} + +QHash QDeclarativeGeocodeModel::roleNames() const +{ + QHash roleNames = QAbstractItemModel::roleNames(); + roleNames.insert(LocationRole, "locationData"); + return roleNames; +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (plugin_ == plugin) + return; + + reset(); // reset the model + plugin_ = plugin; + if (complete_) + emit pluginChanged(); + + if (!plugin) + return; + + if (plugin_->isAttached()) { + pluginReady(); + } else { + connect(plugin_, SIGNAL(attached()), + this, SLOT(pluginReady())); + } +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::pluginReady() +{ + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + QGeoCodingManager *geocodingManager = serviceProvider->geocodingManager(); + + if (serviceProvider->error() != QGeoServiceProvider::NoError) { + QDeclarativeGeocodeModel::GeocodeError newError = UnknownError; + switch (serviceProvider->error()) { + case QGeoServiceProvider::NotSupportedError: + newError = EngineNotSetError; break; + case QGeoServiceProvider::UnknownParameterError: + newError = UnknownParameterError; break; + case QGeoServiceProvider::MissingRequiredParameterError: + newError = MissingRequiredParameterError; break; + case QGeoServiceProvider::ConnectionError: + newError = CommunicationError; break; + default: + break; + } + + setError(newError, serviceProvider->errorString()); + return; + } + + if (!geocodingManager) { + setError(EngineNotSetError,tr("Plugin does not support (reverse) geocoding.")); + return; + } + + connect(geocodingManager, SIGNAL(finished(QGeoCodeReply*)), + this, SLOT(geocodeFinished(QGeoCodeReply*))); + connect(geocodingManager, SIGNAL(error(QGeoCodeReply*,QGeoCodeReply::Error,QString)), + this, SLOT(geocodeError(QGeoCodeReply*,QGeoCodeReply::Error,QString))); +} + +/*! + \qmlproperty Plugin QtLocation::GeocodeModel::plugin + + This property holds the plugin that provides the actual geocoding service. + Note that all plugins do not necessarily provide geocoding (could for example provide + only routing or maps). + + \sa Plugin +*/ + +QDeclarativeGeoServiceProvider *QDeclarativeGeocodeModel::plugin() const +{ + return plugin_; +} + +void QDeclarativeGeocodeModel::setBounds(const QVariant &boundingArea) +{ + QGeoShape s; + + if (boundingArea.userType() == qMetaTypeId()) + s = boundingArea.value(); + else if (boundingArea.userType() == qMetaTypeId()) + s = boundingArea.value(); + else if (boundingArea.userType() == qMetaTypeId()) + s = boundingArea.value(); + + + if (boundingArea_ == s) + return; + + boundingArea_ = s; + emit boundsChanged(); +} + +/*! + \qmlproperty geoshape QtLocation::GeocodeModel::bounds + + This property holds the bounding area used to limit the results to those + within the area. This is particularly useful if query is only partially filled out, + as the service will attempt to (reverse) geocode all matches for the specified data. + + Accepted types are \l {georectangle} and + \l {geocircle}. +*/ +QVariant QDeclarativeGeocodeModel::bounds() const +{ + if (boundingArea_.type() == QGeoShape::RectangleType) + return QVariant::fromValue(QGeoRectangle(boundingArea_)); + else if (boundingArea_.type() == QGeoShape::CircleType) + return QVariant::fromValue(QGeoCircle(boundingArea_)); + else + return QVariant::fromValue(boundingArea_); +} + +void QDeclarativeGeocodeModel::geocodeFinished(QGeoCodeReply *reply) +{ + if (reply != reply_ || reply->error() != QGeoCodeReply::NoError) + return; + int oldCount = declarativeLocations_.count(); + setLocations(reply->locations()); + setError(NoError, QString()); + setStatus(QDeclarativeGeocodeModel::Ready); + reply->deleteLater(); + reply_ = 0; + emit locationsChanged(); + if (oldCount != declarativeLocations_.count()) + emit countChanged(); +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::geocodeError(QGeoCodeReply *reply, + QGeoCodeReply::Error error, + const QString &errorString) +{ + if (reply != reply_) + return; + Q_UNUSED(error); + int oldCount = declarativeLocations_.count(); + if (oldCount > 0) { + // Reset the model + setLocations(reply->locations()); + emit locationsChanged(); + emit countChanged(); + } + setError(static_cast(error), errorString); + setStatus(QDeclarativeGeocodeModel::Error); + reply->deleteLater(); + reply_ = 0; +} + +/*! + \qmlproperty enumeration QtLocation::GeocodeModel::status + + This read-only property holds the current status of the model. + + \list + \li GeocodeModel.Null - No geocode requests have been issued or \l reset has been called. + \li GeocodeModel.Ready - Geocode request(s) have finished successfully. + \li GeocodeModel.Loading - Geocode request has been issued but not yet finished + \li GeocodeModel.Error - Geocoding error has occurred, details are in \l error and \l errorString + \endlist +*/ + +QDeclarativeGeocodeModel::Status QDeclarativeGeocodeModel::status() const +{ + return status_; +} + +void QDeclarativeGeocodeModel::setStatus(QDeclarativeGeocodeModel::Status status) +{ + if (status_ == status) + return; + status_ = status; + emit statusChanged(); +} + +/*! + \qmlproperty enumeration QtLocation::GeocodeModel::error + + This read-only property holds the latest error value of the geocoding request. + + \list + \li GeocodeModel.NoError - No error has occurred. + \li GeocodeModel.CombinationError - An error occurred while results where being combined from multiple sources. + \li GeocodeModel.CommunicationError - An error occurred while communicating with the service provider. + \li GeocodeModel.EngineNotSetError - The model's plugin property was not set or there is no geocoding manager associated with the plugin. + \li GeocodeModel.MissingRequiredParameterError - A required parameter was not specified. + \li GeocodeModel.ParseError - The response from the service provider was in an unrecognizable format. + \li GeocodeModel.UnknownError - An error occurred which does not fit into any of the other categories. + \li GeocodeModel.UnknownParameterError - The plugin did not recognize one of the parameters it was given. + \li GeocodeModel.UnsupportedOptionError - The requested operation is not supported by the geocoding provider. + This may happen when the loaded engine does not support a particular geocoding request + such as reverse geocoding. + \endlist +*/ + +QDeclarativeGeocodeModel::GeocodeError QDeclarativeGeocodeModel::error() const +{ + return error_; +} + +void QDeclarativeGeocodeModel::setError(GeocodeError error, const QString &errorString) +{ + if (error_ == error && errorString_ == errorString) + return; + error_ = error; + errorString_ = errorString; + emit errorChanged(); +} + +/*! + \qmlproperty string QtLocation::GeocodeModel::errorString + + This read-only property holds the textual presentation of the latest geocoding error. + If no error has occurred or the model has been reset, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. +*/ + +QString QDeclarativeGeocodeModel::errorString() const +{ + return errorString_; +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::setLocations(const QList &locations) +{ + beginResetModel(); + qDeleteAll(declarativeLocations_); + declarativeLocations_.clear(); + for (int i = 0; i < locations.count(); ++i) { + QDeclarativeGeoLocation *location = new QDeclarativeGeoLocation(locations.at(i), this); + declarativeLocations_.append(location); + } + endResetModel(); +} + +/*! + \qmlproperty int QtLocation::GeocodeModel::count + + This property holds how many locations the model currently has. + Amongst other uses, you can use this value when accessing locations + via the GeocodeModel::get -method. +*/ + +int QDeclarativeGeocodeModel::count() const +{ + return declarativeLocations_.count(); +} + +/*! + \qmlmethod Location QtLocation::GeocodeModel::get(int) + + Returns the Location at given index. Use \l count property to check the + amount of locations available. The locations are indexed from zero, so the accessible range + is 0...(count - 1). + + If you access out of bounds, a zero (null object) is returned and a warning is issued. +*/ + +QDeclarativeGeoLocation *QDeclarativeGeocodeModel::get(int index) +{ + if (index < 0 || index >= declarativeLocations_.count()) { + qmlInfo(this) << QStringLiteral("Index '%1' out of range").arg(index); + return 0; + } + return declarativeLocations_.at(index); +} + +/*! + \qmlproperty int QtLocation::GeocodeModel::limit + + This property holds the maximum number of results. The limit and \l offset values are only + applicable with free string geocoding (that is they are not considered when using addresses + or coordinates in the search query). + + If limit is -1 the entire result set will be returned, otherwise at most limit results will be + returned. The limit and \l offset results can be used together to implement paging. +*/ + +int QDeclarativeGeocodeModel::limit() const +{ + return limit_; +} + +void QDeclarativeGeocodeModel::setLimit(int limit) +{ + if (limit == limit_) + return; + limit_ = limit; + if (autoUpdate_) { + update(); + } + emit limitChanged(); +} + +/*! + \qmlproperty int QtLocation::GeocodeModel::offset + + This property tells not to return the first 'offset' number of the results. The \l limit and + offset values are only applicable with free string geocoding (that is they are not considered + when using addresses or coordinates in the search query). + + The \l limit and offset results can be used together to implement paging. +*/ + +int QDeclarativeGeocodeModel::offset() const +{ + return offset_; +} + +void QDeclarativeGeocodeModel::setOffset(int offset) +{ + if (offset == offset_) + return; + offset_ = offset; + if (autoUpdate_) { + update(); + } + emit offsetChanged(); +} + +/*! + \qmlmethod void QtLocation::GeocodeModel::reset() + + Resets the model. All location data is cleared, any outstanding requests + are aborted and possible errors are cleared. Model status will be set + to GeocodeModel.Null +*/ + +void QDeclarativeGeocodeModel::reset() +{ + beginResetModel(); + if (!declarativeLocations_.isEmpty()) { + setLocations(QList()); + emit countChanged(); + } + endResetModel(); + + abortRequest(); + setError(NoError, QString()); + setStatus(QDeclarativeGeocodeModel::Null); +} + +/*! + \qmlmethod void QtLocation::GeocodeModel::cancel() + + Cancels any outstanding requests and clears errors. Model status will be set to either + GeocodeModel.Null or GeocodeModel.Ready. +*/ +void QDeclarativeGeocodeModel::cancel() +{ + abortRequest(); + setError(NoError, QString()); + setStatus(declarativeLocations_.isEmpty() ? Null : Ready); +} + +/*! + \qmlproperty QVariant QtLocation::GeocodeModel::query + + This property holds the data of the geocoding request. + The property accepts three types of queries which determine both the data and + the type of action to be performed: + + \list + \li Address - Geocoding (address to coordinate) + \li \l {coordinate} - Reverse geocoding (coordinate to address) + \li string - Geocoding (address to coordinate) + \endlist +*/ + +QVariant QDeclarativeGeocodeModel::query() const +{ + return queryVariant_; +} + +void QDeclarativeGeocodeModel::setQuery(const QVariant &query) +{ + if (query == queryVariant_) + return; + + if (query.userType() == qMetaTypeId()) { + if (address_) { + address_->disconnect(this); + address_ = 0; + } + searchString_.clear(); + + coordinate_ = query.value(); + } else if (query.type() == QVariant::String) { + searchString_ = query.toString(); + if (address_) { + address_->disconnect(this); + address_ = 0; + } + coordinate_ = QGeoCoordinate(); + } else if (QObject *object = query.value()) { + if (QDeclarativeGeoAddress *address = qobject_cast(object)) { + if (address_) + address_->disconnect(this); + coordinate_ = QGeoCoordinate(); + searchString_.clear(); + + address_ = address; + connect(address_, SIGNAL(countryChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(countryCodeChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(stateChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(countyChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(cityChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(districtChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(streetChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(postalCodeChanged()), this, SLOT(queryContentChanged())); + } else { + qmlInfo(this) << QStringLiteral("Unsupported query type for geocode model ") + << QStringLiteral("(coordinate, string and Address supported)."); + return; + } + } else { + qmlInfo(this) << QStringLiteral("Unsupported query type for geocode model ") + << QStringLiteral("(coordinate, string and Address supported)."); + return; + } + + queryVariant_ = query; + emit queryChanged(); + if (autoUpdate_) + update(); +} + +/*! + \qmlproperty bool QtLocation::GeocodeModel::autoUpdate + + This property controls whether the Model automatically updates in response + to changes in its attached query. The default value of this property + is false. + + If setting this value to 'true' and using an Address or + \l {coordinate} as the query, note that any change at all in the + object's properties will trigger a new request to be sent. If you are adjusting many + properties of the object whilst autoUpdate is enabled, this can generate large numbers of + useless (and later discarded) requests. +*/ + +bool QDeclarativeGeocodeModel::autoUpdate() const +{ + return autoUpdate_; +} + +void QDeclarativeGeocodeModel::setAutoUpdate(bool update) +{ + if (autoUpdate_ == update) + return; + autoUpdate_ = update; + emit autoUpdateChanged(); +} + +#include "moc_qdeclarativegeocodemodel_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeocodemodel_p.h b/src/imports/location/qdeclarativegeocodemodel_p.h new file mode 100644 index 0000000..2fbe993 --- /dev/null +++ b/src/imports/location/qdeclarativegeocodemodel_p.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOCODEMODEL_H +#define QDECLARATIVEGEOCODEMODEL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeoserviceprovider_p.h" + +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoServiceProvider; +class QGeoCodingManager; +class QDeclarativeGeoLocation; + +class QDeclarativeGeocodeModel : public QAbstractListModel, public QQmlParserStatus +{ + Q_OBJECT + Q_ENUMS(Status) + Q_ENUMS(GeocodeError) + + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(bool autoUpdate READ autoUpdate WRITE setAutoUpdate NOTIFY autoUpdateChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged) + Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged) + Q_PROPERTY(QVariant query READ query WRITE setQuery NOTIFY queryChanged) + Q_PROPERTY(QVariant bounds READ bounds WRITE setBounds NOTIFY boundsChanged) + Q_PROPERTY(GeocodeError error READ error NOTIFY errorChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + enum Status { + Null, + Ready, + Loading, + Error + }; + + enum GeocodeError { + NoError = QGeoCodeReply::NoError, + EngineNotSetError = QGeoCodeReply::EngineNotSetError, //TODO Qt6 consider merge with NotSupportedError + CommunicationError = QGeoCodeReply::CommunicationError, //TODO Qt6 merge with Map's ConnectionError + ParseError = QGeoCodeReply::ParseError, + UnsupportedOptionError = QGeoCodeReply::UnsupportedOptionError, //TODO Qt6 consider rename UnsupportedOperationError + CombinationError = QGeoCodeReply::CombinationError, + UnknownError = QGeoCodeReply::UnknownError, + //we leave gap for future QGeoCodeReply errors + + //QGeoServiceProvider related errors start here + UnknownParameterError = 100, + MissingRequiredParameterError + }; + + enum Roles { + LocationRole = Qt::UserRole + 1 + }; + + explicit QDeclarativeGeocodeModel(QObject *parent = 0); + virtual ~QDeclarativeGeocodeModel(); + + // From QQmlParserStatus + virtual void classBegin() {} + virtual void componentComplete(); + + // From QAbstractListModel + virtual int rowCount(const QModelIndex &parent) const; + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QHash roleNames() const; + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + + void setBounds(const QVariant &boundingArea); + QVariant bounds() const; + + Status status() const; + QString errorString() const; + GeocodeError error() const; + + bool autoUpdate() const; + void setAutoUpdate(bool update); + + int count() const; + Q_INVOKABLE QDeclarativeGeoLocation *get(int index); + + int limit() const; + void setLimit(int limit); + int offset() const; + void setOffset(int offset); + + QVariant query() const; + void setQuery(const QVariant &query); + Q_INVOKABLE void reset(); + Q_INVOKABLE void cancel(); + +Q_SIGNALS: + void countChanged(); + void pluginChanged(); + void statusChanged(); + void errorChanged(); //emitted also for errorString notification + void locationsChanged(); + void autoUpdateChanged(); + void boundsChanged(); + void queryChanged(); + void limitChanged(); + void offsetChanged(); + +public Q_SLOTS: + void update(); + +protected Q_SLOTS: + void queryContentChanged(); + void geocodeFinished(QGeoCodeReply *reply); + void geocodeError(QGeoCodeReply *reply, + QGeoCodeReply::Error error, + const QString &errorString); + void pluginReady(); + +protected: + QGeoCodingManager *searchManager(); + void setStatus(Status status); + void setError(GeocodeError error, const QString &errorString); + bool autoUpdate_; + bool complete_; + +private: + void setLocations(const QList &locations); + void abortRequest(); + QGeoCodeReply *reply_; + + QDeclarativeGeoServiceProvider *plugin_; + QGeoShape boundingArea_; + + QList declarativeLocations_; + + Status status_; + QString errorString_; + GeocodeError error_; + QVariant queryVariant_; + QGeoCoordinate coordinate_; + QDeclarativeGeoAddress *address_; + QString searchString_; + + int limit_; + int offset_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/qdeclarativegeomaneuver.cpp b/src/imports/location/qdeclarativegeomaneuver.cpp new file mode 100644 index 0000000..72c3886 --- /dev/null +++ b/src/imports/location/qdeclarativegeomaneuver.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomaneuver_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RouteManeuver + \instantiates QDeclarativeGeoManeuver + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The RouteManeuver type represents the information relevant to the + point at which two RouteSegments meet. + + RouteSegment instances can be thought of as edges on a routing + graph, with RouteManeuver instances as optional labels attached to the + vertices of the graph. + + The most interesting information held in a RouteManeuver instance is + normally the textual navigation to provide and the position at which to + provide it, accessible by \l instructionText and \l position respectively. + + \section1 Example + + The following QML snippet demonstrates how to print information about a + route maneuver: + + \snippet declarative/routing.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/routing.qml RouteManeuver +*/ + +QDeclarativeGeoManeuver::QDeclarativeGeoManeuver(QObject *parent) + : QObject(parent) +{ +} + +QDeclarativeGeoManeuver::QDeclarativeGeoManeuver(const QGeoManeuver &maneuver, QObject *parent) + : QObject(parent), + maneuver_(maneuver) +{ +} + +QDeclarativeGeoManeuver::~QDeclarativeGeoManeuver() {} + +/*! + \qmlproperty bool RouteManeuver::valid + + This read-only property holds whether this maneuver is valid or not. + + Invalid maneuvers are used when there is no information + that needs to be attached to the endpoint of a QGeoRouteSegment instance. +*/ + +bool QDeclarativeGeoManeuver::valid() const +{ + return maneuver_.isValid(); +} + +/*! + \qmlproperty coordinate RouteManeuver::position + + This read-only property holds where the \l instructionText should be displayed. + +*/ + +QGeoCoordinate QDeclarativeGeoManeuver::position() const +{ + return maneuver_.position(); +} + +/*! + \qmlproperty string RouteManeuver::instructionText + + This read-only property holds textual navigation instruction. +*/ + +QString QDeclarativeGeoManeuver::instructionText() const +{ + return maneuver_.instructionText(); +} + +/*! + \qmlproperty enumeration RouteManeuver::direction + + Describes the change in direction associated with the instruction text + that is associated with a RouteManeuver. + + \list + \li RouteManeuver.NoDirection - There is no direction associated with the instruction text + \li RouteManeuver.DirectionForward - The instruction indicates that the direction of travel does not need to change + \li RouteManeuver.DirectionBearRight - The instruction indicates that the direction of travel should bear to the right + \li RouteManeuver.DirectionLightRight - The instruction indicates that a light turn to the right is required + \li RouteManeuver.DirectionRight - The instruction indicates that a turn to the right is required + \li RouteManeuver.DirectionHardRight - The instruction indicates that a hard turn to the right is required + \li RouteManeuver.DirectionUTurnRight - The instruction indicates that a u-turn to the right is required + \li RouteManeuver.DirectionUTurnLeft - The instruction indicates that a u-turn to the left is required + \li RouteManeuver.DirectionHardLeft - The instruction indicates that a hard turn to the left is required + \li RouteManeuver.DirectionLeft - The instruction indicates that a turn to the left is required + \li RouteManeuver.DirectionLightLeft - The instruction indicates that a light turn to the left is required + \li RouteManeuver.DirectionBearLeft - The instruction indicates that the direction of travel should bear to the left + \endlist +*/ + +QDeclarativeGeoManeuver::Direction QDeclarativeGeoManeuver::direction() const +{ + return QDeclarativeGeoManeuver::Direction(maneuver_.direction()); +} + +/*! + \qmlproperty int RouteManeuver::timeToNextInstruction + + This read-only property holds the estimated time it will take to travel + from the point at which the associated instruction was issued and the + point that the next instruction should be issued, in seconds. +*/ + +int QDeclarativeGeoManeuver::timeToNextInstruction() const +{ + return maneuver_.timeToNextInstruction(); +} + +/*! + \qmlproperty real RouteManeuver::distanceToNextInstruction + + This read-only property holds the distance, in meters, between the point at which + the associated instruction was issued and the point that the next instruction should + be issued. +*/ + +qreal QDeclarativeGeoManeuver::distanceToNextInstruction() const +{ + return maneuver_.distanceToNextInstruction(); +} + +/*! + \qmlproperty coordinate RouteManeuver::waypoint + + This property holds the waypoint associated with this maneuver. + All maneuvers do not have a waypoint associated with them, this + can be checked with \l waypointValid. + +*/ + +QGeoCoordinate QDeclarativeGeoManeuver::waypoint() const +{ + return maneuver_.waypoint(); +} + +/*! + \qmlproperty bool RouteManeuver::waypointValid + + This read-only property holds whether this \l waypoint, associated with this + maneuver, is valid or not. +*/ + +bool QDeclarativeGeoManeuver::waypointValid() const +{ + return maneuver_.waypoint().isValid(); +} + +#include "moc_qdeclarativegeomaneuver_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeomaneuver_p.h b/src/imports/location/qdeclarativegeomaneuver_p.h new file mode 100644 index 0000000..b189d83 --- /dev/null +++ b/src/imports/location/qdeclarativegeomaneuver_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMANEUVER_H +#define QDECLARATIVEGEOMANEUVER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoManeuver : public QObject +{ + Q_OBJECT + Q_ENUMS(Direction) + + Q_PROPERTY(bool valid READ valid CONSTANT) + Q_PROPERTY(QGeoCoordinate position READ position CONSTANT) + Q_PROPERTY(QString instructionText READ instructionText CONSTANT) + Q_PROPERTY(Direction direction READ direction CONSTANT) + Q_PROPERTY(int timeToNextInstruction READ timeToNextInstruction CONSTANT) + Q_PROPERTY(qreal distanceToNextInstruction READ distanceToNextInstruction CONSTANT) + Q_PROPERTY(QGeoCoordinate waypoint READ waypoint CONSTANT) + Q_PROPERTY(bool waypointValid READ waypointValid CONSTANT) + +public: + enum Direction { + NoDirection = QGeoManeuver::NoDirection, + DirectionForward = QGeoManeuver::DirectionForward, + DirectionBearRight = QGeoManeuver::DirectionBearRight, + DirectionLightRight = QGeoManeuver::DirectionLightRight, + DirectionRight = QGeoManeuver::DirectionRight, + DirectionHardRight = QGeoManeuver::DirectionHardRight, + DirectionUTurnRight = QGeoManeuver::DirectionUTurnRight, + DirectionUTurnLeft = QGeoManeuver::DirectionUTurnLeft, + DirectionHardLeft = QGeoManeuver::DirectionHardLeft, + DirectionLeft = QGeoManeuver::DirectionLeft, + DirectionLightLeft = QGeoManeuver::DirectionLightLeft, + DirectionBearLeft = QGeoManeuver::DirectionBearLeft + }; + + explicit QDeclarativeGeoManeuver(QObject *parent = 0); + QDeclarativeGeoManeuver(const QGeoManeuver &maneuver, QObject *parent = 0); + ~QDeclarativeGeoManeuver(); + + bool valid() const; + bool waypointValid() const; + + QGeoCoordinate position() const; + QString instructionText() const; + Direction direction() const; + int timeToNextInstruction() const; + qreal distanceToNextInstruction() const; + QGeoCoordinate waypoint() const; + +private: + QGeoManeuver maneuver_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/qdeclarativegeomap.cpp b/src/imports/location/qdeclarativegeomap.cpp new file mode 100644 index 0000000..fd98b2c --- /dev/null +++ b/src/imports/location/qdeclarativegeomap.cpp @@ -0,0 +1,1551 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomap_p.h" +#include "qdeclarativegeomapquickitem_p.h" +#include "qdeclarativegeomapcopyrightsnotice_p.h" +#include "qdeclarativegeoserviceprovider_p.h" +#include "qdeclarativegeomaptype_p.h" +#include "qgeomappingmanager_p.h" +#include "qgeocameracapabilities_p.h" +#include "qgeomap_p.h" +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Map + \instantiates QDeclarativeGeoMap + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 + + \brief The Map type displays a map. + + The Map type is used to display a map or image of the Earth, with + the capability to also display interactive objects tied to the map's + surface. + + There are a variety of different ways to visualize the Earth's surface + in a 2-dimensional manner, but all of them involve some kind of projection: + a mathematical relationship between the 3D coordinates (latitude, longitude + and altitude) and 2D coordinates (X and Y in pixels) on the screen. + + Different sources of map data can use different projections, and from the + point of view of the Map type, we treat these as one replaceable unit: + the Map plugin. A Map plugin consists of a data source, as well as all other + details needed to display its data on-screen. + + The current Map plugin in use is contained in the \l plugin property of + the Map item. In order to display any image in a Map item, you will need + to set this property. See the \l Plugin type for a description of how + to retrieve an appropriate plugin for use. + + The geographic region displayed in the Map item is referred to as its + viewport, and this is defined by the properties \l center, and + \l zoomLevel. The \l center property contains a \l {coordinate} + specifying the center of the viewport, while \l zoomLevel controls the scale of the + map. See each of these properties for further details about their values. + + When the map is displayed, each possible geographic coordinate that is + visible will map to some pixel X and Y coordinate on the screen. To perform + conversions between these two, Map provides the \l toCoordinate and + \l fromCoordinate functions, which are of general utility. + + \section2 Map Objects + + Map related objects can be declared within the body of a Map object in Qt Quick and will + automatically appear on the Map. To add objects programmatically, first be + sure they are created with the Map as their parent (for example in an argument to + Component::createObject), and then call the \l addMapItem method on the Map. + A corresponding \l removeMapItem method also exists to do the opposite and + remove an object from the Map. + + Moving Map objects around, resizing them or changing their shape normally + does not involve any special interaction with Map itself -- changing these + details about a map object will automatically update the display. + + \section2 Interaction + + The Map type includes support for pinch and flick gestures to control + zooming and panning. These are enabled by default, and available at any + time by using the \l gesture object. The actual GestureArea is constructed + specially at startup and cannot be replaced or destroyed. Its properties + can be altered, however, to control its behavior. + + \section2 Performance + + Maps are rendered using OpenGL (ES) and the Qt Scene Graph stack, and as + a result perform quite well where GL accelerated hardware is available. + + For "online" Map plugins, network bandwidth and latency can be major + contributors to the user's perception of performance. Extensive caching is + performed to mitigate this, but such mitigation is not always perfect. For + "offline" plugins, the time spent retrieving the stored geographic data + and rendering the basic map features can often play a dominant role. Some + offline plugins may use hardware acceleration themselves to (partially) + avert this. + + In general, large and complex Map items such as polygons and polylines with + large numbers of vertices can have an adverse effect on UI performance. + Further, more detailed notes on this are in the documentation for each + map item type. + + \section2 Example Usage + + The following snippet shows a simple Map and the necessary Plugin type + to use it. The map is centered over Oslo, Norway, with zoom level 10. + + \quotefromfile minimal_map/main.qml + \skipto import + \printuntil } + \printline } + \skipto Map + \printuntil } + \printline } + + \image minimal_map.png +*/ + +/*! + \qmlsignal QtLocation::Map::copyrightLinkActivated(string link) + + This signal is emitted when the user clicks on a \a link in the copyright notice. The + application should open the link in a browser or display its contents to the user. +*/ + +static const qreal EARTH_MEAN_RADIUS = 6371007.2; + +QDeclarativeGeoMap::QDeclarativeGeoMap(QQuickItem *parent) + : QQuickItem(parent), + m_plugin(0), + m_serviceProvider(0), + m_mappingManager(0), + m_activeMapType(0), + m_gestureArea(new QQuickGeoMapGestureArea(this)), + m_map(0), + m_error(QGeoServiceProvider::NoError), + m_color(QColor::fromRgbF(0.9, 0.9, 0.9)), + m_componentCompleted(false), + m_pendingFitViewport(false), + m_copyrightsVisible(true), + m_maximumViewportLatitude(0.0), + m_initialized(false), + m_validRegion(false) +{ + setAcceptHoverEvents(false); + setAcceptedMouseButtons(Qt::LeftButton); + setFlags(QQuickItem::ItemHasContents | QQuickItem::ItemClipsChildrenToShape); + setFiltersChildMouseEvents(true); + + connect(this, SIGNAL(childrenChanged()), this, SLOT(onMapChildrenChanged()), Qt::QueuedConnection); + + m_activeMapType = new QDeclarativeGeoMapType(QGeoMapType(QGeoMapType::NoMap, + tr("No Map"), + tr("No Map"), false, false, 0), this); + m_cameraData.setCenter(QGeoCoordinate(51.5073,-0.1277)); //London city center + m_cameraData.setZoomLevel(8.0); +} + +QDeclarativeGeoMap::~QDeclarativeGeoMap() +{ + if (!m_mapViews.isEmpty()) + qDeleteAll(m_mapViews); + // remove any map items associations + for (int i = 0; i < m_mapItems.count(); ++i) { + if (m_mapItems.at(i)) + m_mapItems.at(i).data()->setMap(0,0); + } + m_mapItems.clear(); + + delete m_copyrights.data(); + m_copyrights.clear(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::onMapChildrenChanged() +{ + if (!m_componentCompleted || !m_map) + return; + + int maxChildZ = 0; + QObjectList kids = children(); + bool foundCopyrights = false; + + for (int i = 0; i < kids.size(); ++i) { + QDeclarativeGeoMapCopyrightNotice *copyrights = qobject_cast(kids.at(i)); + if (copyrights) { + foundCopyrights = true; + } else { + QDeclarativeGeoMapItemBase *mapItem = qobject_cast(kids.at(i)); + if (mapItem) { + if (mapItem->z() > maxChildZ) + maxChildZ = mapItem->z(); + } + } + } + + QDeclarativeGeoMapCopyrightNotice *copyrights = m_copyrights.data(); + // if copyrights object not found within the map's children + if (!foundCopyrights) { + // if copyrights object was deleted! + if (!copyrights) { + // create a new one and set its parent, re-assign it to the weak pointer, then connect the copyrights-change signal + m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this); + copyrights = m_copyrights.data(); + connect(m_map, SIGNAL(copyrightsChanged(QImage)), + copyrights, SLOT(copyrightsChanged(QImage))); + connect(m_map, SIGNAL(copyrightsChanged(QString)), + copyrights, SLOT(copyrightsChanged(QString))); + connect(copyrights, SIGNAL(linkActivated(QString)), + this, SIGNAL(copyrightLinkActivated(QString))); + + // set visibility of copyright notice + copyrights->setCopyrightsVisible(m_copyrightsVisible); + + } else { + // just re-set its parent. + copyrights->setParent(this); + } + } + + // put the copyrights notice object at the highest z order + copyrights->setCopyrightsZ(maxChildZ + 1); +} + +static QDeclarativeGeoMapType *findMapType(const QList &types, const QGeoMapType &type) +{ + for (int i = 0; i < types.size(); ++i) + if (types[i]->mapType() == type) + return types[i]; + return Q_NULLPTR; +} + +void QDeclarativeGeoMap::onSupportedMapTypesChanged() +{ + QList supportedMapTypes; + QList types = m_mappingManager->supportedMapTypes(); + for (int i = 0; i < types.size(); ++i) { + // types that are present and get removed will be deleted at QObject destruction + QDeclarativeGeoMapType *type = findMapType(m_supportedMapTypes, types[i]); + if (!type) + type = new QDeclarativeGeoMapType(types[i], this); + supportedMapTypes.append(type); + } + m_supportedMapTypes.swap(supportedMapTypes); + if (m_supportedMapTypes.isEmpty()) { + m_map->setActiveMapType(QGeoMapType()); // no supported map types: setting an invalid one + } else { + bool hasMapType = false; + foreach (QDeclarativeGeoMapType *declarativeType, m_supportedMapTypes) { + if (declarativeType->mapType() == m_map->activeMapType()) + hasMapType = true; + } + if (!hasMapType) { + QDeclarativeGeoMapType *type = m_supportedMapTypes.at(0); + m_activeMapType = type; + m_map->setActiveMapType(type->mapType()); + } + } + + emit supportedMapTypesChanged(); +} + +void QDeclarativeGeoMap::setError(QGeoServiceProvider::Error error, const QString &errorString) +{ + if (m_error == error && m_errorString == errorString) + return; + m_error = error; + m_errorString = errorString; + emit errorChanged(); +} + +void QDeclarativeGeoMap::initialize() +{ + // try to keep center change signal in the end + bool centerHasChanged = false; + + setMinimumZoomLevel(m_map->minimumZoomForMapSize(width(), height())); + + // set latitude bundary check + m_maximumViewportLatitude = m_map->maximumLatitudeForZoom(m_cameraData.zoomLevel()); + QGeoCoordinate center = m_cameraData.center(); + center.setLatitude(qBound(-m_maximumViewportLatitude, center.latitude(), m_maximumViewportLatitude)); + + if (center != m_cameraData.center()) { + centerHasChanged = true; + m_cameraData.setCenter(center); + } + + m_map->setCameraData(m_cameraData); + + m_initialized = true; + + if (centerHasChanged) + emit centerChanged(m_cameraData.center()); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::pluginReady() +{ + m_serviceProvider = m_plugin->sharedGeoServiceProvider(); + m_mappingManager = m_serviceProvider->mappingManager(); + + if (m_serviceProvider->error() != QGeoServiceProvider::NoError) { + setError(m_serviceProvider->error(), m_serviceProvider->errorString()); + return; + } + + if (!m_mappingManager) { + //TODO Should really be EngineNotSetError (see QML GeoCodeModel) + setError(QGeoServiceProvider::NotSupportedError, tr("Plugin does not support mapping.")); + return; + } + + if (!m_mappingManager->isInitialized()) + connect(m_mappingManager, SIGNAL(initialized()), this, SLOT(mappingManagerInitialized())); + else + mappingManagerInitialized(); + + // make sure this is only called once + disconnect(this, SLOT(pluginReady())); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::componentComplete() +{ + m_componentCompleted = true; + populateMap(); + QQuickItem::componentComplete(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mousePressEvent(QMouseEvent *event) +{ + if (isInteractive()) + m_gestureArea->handleMousePressEvent(event); + else + QQuickItem::mousePressEvent(event); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mouseMoveEvent(QMouseEvent *event) +{ + if (isInteractive()) + m_gestureArea->handleMouseMoveEvent(event); + else + QQuickItem::mouseMoveEvent(event); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mouseReleaseEvent(QMouseEvent *event) +{ + if (isInteractive()) { + m_gestureArea->handleMouseReleaseEvent(event); + ungrabMouse(); + } else { + QQuickItem::mouseReleaseEvent(event); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mouseUngrabEvent() +{ + if (isInteractive()) + m_gestureArea->handleMouseUngrabEvent(); + else + QQuickItem::mouseUngrabEvent(); +} + +void QDeclarativeGeoMap::touchUngrabEvent() +{ + if (isInteractive()) + m_gestureArea->handleTouchUngrabEvent(); + else + QQuickItem::touchUngrabEvent(); +} + +/*! + \qmlproperty MapGestureArea QtLocation::Map::gesture + + Contains the MapGestureArea created with the Map. This covers pan, flick and pinch gestures. + Use \c{gesture.enabled: true} to enable basic gestures, or see \l{MapGestureArea} for + further details. +*/ + +QQuickGeoMapGestureArea *QDeclarativeGeoMap::gesture() +{ + return m_gestureArea; +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::populateMap() +{ + QObjectList kids = children(); + QList quickKids = childItems(); + for (int i=0; i < quickKids.count(); ++i) + kids.append(quickKids.at(i)); + + for (int i = 0; i < kids.size(); ++i) { + // dispatch items appropriately + QDeclarativeGeoMapItemView *mapView = qobject_cast(kids.at(i)); + if (mapView) { + m_mapViews.append(mapView); + setupMapView(mapView); + continue; + } + QDeclarativeGeoMapItemBase *mapItem = qobject_cast(kids.at(i)); + if (mapItem) { + addMapItem(mapItem); + } + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::setupMapView(QDeclarativeGeoMapItemView *view) +{ + Q_UNUSED(view) + view->setMap(this); + view->repopulate(); +} + +/*! + * \internal + */ +QSGNode *QDeclarativeGeoMap::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + if (!m_map) { + delete oldNode; + return 0; + } + + QSGSimpleRectNode *root = static_cast(oldNode); + if (!root) + root = new QSGSimpleRectNode(boundingRect(), m_color); + else { + root->setRect(boundingRect()); + root->setColor(m_color); + } + + QSGNode *content = root->childCount() ? root->firstChild() : 0; + content = m_map->updateSceneGraph(content, window()); + if (content && root->childCount() == 0) + root->appendChildNode(content); + + return root; +} + +/*! + \qmlproperty Plugin QtLocation::Map::plugin + + This property holds the plugin which provides the mapping functionality. + + This is a write-once property. Once the map has a plugin associated with + it, any attempted modifications of the plugin will be ignored. +*/ + +void QDeclarativeGeoMap::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (m_plugin) { + qmlInfo(this) << QStringLiteral("Plugin is a write-once property, and cannot be set again."); + return; + } + m_plugin = plugin; + emit pluginChanged(m_plugin); + + if (m_plugin->isAttached()) { + pluginReady(); + } else { + connect(m_plugin, SIGNAL(attached()), + this, SLOT(pluginReady())); + } +} + +/*! + \internal + this function will only be ever called once +*/ +void QDeclarativeGeoMap::mappingManagerInitialized() +{ + m_map = m_mappingManager->createMap(this); + + if (!m_map) + return; + + m_gestureArea->setMap(m_map); + + QList types = m_mappingManager->supportedMapTypes(); + for (int i = 0; i < types.size(); ++i) { + QDeclarativeGeoMapType *type = new QDeclarativeGeoMapType(types[i], this); + m_supportedMapTypes.append(type); + } + + if (!m_supportedMapTypes.isEmpty()) { + QDeclarativeGeoMapType *type = m_supportedMapTypes.at(0); + m_activeMapType = type; + m_map->setActiveMapType(type->mapType()); + } else { + m_map->setActiveMapType(m_activeMapType->mapType()); + } + + //The zoom level limits are only restricted by the plugins values, if the user has set a more + //strict zoom level limit before initialization nothing is done here. + //minimum zoom level might be changed to limit gray bundaries + + if (m_gestureArea->maximumZoomLevel() < 0 + || m_mappingManager->cameraCapabilities().maximumZoomLevel() < m_gestureArea->maximumZoomLevel()) + setMaximumZoomLevel(m_mappingManager->cameraCapabilities().maximumZoomLevel()); + + if (m_mappingManager->cameraCapabilities().minimumZoomLevel() > m_gestureArea->minimumZoomLevel()) + setMinimumZoomLevel(m_mappingManager->cameraCapabilities().minimumZoomLevel()); + + + // Map tiles are built in this call. m_map->minimumZoom() becomes operational + // after this has been called at least once, after creation. + + if (!m_initialized && width() > 0 && height() > 0) { + m_map->setSize(QSize(width(), height())); + initialize(); + } + + m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this); + connect(m_map, SIGNAL(copyrightsChanged(QImage)), + m_copyrights.data(), SLOT(copyrightsChanged(QImage))); + connect(m_map, SIGNAL(copyrightsChanged(QString)), + m_copyrights.data(), SLOT(copyrightsChanged(QString))); + connect(m_copyrights.data(), SIGNAL(linkActivated(QString)), + this, SIGNAL(copyrightLinkActivated(QString))); + connect(m_map, &QGeoMap::sgNodeChanged, this, &QQuickItem::update); + + // set visibility of copyright notice + m_copyrights->setCopyrightsVisible(m_copyrightsVisible); + + // This prefetches a buffer around the map + m_map->prefetchData(); + + connect(m_mappingManager, SIGNAL(supportedMapTypesChanged()), this, SLOT(onSupportedMapTypesChanged())); + emit minimumZoomLevelChanged(); + emit maximumZoomLevelChanged(); + emit supportedMapTypesChanged(); + emit activeMapTypeChanged(); + + // Any map items that were added before the plugin was ready + // need to have setMap called again + foreach (const QPointer &item, m_mapItems) { + if (item) + item.data()->setMap(this, m_map); + } +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider *QDeclarativeGeoMap::plugin() const +{ + return m_plugin; +} + +/*! + \internal + Sets the gesture areas minimum zoom level. If the camera capabilities + has been set this method honors the boundaries set by it. + The minimum zoom level will also have a lower bound dependent on the size + of the canvas, effectively preventing to display out of bounds areas. +*/ +void QDeclarativeGeoMap::setMinimumZoomLevel(qreal minimumZoomLevel) +{ + + if (minimumZoomLevel >= 0) { + qreal oldMinimumZoomLevel = this->minimumZoomLevel(); + + if (m_map) { + minimumZoomLevel = qBound(qreal(m_map->cameraCapabilities().minimumZoomLevel()), minimumZoomLevel, maximumZoomLevel()); + double minimumViewportZoomLevel = m_map->minimumZoomForMapSize(width(),height()); + if (minimumZoomLevel < minimumViewportZoomLevel) + minimumZoomLevel = minimumViewportZoomLevel; + } + + m_gestureArea->setMinimumZoomLevel(minimumZoomLevel); + + if (zoomLevel() < minimumZoomLevel) + setZoomLevel(minimumZoomLevel); + + if (oldMinimumZoomLevel != minimumZoomLevel) + emit minimumZoomLevelChanged(); + } +} + +/*! + \qmlproperty real QtLocation::Map::minimumZoomLevel + + This property holds the minimum valid zoom level for the map. + + The minimum zoom level defined by the \l plugin used is a lower bound for + this property. However, the returned value is also canvas-size-dependent, and + can be higher than the user-specified value, or than the minimum zoom level + defined by the plugin used, to prevent the map from being smaller than the + viewport in either dimension. + + If a plugin supporting mapping is not set, -1.0 is returned. +*/ + +qreal QDeclarativeGeoMap::minimumZoomLevel() const +{ + if (m_gestureArea->minimumZoomLevel() != -1) + return m_gestureArea->minimumZoomLevel(); + else if (m_map) + return m_map->cameraCapabilities().minimumZoomLevel(); + else + return -1.0; +} + +/*! + \internal + Sets the gesture areas maximum zoom level. If the camera capabilities + has been set this method honors the boundaries set by it. +*/ +void QDeclarativeGeoMap::setMaximumZoomLevel(qreal maximumZoomLevel) +{ + if (maximumZoomLevel >= 0) { + qreal oldMaximumZoomLevel = this->maximumZoomLevel(); + + if (m_map) + maximumZoomLevel = qBound(minimumZoomLevel(), maximumZoomLevel, qreal(m_map->cameraCapabilities().maximumZoomLevel())); + + m_gestureArea->setMaximumZoomLevel(maximumZoomLevel); + + if (zoomLevel() > maximumZoomLevel) + setZoomLevel(maximumZoomLevel); + + if (oldMaximumZoomLevel != maximumZoomLevel) + emit maximumZoomLevelChanged(); + } +} + +/*! + \qmlproperty real QtLocation::Map::maximumZoomLevel + + This property holds the maximum valid zoom level for the map. + + The maximum zoom level is defined by the \l plugin used. + If a plugin supporting mapping is not set, -1.0 is returned. +*/ + +qreal QDeclarativeGeoMap::maximumZoomLevel() const +{ + if (m_gestureArea->maximumZoomLevel() != -1) + return m_gestureArea->maximumZoomLevel(); + else if (m_map) + return m_map->cameraCapabilities().maximumZoomLevel(); + else + return -1.0; +} + +/*! + \qmlproperty real QtLocation::Map::zoomLevel + + This property holds the zoom level for the map. + + Larger values for the zoom level provide more detail. Zoom levels + are always non-negative. The default value is 8.0. +*/ +void QDeclarativeGeoMap::setZoomLevel(qreal zoomLevel) +{ + if (m_cameraData.zoomLevel() == zoomLevel || zoomLevel < 0) + return; + + //small optiomatization to avoid double setCameraData + bool centerHasChanged = false; + + if (m_initialized) { + m_cameraData.setZoomLevel(qBound(minimumZoomLevel(), zoomLevel, maximumZoomLevel())); + m_maximumViewportLatitude = m_map->maximumLatitudeForZoom(m_cameraData.zoomLevel()); + QGeoCoordinate coord = m_cameraData.center(); + coord.setLatitude(qBound(-m_maximumViewportLatitude, coord.latitude(), m_maximumViewportLatitude)); + if (coord != m_cameraData.center()) { + centerHasChanged = true; + m_cameraData.setCenter(coord); + } + m_map->setCameraData(m_cameraData); + } else { + m_cameraData.setZoomLevel(zoomLevel); + } + + m_validRegion = false; + + if (centerHasChanged) + emit centerChanged(m_cameraData.center()); + emit zoomLevelChanged(m_cameraData.zoomLevel()); +} + +qreal QDeclarativeGeoMap::zoomLevel() const +{ + return m_cameraData.zoomLevel(); +} + +/*! + \qmlproperty coordinate QtLocation::Map::center + + This property holds the coordinate which occupies the center of the + mapping viewport. Invalid center coordinates are ignored. + + The default value is an arbitrary valid coordinate. +*/ +void QDeclarativeGeoMap::setCenter(const QGeoCoordinate ¢er) +{ + if (center == m_cameraData.center()) + return; + + if (!center.isValid()) + return; + + if (m_initialized) { + QGeoCoordinate coord(center); + coord.setLatitude(qBound(-m_maximumViewportLatitude, center.latitude(), m_maximumViewportLatitude)); + m_cameraData.setCenter(coord); + m_map->setCameraData(m_cameraData); + } else { + m_cameraData.setCenter(center); + } + + m_validRegion = false; + emit centerChanged(m_cameraData.center()); +} + +QGeoCoordinate QDeclarativeGeoMap::center() const +{ + return m_cameraData.center(); +} + + +/*! + \qmlproperty geoshape QtLocation::Map::visibleRegion + + This property holds the region which occupies the viewport of + the map. The camera is positioned in the center of the shape, and + at the largest integral zoom level possible which allows the + whole shape to be visible on the screen. This implies that + reading this property back shortly after having been set the + returned area is equal or larger than the set area. + + Setting this property implicitly changes the \l center and + \l zoomLevel of the map. Any previously set value to those + properties will be overridden. + + This property does not provide any change notifications. + + \since 5.6 +*/ +void QDeclarativeGeoMap::setVisibleRegion(const QGeoShape &shape) +{ + if (shape == m_region && m_validRegion) + return; + + m_region = shape; + if (!shape.isValid()) { + // shape invalidated -> nothing to fit anymore + m_pendingFitViewport = false; + return; + } + + if (!width() || !height()) { + m_pendingFitViewport = true; + return; + } + + fitViewportToGeoShape(); +} + +QGeoShape QDeclarativeGeoMap::visibleRegion() const +{ + if (!m_map || !width() || !height()) + return m_region; + + QGeoCoordinate tl = m_map->itemPositionToCoordinate(QDoubleVector2D(0, 0)); + QGeoCoordinate br = m_map->itemPositionToCoordinate(QDoubleVector2D(width(), height())); + + return QGeoRectangle(tl, br); +} + +/*! + \qmlproperty bool QtLocation::Map::copyrightsVisible + + This property holds the visibility of the copyrights notice. The notice is usually + displayed in the bottom left corner. By default, this property is set to \c true. + + \note Many map providers require the notice to be visible as part of the terms and conditions. + Please consult the relevant provider documentation before turning this notice off. + + \since 5.7 +*/ +void QDeclarativeGeoMap::setCopyrightsVisible(bool visible) +{ + if (m_copyrightsVisible == visible) + return; + + if (!m_copyrights.isNull()) + m_copyrights->setCopyrightsVisible(visible); + + m_copyrightsVisible = visible; + emit copyrightsVisibleChanged(visible); +} + +bool QDeclarativeGeoMap::copyrightsVisible() const +{ + return m_copyrightsVisible; +} + + + +/*! + \qmlproperty color QtLocation::Map::color + + This property holds the background color of the map element. + + \since 5.6 +*/ +void QDeclarativeGeoMap::setColor(const QColor &color) +{ + if (color != m_color) { + m_color = color; + update(); + emit colorChanged(m_color); + } +} + +QColor QDeclarativeGeoMap::color() const +{ + return m_color; +} + +void QDeclarativeGeoMap::fitViewportToGeoShape() +{ + int margins = 10; + if (!m_map || width() <= margins || height() <= margins) + return; + + QGeoCoordinate topLeft; + QGeoCoordinate bottomRight; + + switch (m_region.type()) { + case QGeoShape::RectangleType: + { + QGeoRectangle rect = m_region; + topLeft = rect.topLeft(); + bottomRight = rect.bottomRight(); + break; + } + case QGeoShape::CircleType: + { + const double pi = M_PI; + QGeoCircle circle = m_region; + QGeoCoordinate centerCoordinate = circle.center(); + + // calculate geo bounding box of the circle + // circle tangential points with meridians and the north pole create + // spherical triangle, we use spherical law of sines + // sin(lon_delta_in_rad)/sin(r_in_rad) = + // sin(alpha_in_rad)/sin(pi/2 - lat_in_rad), where: + // * lon_delta_in_rad - delta of longitudes of circle center + // and tangential points + // * r_in_rad - angular radius of the circle + // * lat_in_rad - latitude of circle center + // * alpha_in_rad - angle between meridian and radius to the circle => + // this is tangential point => sin(alpha) = 1 + // * lat_delta_in_rad - delta of latitudes of circle center and + // latitude of points where great circle (going through circle + // center) crosses circle and the pole + + double r_in_rad = circle.radius() / EARTH_MEAN_RADIUS; // angular r + double lat_delta_in_deg = r_in_rad * 180 / pi; + double lon_delta_in_deg = std::asin(std::sin(r_in_rad) / + std::cos(centerCoordinate.latitude() * pi / 180)) * 180 / pi; + + topLeft.setLatitude(centerCoordinate.latitude() + lat_delta_in_deg); + topLeft.setLongitude(centerCoordinate.longitude() - lon_delta_in_deg); + bottomRight.setLatitude(centerCoordinate.latitude() + - lat_delta_in_deg); + bottomRight.setLongitude(centerCoordinate.longitude() + + lon_delta_in_deg); + + // adjust if circle reaches poles => cross all meridians and + // fit into Mercator projection bounds + if (topLeft.latitude() > 90 || bottomRight.latitude() < -90) { + topLeft.setLatitude(qMin(topLeft.latitude(), 85.05113)); + topLeft.setLongitude(-180.0); + bottomRight.setLatitude(qMax(bottomRight.latitude(), + -85.05113)); + bottomRight.setLongitude(180.0); + } + break; + } + case QGeoShape::UnknownType: + //Fallthrough to default + default: + return; + } + + // adjust zoom, use reference world to keep things simple + // otherwise we would need to do the error prone longitudes + // wrapping + QDoubleVector2D topLeftPoint = + m_map->referenceCoordinateToItemPosition(topLeft); + QDoubleVector2D bottomRightPoint = + m_map->referenceCoordinateToItemPosition(bottomRight); + + double bboxWidth = bottomRightPoint.x() - topLeftPoint.x(); + double bboxHeight = bottomRightPoint.y() - topLeftPoint.y(); + + // find center of the bounding box + QGeoCoordinate centerCoordinate = + m_map->referenceItemPositionToCoordinate( + (topLeftPoint + bottomRightPoint)/2); + + // position camera to the center of bounding box + setCenter(centerCoordinate); + + // if the shape is empty we just change center position, not zoom + if (bboxHeight == 0 && bboxWidth == 0) + return; + + double zoomRatio = qMax(bboxWidth / (width() - margins), + bboxHeight / (height() - margins)); + // fixme: use log2 with c++11 + zoomRatio = std::log(zoomRatio) / std::log(2.0); + double newZoom = qMax(minimumZoomLevel(), zoomLevel() + - zoomRatio); + setZoomLevel(newZoom); + m_validRegion = true; +} + + +/*! + \qmlproperty list QtLocation::Map::supportedMapTypes + + This read-only property holds the set of \l{MapType}{map types} supported by this map. + + \sa activeMapType +*/ +QQmlListProperty QDeclarativeGeoMap::supportedMapTypes() +{ + return QQmlListProperty(this, m_supportedMapTypes); +} + +/*! + \qmlmethod coordinate QtLocation::Map::toCoordinate(QPointF position, bool clipToViewPort) + + Returns the coordinate which corresponds to the \a position relative to the map item. + + If \a cliptoViewPort is \c true, or not supplied then returns an invalid coordinate if + \a position is not within the current viewport. +*/ +QGeoCoordinate QDeclarativeGeoMap::toCoordinate(const QPointF &position, bool clipToViewPort) const +{ + if (m_map) + return m_map->itemPositionToCoordinate(QDoubleVector2D(position), clipToViewPort); + else + return QGeoCoordinate(); +} + +/*! + \qmlmethod point QtLocation::Map::fromCoordinate(coordinate coordinate, bool clipToViewPort) + + Returns the position relative to the map item which corresponds to the \a coordinate. + + If \a cliptoViewPort is \c true, or not supplied then returns an invalid QPointF if + \a coordinate is not within the current viewport. +*/ +QPointF QDeclarativeGeoMap::fromCoordinate(const QGeoCoordinate &coordinate, bool clipToViewPort) const +{ + if (m_map) + return m_map->coordinateToItemPosition(coordinate, clipToViewPort).toPointF(); + else + return QPointF(qQNaN(), qQNaN()); +} + +/*! + \qmlmethod void QtLocation::Map::pan(int dx, int dy) + + Starts panning the map by \a dx pixels along the x-axis and + by \a dy pixels along the y-axis. + + Positive values for \a dx move the map right, negative values left. + Positive values for \a dy move the map down, negative values up. + + During panning the \l center, and \l zoomLevel may change. +*/ +void QDeclarativeGeoMap::pan(int dx, int dy) +{ + if (!m_map) + return; + if (dx == 0 && dy == 0) + return; + QGeoCoordinate coord = m_map->itemPositionToCoordinate( + QDoubleVector2D(m_map->width() / 2 + dx, + m_map->height() / 2 + dy)); + setCenter(coord); +} + + +/*! + \qmlmethod void QtLocation::Map::prefetchData() + + Optional hint that allows the map to prefetch during this idle period +*/ +void QDeclarativeGeoMap::prefetchData() +{ + if (!m_map) + return; + m_map->prefetchData(); +} + +/*! + \qmlmethod void QtLocation::Map::clearData() + + Clears map data collected by the currently selected plugin. + \note This method will delete cached files. + \sa plugin +*/ +void QDeclarativeGeoMap::clearData() +{ + m_map->clearData(); +} + +/*! + \qmlproperty string QtLocation::Map::errorString + + This read-only property holds the textual presentation of the latest mapping provider error. + If no error has occurred, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. + + \sa QGeoServiceProvider::errorString() +*/ + +QString QDeclarativeGeoMap::errorString() const +{ + return m_errorString; +} + +/*! + \qmlproperty enumeration QtLocation::Map::error + + This read-only property holds the last occurred mapping service provider error. + + \list + \li Map.NoError - No error has occurred. + \li Map.NotSupportedError -The maps plugin property was not set or there is no mapping manager associated with the plugin. + \li Map.UnknownParameterError -The plugin did not recognize one of the parameters it was given. + \li Map.MissingRequiredParameterError - The plugin did not find one of the parameters it was expecting. + \li Map.ConnectionError - The plugin could not connect to its backend service or database. + \endlist + + \sa QGeoServiceProvider::Error +*/ + +QGeoServiceProvider::Error QDeclarativeGeoMap::error() const +{ + return m_error; +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::touchEvent(QTouchEvent *event) +{ + if (isInteractive()) { + m_gestureArea->handleTouchEvent(event); + if ( event->type() == QEvent::TouchEnd || + event->type() == QEvent::TouchCancel) { + ungrabTouchPoints(); + } + } else { + //ignore event so sythesized event is generated; + QQuickItem::touchEvent(event); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::wheelEvent(QWheelEvent *event) +{ + if (isInteractive()) + m_gestureArea->handleWheelEvent(event); + else + QQuickItem::wheelEvent(event); + +} + +bool QDeclarativeGeoMap::isInteractive() +{ + return (m_gestureArea->enabled() && m_gestureArea->acceptedGestures()) || m_gestureArea->isActive(); +} + +/*! + \internal +*/ +bool QDeclarativeGeoMap::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + Q_UNUSED(item) + if (!isVisible() || !isEnabled() || !isInteractive()) + return QQuickItem::childMouseEventFilter(item, event); + + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + return sendMouseEvent(static_cast(event)); + case QEvent::UngrabMouse: { + QQuickWindow *win = window(); + if (!win) break; + if (!win->mouseGrabberItem() || + (win->mouseGrabberItem() && + win->mouseGrabberItem() != this)) { + // child lost grab, we could even lost + // some events if grab already belongs for example + // in item in diffrent window , clear up states + mouseUngrabEvent(); + } + break; + } + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + case QEvent::TouchCancel: + if (static_cast(event)->touchPoints().count() >= 2) { + // 1 touch point = handle with MouseEvent (event is always synthesized) + // let the synthesized mouse event grab the mouse, + // note there is no mouse grabber at this point since + // touch event comes first (see Qt::AA_SynthesizeMouseForUnhandledTouchEvents) + return sendTouchEvent(static_cast(event)); + } + default: + break; + } + return QQuickItem::childMouseEventFilter(item, event); +} + +/*! + \qmlmethod void QtLocation::Map::addMapItem(MapItem item) + + Adds the given \a item to the Map (for example MapQuickItem, MapCircle). If the object + already is on the Map, it will not be added again. + + As an example, consider the case where you have a MapCircle representing your current position: + + \snippet declarative/maps.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/maps.qml Map addMapItem MapCircle at current position + + \note MapItemViews cannot be added with this method. + + \sa mapItems, removeMapItem, clearMapItems +*/ + +void QDeclarativeGeoMap::addMapItem(QDeclarativeGeoMapItemBase *item) +{ + if (!item || item->quickMap()) + return; + m_updateMutex.lock(); + item->setParentItem(this); + if (m_map) + item->setMap(this, m_map); + m_mapItems.append(item); + emit mapItemsChanged(); + m_updateMutex.unlock(); +} + +/*! + \qmlproperty list QtLocation::Map::mapItems + + Returns the list of all map items in no particular order. + These items include items that were declared statically as part of + the type declaration, as well as dynamical items (\l addMapItem, + \l MapItemView). + + \sa addMapItem, removeMapItem, clearMapItems +*/ + +QList QDeclarativeGeoMap::mapItems() +{ + QList ret; + foreach (const QPointer &ptr, m_mapItems) { + if (ptr) + ret << ptr.data(); + } + return ret; +} + +/*! + \qmlmethod void QtLocation::Map::removeMapItem(MapItem item) + + Removes the given \a item from the Map (for example MapQuickItem, MapCircle). If + the MapItem does not exist or was not previously added to the map, the + method does nothing. + + \sa mapItems, addMapItem, clearMapItems +*/ +void QDeclarativeGeoMap::removeMapItem(QDeclarativeGeoMapItemBase *ptr) +{ + if (!ptr || !m_map) + return; + QPointer item(ptr); + if (!m_mapItems.contains(item)) + return; + m_updateMutex.lock(); + item.data()->setParentItem(0); + item.data()->setMap(0, 0); + // these can be optimized for perf, as we already check the 'contains' above + m_mapItems.removeOne(item); + emit mapItemsChanged(); + m_updateMutex.unlock(); +} + +/*! + \qmlmethod void QtLocation::Map::clearMapItems() + + Removes all items from the map. + + \sa mapItems, addMapItem, removeMapItem +*/ +void QDeclarativeGeoMap::clearMapItems() +{ + if (m_mapItems.isEmpty()) + return; + m_updateMutex.lock(); + for (int i = 0; i < m_mapItems.count(); ++i) { + if (m_mapItems.at(i)) { + m_mapItems.at(i).data()->setParentItem(0); + m_mapItems.at(i).data()->setMap(0, 0); + } + } + m_mapItems.clear(); + emit mapItemsChanged(); + m_updateMutex.unlock(); +} + +/*! + \qmlproperty MapType QtLocation::Map::activeMapType + + \brief Access to the currently active \l{MapType}{map type}. + + This property can be set to change the active \l{MapType}{map type}. + See the \l{Map::supportedMapTypes}{supportedMapTypes} property for possible values. + + \sa MapType +*/ +void QDeclarativeGeoMap::setActiveMapType(QDeclarativeGeoMapType *mapType) +{ + if (m_activeMapType->mapType() != mapType->mapType()) { + m_activeMapType = mapType; + if (m_map) + m_map->setActiveMapType(mapType->mapType()); + emit activeMapTypeChanged(); + } +} + +QDeclarativeGeoMapType * QDeclarativeGeoMap::activeMapType() const +{ + return m_activeMapType; +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + m_gestureArea->setSize(newGeometry.size()); + QQuickItem::geometryChanged(newGeometry, oldGeometry); + + if (!m_map || !newGeometry.size().isValid()) + return; + + m_map->setSize(newGeometry.size().toSize()); + + if (!m_initialized) + initialize(); + else + setMinimumZoomLevel(m_map->minimumZoomForMapSize(newGeometry.width(), newGeometry.height())); + + /*! + The fitViewportTo*() functions depend on a valid map geometry. + If they were called prior to the first resize they cause + the zoomlevel to jump to 0 (showing the world). Therefore the + calls were queued up until now. + + Multiple fitViewportTo*() calls replace each other. + */ + if (m_pendingFitViewport && width() && height()) { + fitViewportToGeoShape(); + m_pendingFitViewport = false; + } + +} + +/*! + \qmlmethod void QtLocation::Map::fitViewportToMapItems() + + Fits the current viewport to the boundary of all map items. The camera is positioned + in the center of the map items, and at the largest integral zoom level possible which + allows all map items to be visible on screen + +*/ +void QDeclarativeGeoMap::fitViewportToMapItems() +{ + fitViewportToMapItemsRefine(true); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::fitViewportToMapItemsRefine(bool refine) +{ + if (!m_map) + return; + + if (m_mapItems.size() == 0) + return; + + double minX = 0; + double maxX = 0; + double minY = 0; + double maxY = 0; + double topLeftX = 0; + double topLeftY = 0; + double bottomRightX = 0; + double bottomRightY = 0; + bool haveQuickItem = false; + + // find bounds of all map items + int itemCount = 0; + for (int i = 0; i < m_mapItems.count(); ++i) { + if (!m_mapItems.at(i)) + continue; + QDeclarativeGeoMapItemBase *item = m_mapItems.at(i).data(); + if (!item) + continue; + + // skip quick items in the first pass and refine the fit later + if (refine) { + QDeclarativeGeoMapQuickItem *quickItem = + qobject_cast(item); + if (quickItem) { + haveQuickItem = true; + continue; + } + } + // Force map items to update immediately. Needed to ensure correct item size and positions + // when recursively calling this function. + if (item->isPolishScheduled()) + item->updatePolish(); + + topLeftX = item->position().x(); + topLeftY = item->position().y(); + bottomRightX = topLeftX + item->width(); + bottomRightY = topLeftY + item->height(); + + if (itemCount == 0) { + minX = topLeftX; + maxX = bottomRightX; + minY = topLeftY; + maxY = bottomRightY; + } else { + minX = qMin(minX, topLeftX); + maxX = qMax(maxX, bottomRightX); + minY = qMin(minY, topLeftY); + maxY = qMax(maxY, bottomRightY); + } + ++itemCount; + } + + if (itemCount == 0) { + if (haveQuickItem) + fitViewportToMapItemsRefine(false); + return; + } + double bboxWidth = maxX - minX; + double bboxHeight = maxY - minY; + double bboxCenterX = minX + (bboxWidth / 2.0); + double bboxCenterY = minY + (bboxHeight / 2.0); + + // position camera to the center of bounding box + QGeoCoordinate coordinate; + coordinate = m_map->itemPositionToCoordinate(QDoubleVector2D(bboxCenterX, bboxCenterY), false); + setProperty("center", QVariant::fromValue(coordinate)); + + // adjust zoom + double bboxWidthRatio = bboxWidth / (bboxWidth + bboxHeight); + double mapWidthRatio = width() / (width() + height()); + double zoomRatio; + + if (bboxWidthRatio > mapWidthRatio) + zoomRatio = bboxWidth / width(); + else + zoomRatio = bboxHeight / height(); + + qreal newZoom = std::log10(zoomRatio) / std::log10(0.5); + newZoom = std::floor(qMax(minimumZoomLevel(), (zoomLevel() + newZoom))); + setProperty("zoomLevel", QVariant::fromValue(newZoom)); + + // as map quick items retain the same screen size after the camera zooms in/out + // we refine the viewport again to achieve better results + if (refine) + fitViewportToMapItemsRefine(false); +} + +bool QDeclarativeGeoMap::sendMouseEvent(QMouseEvent *event) +{ + QPointF localPos = mapFromScene(event->windowPos()); + QQuickWindow *win = window(); + QQuickItem *grabber = win ? win->mouseGrabberItem() : 0; + bool stealEvent = m_gestureArea->isActive(); + + if ((stealEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) { + QScopedPointer mouseEvent(QQuickWindowPrivate::cloneMouseEvent(event, &localPos)); + mouseEvent->setAccepted(false); + + switch (mouseEvent->type()) { + case QEvent::MouseMove: + m_gestureArea->handleMouseMoveEvent(mouseEvent.data()); + break; + case QEvent::MouseButtonPress: + m_gestureArea->handleMousePressEvent(mouseEvent.data()); + break; + case QEvent::MouseButtonRelease: + m_gestureArea->handleMouseReleaseEvent(mouseEvent.data()); + break; + default: + break; + } + + stealEvent = m_gestureArea->isActive(); + grabber = win ? win->mouseGrabberItem() : 0; + + if (grabber && stealEvent && !grabber->keepMouseGrab() && grabber != this) + grabMouse(); + + if (stealEvent) { + //do not deliver + event->setAccepted(true); + return true; + } else { + return false; + } + } + + if (event->type() == QEvent::MouseButtonRelease) { + if (win && win->mouseGrabberItem() == this) + ungrabMouse(); + } + + return false; +} + +bool QDeclarativeGeoMap::sendTouchEvent(QTouchEvent *event) +{ + QQuickWindowPrivate *win = window() ? QQuickWindowPrivate::get(window()) : 0; + const QTouchEvent::TouchPoint &point = event->touchPoints().first(); + QQuickItem *grabber = win ? win->itemForTouchPointId.value(point.id()) : 0; + + bool stealEvent = m_gestureArea->isActive(); + bool containsPoint = contains(mapFromScene(point.scenePos())); + + if ((stealEvent || containsPoint) && (!grabber || !grabber->keepTouchGrab())) { + QScopedPointer touchEvent(new QTouchEvent(event->type(), event->device(), event->modifiers(), event->touchPointStates(), event->touchPoints())); + touchEvent->setTimestamp(event->timestamp()); + touchEvent->setAccepted(false); + + m_gestureArea->handleTouchEvent(touchEvent.data()); + stealEvent = m_gestureArea->isActive(); + grabber = win ? win->itemForTouchPointId.value(point.id()) : 0; + + if (grabber && stealEvent && !grabber->keepTouchGrab() && grabber != this) { + QVector ids; + foreach (const QTouchEvent::TouchPoint &tp, event->touchPoints()) { + if (!(tp.state() & Qt::TouchPointReleased)) { + ids.append(tp.id()); + } + } + grabTouchPoints(ids); + } + + if (stealEvent) { + //do not deliver + event->setAccepted(true); + return true; + } else { + return false; + } + } + + if (event->type() == QEvent::TouchEnd) { + if (win && win->itemForTouchPointId.value(point.id()) == this) { + ungrabTouchPoints(); + } + } + return false; +} + +#include "moc_qdeclarativegeomap_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeomap_p.h b/src/imports/location/qdeclarativegeomap_p.h new file mode 100644 index 0000000..d1d6965 --- /dev/null +++ b/src/imports/location/qdeclarativegeomap_p.h @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAP_H +#define QDECLARATIVEGEOMAP_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeoserviceprovider.h" +#include "qdeclarativegeomapitemview_p.h" +#include "qquickgeomapgesturearea_p.h" +#include "qgeocameradata_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoServiceProvider; +class QDeclarativeGeoMapType; +class QDeclarativeGeoMapCopyrightNotice; + +class QDeclarativeGeoMap : public QQuickItem +{ + Q_OBJECT + Q_ENUMS(QGeoServiceProvider::Error) + Q_PROPERTY(QQuickGeoMapGestureArea *gesture READ gesture CONSTANT) + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(qreal minimumZoomLevel READ minimumZoomLevel WRITE setMinimumZoomLevel NOTIFY minimumZoomLevelChanged) + Q_PROPERTY(qreal maximumZoomLevel READ maximumZoomLevel WRITE setMaximumZoomLevel NOTIFY maximumZoomLevelChanged) + Q_PROPERTY(qreal zoomLevel READ zoomLevel WRITE setZoomLevel NOTIFY zoomLevelChanged) + Q_PROPERTY(QDeclarativeGeoMapType *activeMapType READ activeMapType WRITE setActiveMapType NOTIFY activeMapTypeChanged) + Q_PROPERTY(QQmlListProperty supportedMapTypes READ supportedMapTypes NOTIFY supportedMapTypesChanged) + Q_PROPERTY(QGeoCoordinate center READ center WRITE setCenter NOTIFY centerChanged) + Q_PROPERTY(QList mapItems READ mapItems NOTIFY mapItemsChanged) + Q_PROPERTY(QGeoServiceProvider::Error error READ error NOTIFY errorChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged) + Q_PROPERTY(QGeoShape visibleRegion READ visibleRegion WRITE setVisibleRegion) + Q_PROPERTY(bool copyrightsVisible READ copyrightsVisible WRITE setCopyrightsVisible NOTIFY copyrightsVisibleChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + + explicit QDeclarativeGeoMap(QQuickItem *parent = 0); + ~QDeclarativeGeoMap(); + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + + void setActiveMapType(QDeclarativeGeoMapType *mapType); + QDeclarativeGeoMapType *activeMapType() const; + + void setMinimumZoomLevel(qreal minimumZoomLevel); + qreal minimumZoomLevel() const; + + void setMaximumZoomLevel(qreal maximumZoomLevel); + qreal maximumZoomLevel() const; + + void setZoomLevel(qreal zoomLevel); + qreal zoomLevel() const; + + void setCenter(const QGeoCoordinate ¢er); + QGeoCoordinate center() const; + + void setVisibleRegion(const QGeoShape &shape); + QGeoShape visibleRegion() const; + + void setCopyrightsVisible(bool visible); + bool copyrightsVisible() const; + + void setColor(const QColor &color); + QColor color() const; + + QQmlListProperty supportedMapTypes(); + + Q_INVOKABLE void removeMapItem(QDeclarativeGeoMapItemBase *item); + Q_INVOKABLE void addMapItem(QDeclarativeGeoMapItemBase *item); + Q_INVOKABLE void clearMapItems(); + QList mapItems(); + + Q_INVOKABLE QGeoCoordinate toCoordinate(const QPointF &position, bool clipToViewPort = true) const; + Q_INVOKABLE QPointF fromCoordinate(const QGeoCoordinate &coordinate, bool clipToViewPort = true) const; + + QQuickGeoMapGestureArea *gesture(); + + Q_INVOKABLE void fitViewportToMapItems(); + Q_INVOKABLE void pan(int dx, int dy); + Q_INVOKABLE void prefetchData(); // optional hint for prefetch + Q_INVOKABLE void clearData(); + + QString errorString() const; + QGeoServiceProvider::Error error() const; + +Q_SIGNALS: + void pluginChanged(QDeclarativeGeoServiceProvider *plugin); + void zoomLevelChanged(qreal zoomLevel); + void centerChanged(const QGeoCoordinate &coordinate); + void activeMapTypeChanged(); + void supportedMapTypesChanged(); + void minimumZoomLevelChanged(); + void maximumZoomLevelChanged(); + void mapItemsChanged(); + void errorChanged(); + void copyrightLinkActivated(const QString &link); + void copyrightsVisibleChanged(bool visible); + void colorChanged(const QColor &color); + +protected: + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE ; + void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE ; + void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE ; + void mouseUngrabEvent() Q_DECL_OVERRIDE ; + void touchUngrabEvent() Q_DECL_OVERRIDE; + void touchEvent(QTouchEvent *event) Q_DECL_OVERRIDE ; + void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE ; + + bool childMouseEventFilter(QQuickItem *item, QEvent *event) Q_DECL_OVERRIDE; + bool sendMouseEvent(QMouseEvent *event); + bool sendTouchEvent(QTouchEvent *event); + + void componentComplete() Q_DECL_OVERRIDE; + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + + void setError(QGeoServiceProvider::Error error, const QString &errorString); + void initialize(); +private Q_SLOTS: + void mappingManagerInitialized(); + void pluginReady(); + void onMapChildrenChanged(); + void onSupportedMapTypesChanged(); + +private: + void setupMapView(QDeclarativeGeoMapItemView *view); + void populateMap(); + void fitViewportToMapItemsRefine(bool refine); + void fitViewportToGeoShape(); + bool isInteractive(); + +private: + QDeclarativeGeoServiceProvider *m_plugin; + QGeoServiceProvider *m_serviceProvider; + QGeoMappingManager *m_mappingManager; + QDeclarativeGeoMapType *m_activeMapType; + QList m_supportedMapTypes; + QList m_mapViews; + QQuickGeoMapGestureArea *m_gestureArea; + QGeoMap *m_map; + QPointer m_copyrights; + QList > m_mapItems; + QMutex m_updateMutex; + QString m_errorString; + QGeoServiceProvider::Error m_error; + QGeoShape m_region; + QColor m_color; + QGeoCameraData m_cameraData; + bool m_componentCompleted; + bool m_pendingFitViewport; + bool m_copyrightsVisible; + double m_maximumViewportLatitude; + bool m_initialized; + bool m_validRegion; + + friend class QDeclarativeGeoMapItem; + friend class QDeclarativeGeoMapItemView; + friend class QQuickGeoMapGestureArea; + Q_DISABLE_COPY(QDeclarativeGeoMap) +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMap) + +#endif diff --git a/src/imports/location/qdeclarativegeomapcopyrightsnotice.cpp b/src/imports/location/qdeclarativegeomapcopyrightsnotice.cpp new file mode 100644 index 0000000..7b5a576 --- /dev/null +++ b/src/imports/location/qdeclarativegeomapcopyrightsnotice.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Aaron McCarthy +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapcopyrightsnotice_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QDeclarativeGeoMapCopyrightNotice::QDeclarativeGeoMapCopyrightNotice(QQuickItem *parent) +: QQuickPaintedItem(parent), m_copyrightsHtml(0), m_copyrightsVisible(true) +{ + QQuickAnchors *anchors = property("anchors").value(); + if (anchors) { + anchors->setLeft(QQuickAnchorLine(parent, QQuickAnchors::LeftAnchor)); + anchors->setBottom(QQuickAnchorLine(parent, QQuickAnchors::BottomAnchor)); + } +} + +QDeclarativeGeoMapCopyrightNotice::~QDeclarativeGeoMapCopyrightNotice() +{ +} + +/*! + \internal +*/ +void QDeclarativeGeoMapCopyrightNotice::paint(QPainter *painter) +{ + painter->drawImage(0, 0, m_copyrightsImage); +} + +void QDeclarativeGeoMapCopyrightNotice::mousePressEvent(QMouseEvent *event) +{ + if (m_copyrightsHtml) { + m_activeAnchor = m_copyrightsHtml->documentLayout()->anchorAt(event->pos()); + if (!m_activeAnchor.isEmpty()) + return; + } + + QQuickPaintedItem::mousePressEvent(event); +} + +void QDeclarativeGeoMapCopyrightNotice::mouseReleaseEvent(QMouseEvent *event) +{ + if (m_copyrightsHtml) { + QString anchor = m_copyrightsHtml->documentLayout()->anchorAt(event->pos()); + if (anchor == m_activeAnchor && !anchor.isEmpty()) { + emit linkActivated(anchor); + m_activeAnchor.clear(); + } + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMapCopyrightNotice::setCopyrightsVisible(bool visible) +{ + m_copyrightsVisible = visible; + + setVisible(!m_copyrightsImage.isNull() && visible); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapCopyrightNotice::setCopyrightsZ(int copyrightsZ) +{ + setZ(copyrightsZ); + update(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapCopyrightNotice::copyrightsChanged(const QImage ©rightsImage) +{ + delete m_copyrightsHtml; + m_copyrightsHtml = 0; + + m_copyrightsImage = copyrightsImage; + + setWidth(m_copyrightsImage.width()); + setHeight(m_copyrightsImage.height()); + + setKeepMouseGrab(false); + setAcceptedMouseButtons(Qt::NoButton); + setVisible(m_copyrightsVisible); + + update(); +} + +void QDeclarativeGeoMapCopyrightNotice::copyrightsChanged(const QString ©rightsHtml) +{ + if (copyrightsHtml.isEmpty() || !m_copyrightsVisible) { + m_copyrightsImage = QImage(); + setVisible(false); + return; + } else { + setVisible(true); + } + + if (!m_copyrightsHtml) + m_copyrightsHtml = new QTextDocument(this); + + m_copyrightsHtml->setHtml(copyrightsHtml); + + m_copyrightsImage = QImage(m_copyrightsHtml->size().toSize(), + QImage::Format_ARGB32_Premultiplied); + m_copyrightsImage.fill(qPremultiply(qRgba(255, 255, 255, 128))); + + QPainter painter(&m_copyrightsImage); + //m_copyrightsHtml->drawContents(&painter); // <- this uses the default application palette, that might have, f.ex., white text + QAbstractTextDocumentLayout::PaintContext ctx; + ctx.palette.setColor(QPalette::Text, QColor(QStringLiteral("black"))); + ctx.palette.setColor(QPalette::Link, QColor(QStringLiteral("blue"))); + m_copyrightsHtml->documentLayout()->draw(&painter, ctx); + + setWidth(m_copyrightsImage.width()); + setHeight(m_copyrightsImage.height()); + + setContentsSize(m_copyrightsImage.size()); + + setKeepMouseGrab(true); + setAcceptedMouseButtons(Qt::LeftButton); + + update(); +} + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeomapcopyrightsnotice_p.h b/src/imports/location/qdeclarativegeomapcopyrightsnotice_p.h new file mode 100644 index 0000000..771ced6 --- /dev/null +++ b/src/imports/location/qdeclarativegeomapcopyrightsnotice_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Aaron McCarthy +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPCOPYRIGHTSNOTICE_H +#define QDECLARATIVEGEOMAPCOPYRIGHTSNOTICE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QTextDocument; + +class QDeclarativeGeoMapCopyrightNotice : public QQuickPaintedItem +{ + Q_OBJECT + +public: + explicit QDeclarativeGeoMapCopyrightNotice(QQuickItem *parent); + ~QDeclarativeGeoMapCopyrightNotice(); + + void setCopyrightsZ(int copyrightsZ); + + void setCopyrightsVisible(bool visible); + +public Q_SLOTS: + void copyrightsChanged(const QImage ©rightsImage); + void copyrightsChanged(const QString ©rightsHtml); + +signals: + void linkActivated(const QString &link); + +protected: + void paint(QPainter *painter) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + +private: + QTextDocument *m_copyrightsHtml; + QImage m_copyrightsImage; + QString m_activeAnchor; + bool m_copyrightsVisible; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/qdeclarativegeomapitembase.cpp b/src/imports/location/qdeclarativegeomapitembase.cpp new file mode 100644 index 0000000..8e825f5 --- /dev/null +++ b/src/imports/location/qdeclarativegeomapitembase.cpp @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapitembase_p.h" +#include "qgeocameradata_p.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoMapViewportChangeEvent::QGeoMapViewportChangeEvent() + : zoomLevelChanged(false), + centerChanged(false), + mapSizeChanged(false), + tiltChanged(false), + bearingChanged(false), + rollChanged(false) +{ +} + +QGeoMapViewportChangeEvent::QGeoMapViewportChangeEvent(const QGeoMapViewportChangeEvent &other) +{ + this->operator=(other); +} + +QGeoMapViewportChangeEvent &QGeoMapViewportChangeEvent::operator=(const QGeoMapViewportChangeEvent &other) +{ + if (this == &other) + return (*this); + + cameraData = other.cameraData; + mapSize = other.mapSize; + zoomLevelChanged = other.zoomLevelChanged; + centerChanged = other.centerChanged; + mapSizeChanged = other.mapSizeChanged; + tiltChanged = other.tiltChanged; + bearingChanged = other.bearingChanged; + rollChanged = other.rollChanged; + + return (*this); +} + +QDeclarativeGeoMapItemBase::QDeclarativeGeoMapItemBase(QQuickItem *parent) +: QQuickItem(parent), map_(0), quickMap_(0) +{ + setFiltersChildMouseEvents(true); + connect(this, SIGNAL(childrenChanged()), + this, SLOT(afterChildrenChanged())); +} + +QDeclarativeGeoMapItemBase::~QDeclarativeGeoMapItemBase() +{ + disconnect(this, SLOT(afterChildrenChanged())); + if (quickMap_) + quickMap_->removeMapItem(this); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemBase::afterChildrenChanged() +{ + QList kids = childItems(); + if (kids.size() > 0) { + bool printedWarning = false; + foreach (QQuickItem *i, kids) { + if (i->flags() & QQuickItem::ItemHasContents + && !qobject_cast(i)) { + if (!printedWarning) { + qmlInfo(this) << "Geographic map items do not support child items"; + printedWarning = true; + } + + qmlInfo(i) << "deleting this child"; + i->deleteLater(); + } + } + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemBase::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + if (quickMap == quickMap_) + return; + if (quickMap && quickMap_) + return; // don't allow association to more than one map + if (quickMap_) + quickMap_->disconnect(this); + if (map_) + map_->disconnect(this); + + quickMap_ = quickMap; + map_ = map; + + if (map_ && quickMap_) { + connect(map_, SIGNAL(cameraDataChanged(QGeoCameraData)), + this, SLOT(baseCameraDataChanged(QGeoCameraData))); + connect(quickMap, SIGNAL(heightChanged()), this, SLOT(polishAndUpdate())); + connect(quickMap, SIGNAL(widthChanged()), this, SLOT(polishAndUpdate())); + lastSize_ = QSizeF(quickMap_->width(), quickMap_->height()); + lastCameraData_ = map_->cameraData(); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemBase::baseCameraDataChanged(const QGeoCameraData &cameraData) +{ + QGeoMapViewportChangeEvent evt; + evt.cameraData = cameraData; + evt.mapSize = QSizeF(quickMap_->width(), quickMap_->height()); + + if (evt.mapSize != lastSize_) + evt.mapSizeChanged = true; + + if (cameraData.bearing() != lastCameraData_.bearing()) + evt.bearingChanged = true; + if (cameraData.center() != lastCameraData_.center()) + evt.centerChanged = true; + if (cameraData.roll() != lastCameraData_.roll()) + evt.rollChanged = true; + if (cameraData.tilt() != lastCameraData_.tilt()) + evt.tiltChanged = true; + if (cameraData.zoomLevel() != lastCameraData_.zoomLevel()) + evt.zoomLevelChanged = true; + + lastSize_ = evt.mapSize; + lastCameraData_ = cameraData; + + afterViewportChanged(evt); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemBase::setPositionOnMap(const QGeoCoordinate &coordinate, const QPointF &offset) +{ + if (!map_ || !quickMap_) + return; + + QPointF topLeft = map_->coordinateToItemPosition(coordinate, false).toPointF() - offset; + + setPosition(topLeft); +} + +/*! + \internal +*/ +float QDeclarativeGeoMapItemBase::zoomLevelOpacity() const +{ + if (quickMap_->zoomLevel() > 3.0) + return 1.0; + else if (quickMap_->zoomLevel() > 2.0) + return quickMap_->zoomLevel() - 2.0; + else + return 0.0; +} + +bool QDeclarativeGeoMapItemBase::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + Q_UNUSED(item) + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + if (contains(static_cast(event)->pos())) { + return false; + } else { + event->setAccepted(false); + return true; + } + default: + return false; + } +} + +/*! + \internal +*/ +QSGNode *QDeclarativeGeoMapItemBase::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *pd) +{ + if (!map_ || !quickMap_) { + delete oldNode; + return 0; + } + + QSGOpacityNode *opn = static_cast(oldNode); + if (!opn) + opn = new QSGOpacityNode(); + + opn->setOpacity(zoomLevelOpacity()); + + QSGNode *oldN = opn->childCount() ? opn->firstChild() : 0; + opn->removeAllChildNodes(); + if (opn->opacity() > 0.0) { + QSGNode *n = this->updateMapItemPaintNode(oldN, pd); + if (n) + opn->appendChildNode(n); + } else { + delete oldN; + } + + return opn; +} + +/*! + \internal +*/ +QSGNode *QDeclarativeGeoMapItemBase::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + delete oldNode; + return 0; +} + +bool QDeclarativeGeoMapItemBase::isPolishScheduled() const +{ + return QQuickItemPrivate::get(this)->polishScheduled; +} + +void QDeclarativeGeoMapItemBase::polishAndUpdate() +{ + polish(); + update(); +} + + +#include "moc_qdeclarativegeomapitembase_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeomapitembase_p.h b/src/imports/location/qdeclarativegeomapitembase_p.h new file mode 100644 index 0000000..c7793fb --- /dev/null +++ b/src/imports/location/qdeclarativegeomapitembase_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPITEMBASE_H +#define QDECLARATIVEGEOMAPITEMBASE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "qdeclarativegeomap_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoMapViewportChangeEvent +{ +public: + explicit QGeoMapViewportChangeEvent(); + QGeoMapViewportChangeEvent(const QGeoMapViewportChangeEvent &other); + QGeoMapViewportChangeEvent &operator=(const QGeoMapViewportChangeEvent &other); + + QGeoCameraData cameraData; + QSizeF mapSize; + + bool zoomLevelChanged; + bool centerChanged; + bool mapSizeChanged; + bool tiltChanged; + bool bearingChanged; + bool rollChanged; +}; + +class QDeclarativeGeoMapItemBase : public QQuickItem +{ + Q_OBJECT +public: + explicit QDeclarativeGeoMapItemBase(QQuickItem *parent = 0); + virtual ~QDeclarativeGeoMapItemBase(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map); + virtual void setPositionOnMap(const QGeoCoordinate &coordinate, const QPointF &offset); + + QDeclarativeGeoMap *quickMap() { return quickMap_; } + QGeoMap *map() { return map_; } + + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *); + +protected Q_SLOTS: + virtual void afterChildrenChanged(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) = 0; + void polishAndUpdate(); + +protected: + float zoomLevelOpacity() const; + bool childMouseEventFilter(QQuickItem *item, QEvent *event); + bool isPolishScheduled() const; + +private Q_SLOTS: + void baseCameraDataChanged(const QGeoCameraData &camera); + +private: + QGeoMap *map_; + QDeclarativeGeoMap *quickMap_; + + QSizeF lastSize_; + QGeoCameraData lastCameraData_; + + friend class QDeclarativeGeoMap; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/qdeclarativegeomapitemview.cpp b/src/imports/location/qdeclarativegeomapitemview.cpp new file mode 100644 index 0000000..0a9128f --- /dev/null +++ b/src/imports/location/qdeclarativegeomapitemview.cpp @@ -0,0 +1,540 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapitemview_p.h" +#include "qdeclarativegeomap_p.h" +#include "qdeclarativegeomapitembase_p.h" +#include "mapitemviewdelegateincubator.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapItemView + \instantiates QDeclarativeGeoMapItemView + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + \inherits QObject + + \brief The MapItemView is used to populate Map from a model. + + The MapItemView is used to populate Map with MapItems from a model. + The MapItemView type only makes sense when contained in a Map, + meaning that it has no standalone presentation. + + \section2 Example Usage + + This example demonstrates how to use the MapViewItem object to display + a \l{Route}{route} on a \l{Map}{map}: + + \snippet declarative/maps.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/maps.qml MapRoute +*/ + +QDeclarativeGeoMapItemView::QDeclarativeGeoMapItemView(QQuickItem *parent) + : QObject(parent), componentCompleted_(false), delegate_(0), + itemModel_(0), map_(0), fitViewport_(false), m_metaObjectType(0), + m_readyIncubators(0), m_repopulating(false) +{ +} + +QDeclarativeGeoMapItemView::~QDeclarativeGeoMapItemView() +{ + removeInstantiatedItems(); + if (m_metaObjectType) + m_metaObjectType->release(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::componentComplete() +{ + componentCompleted_ = true; +} + +void QDeclarativeGeoMapItemView::incubatorStatusChanged(MapItemViewDelegateIncubator *incubator, + QQmlIncubator::Status status, + bool batched) +{ + if (status == QQmlIncubator::Loading) + return; + + QDeclarativeGeoMapItemViewItemData *itemData = incubator->m_itemData; + if (!itemData) { + // Should never get here + qWarning() << "MapItemViewDelegateIncubator incubating invalid itemData"; + return; + } + + switch (status) { + case QQmlIncubator::Ready: + { + QDeclarativeGeoMapItemBase *item = qobject_cast(incubator->object()); + if (!item) + break; + itemData->item = item; + if (!itemData->item) { + qWarning() << "QDeclarativeGeoMapItemView map item delegate is of unsupported type."; + delete incubator->object(); + } else { + if (!batched) { + map_->addMapItem(itemData->item); + fitViewport(); + } else { + ++m_readyIncubators; // QSemaphore not needed as multiple threads not involved + + if (m_readyIncubators == m_itemDataBatched.size()) { + + // Clearing stuff older than the reset + foreach (QDeclarativeGeoMapItemViewItemData *i, m_itemData) + removeItemData(i); + m_itemData.clear(); + + // Adding everthing created after reset was issued + foreach (QDeclarativeGeoMapItemViewItemData *i, m_itemDataBatched) { + map_->addMapItem(i->item); + } + m_itemData = m_itemDataBatched; + m_itemDataBatched.clear(); + + m_readyIncubators = 0; + m_repopulating = false; + + fitViewport(); + } + } + } + delete itemData->incubator; + itemData->incubator = 0; + break; + } + case QQmlIncubator::Null: + // Should never get here + delete itemData->incubator; + itemData->incubator = 0; + break; + case QQmlIncubator::Error: + qWarning() << "QDeclarativeGeoMapItemView map item creation failed."; + delete itemData->incubator; + itemData->incubator = 0; + break; + default: + ; + } +} + +/*! + \qmlproperty model QtLocation::MapItemView::model + + This property holds the model that provides data used for creating the map items defined by the + delegate. Only QAbstractItemModel based models are supported. +*/ +QVariant QDeclarativeGeoMapItemView::model() const +{ + return QVariant::fromValue(itemModel_); +} + +void QDeclarativeGeoMapItemView::setModel(const QVariant &model) +{ + QAbstractItemModel *itemModel = model.value(); + if (itemModel == itemModel_) + return; + + if (itemModel_) { + disconnect(itemModel_, SIGNAL(modelReset()), this, SLOT(itemModelReset())); + disconnect(itemModel_, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(itemModelRowsRemoved(QModelIndex,int,int))); + disconnect(itemModel_, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(itemModelRowsInserted(QModelIndex,int,int))); + disconnect(itemModel_, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(itemModelRowsMoved(QModelIndex,int,int,QModelIndex,int))); + disconnect(itemModel_, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), + this, SLOT(itemModelDataChanged(QModelIndex,QModelIndex,QVector))); + + removeInstantiatedItems(); // this also terminates ongong repopulations. + m_metaObjectType->release(); + m_metaObjectType = 0; + + itemModel_ = 0; + } + + if (itemModel) { + itemModel_ = itemModel; + connect(itemModel_, SIGNAL(modelReset()), this, SLOT(itemModelReset())); + connect(itemModel_, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(itemModelRowsRemoved(QModelIndex,int,int))); + connect(itemModel_, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(itemModelRowsInserted(QModelIndex,int,int))); + connect(itemModel_, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(itemModelRowsMoved(QModelIndex,int,int,QModelIndex,int))); + connect(itemModel_, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), + this, SLOT(itemModelDataChanged(QModelIndex,QModelIndex,QVector))); + + m_metaObjectType = new QQmlOpenMetaObjectType(&QObject::staticMetaObject, 0); + foreach (const QByteArray &name, itemModel_->roleNames()) + m_metaObjectType->createProperty(name); + + instantiateAllItems(); + } + + emit modelChanged(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::itemModelReset() +{ + repopulate(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::itemModelRowsInserted(const QModelIndex &index, int start, int end) +{ + Q_UNUSED(index) + + if (!componentCompleted_ || !map_ || !delegate_ || !itemModel_) + return; + + for (int i = start; i <= end; ++i) { + const QModelIndex insertedIndex = itemModel_->index(i, 0, index); + // If ran inside a qquickwidget which forces incubators to be synchronous, this call won't happen + // with m_repopulating == true while incubators from a model reset are still incubating. + // Note that having the model in a different thread is not supported in general. + createItemForIndex(insertedIndex, m_repopulating); + } + + fitViewport(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::itemModelRowsRemoved(const QModelIndex &index, int start, int end) +{ + Q_UNUSED(index) + + if (!componentCompleted_ || !map_ || !delegate_ || !itemModel_) + return; + + for (int i = end; i >= start; --i) { + if (m_repopulating) { + QDeclarativeGeoMapItemViewItemData *itemData = m_itemDataBatched.takeAt(i); + if (!itemData) + continue; + if (itemData->incubator) { + if (itemData->incubator->isReady()) { + --m_readyIncubators; + delete itemData->incubator->object(); + } + itemData->incubator->clear(); + } + delete itemData; + } else { + QDeclarativeGeoMapItemViewItemData *itemData = m_itemData.takeAt(i); + removeItemData(itemData); + } + } + + fitViewport(); +} + +void QDeclarativeGeoMapItemView::itemModelRowsMoved(const QModelIndex &parent, int start, int end, + const QModelIndex &destination, int row) +{ + Q_UNUSED(parent) + Q_UNUSED(start) + Q_UNUSED(end) + Q_UNUSED(destination) + Q_UNUSED(row) + + qWarning() << "QDeclarativeGeoMapItemView does not support models that move rows."; +} + +void QDeclarativeGeoMapItemView::itemModelDataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight, + const QVector &roles) +{ + Q_UNUSED(roles) + + if (!m_itemData.count() || (m_repopulating && !m_itemDataBatched.count()) ) + return; + + for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { + const QModelIndex index = itemModel_->index(i, 0); + QDeclarativeGeoMapItemViewItemData *itemData; + if (m_repopulating) + itemData= m_itemDataBatched.at(i); + else + itemData= m_itemData.at(i); + + QHashIterator iterator(itemModel_->roleNames()); + while (iterator.hasNext()) { + iterator.next(); + + QVariant modelData = itemModel_->data(index, iterator.key()); + if (!modelData.isValid()) + continue; + + itemData->context->setContextProperty(QString::fromLatin1(iterator.value().constData()), + modelData); + itemData->modelDataMeta->setValue(iterator.value(), modelData); + } + } +} + +/*! + \qmlproperty Component QtLocation::MapItemView::delegate + + This property holds the delegate which defines how each item in the + model should be displayed. The Component must contain exactly one + MapItem -derived object as the root object. +*/ +QQmlComponent *QDeclarativeGeoMapItemView::delegate() const +{ + return delegate_; +} + +void QDeclarativeGeoMapItemView::setDelegate(QQmlComponent *delegate) +{ + if (delegate_ == delegate) + return; + + delegate_ = delegate; + + repopulate(); + emit delegateChanged(); +} + +/*! + \qmlproperty Component QtLocation::MapItemView::autoFitViewport + + This property controls whether to automatically pan and zoom the viewport + to display all map items when items are added or removed. + + Defaults to false. +*/ +bool QDeclarativeGeoMapItemView::autoFitViewport() const +{ + return fitViewport_; +} + +void QDeclarativeGeoMapItemView::setAutoFitViewport(const bool &fitViewport) +{ + if (fitViewport == fitViewport_) + return; + fitViewport_ = fitViewport; + emit autoFitViewportChanged(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::fitViewport() +{ + if (!map_ || !fitViewport_ || m_repopulating) + return; + + if (map_->mapItems().size() > 0) + map_->fitViewportToMapItems(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::setMap(QDeclarativeGeoMap *map) +{ + if (!map || map_) // changing map on the fly not supported + return; + map_ = map; +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::removeInstantiatedItems() +{ + if (!map_) + return; + + terminateOngoingRepopulation(); + foreach (QDeclarativeGeoMapItemViewItemData *itemData, m_itemData) + removeItemData(itemData); + m_itemData.clear(); +} + +/*! + \internal + + Instantiates all items. +*/ +void QDeclarativeGeoMapItemView::instantiateAllItems() +{ + if (!componentCompleted_ || !map_ || !delegate_ || !itemModel_) + return; + Q_ASSERT(!m_itemDataBatched.size()); + m_repopulating = true; + + // QQuickWidget forces incubators to synchronous mode. Thus itemDataChanged gets called during the for loop below. + m_itemDataBatched.resize(itemModel_->rowCount()); + for (int i = 0; i < itemModel_->rowCount(); ++i) { + const QModelIndex index = itemModel_->index(i, 0); + createItemForIndex(index, true); + } + + fitViewport(); +} + +void QDeclarativeGeoMapItemView::removeItemData(QDeclarativeGeoMapItemViewItemData *itemData) +{ + if (!itemData) + return; + if (itemData->incubator) { + if (itemData->incubator->isReady()) { + if (itemData->incubator->object() == itemData->item) { + map_->removeMapItem(itemData->item); // removeMapItem checks whether the item is in the map, so it's safe to call. + itemData->item = 0; + } + delete itemData->incubator->object(); + } + itemData->incubator->clear(); // stops ongoing incubation + } + if (itemData->item) + map_->removeMapItem(itemData->item); + delete itemData; // destroys the ->item too. +} + +void QDeclarativeGeoMapItemView::terminateOngoingRepopulation() +{ + if (m_repopulating) { + // Terminate the previous resetting task. Not all incubators finished, but + // QQmlIncubatorController operates in the same thread, so it is safe + // to check, here, whether incubators are ready or not, without having + // to race with them. + + foreach (QDeclarativeGeoMapItemViewItemData *itemData, m_itemDataBatched) + removeItemData(itemData); + + m_itemDataBatched.clear(); + m_readyIncubators = 0; + m_repopulating = false; + } +} + +/*! + \internal + Removes and repopulates all items. +*/ +void QDeclarativeGeoMapItemView::repopulate() +{ + if (!itemModel_ || !itemModel_->rowCount()) { + removeInstantiatedItems(); + } else { + terminateOngoingRepopulation(); + instantiateAllItems(); // removal of instantiated item done at incubation completion + } +} + +/*! + \internal + + Note: this call is async. that is returns to the event loop before returning to the caller. + May also trigger incubatorStatusChanged() before returning to the caller if the incubator is fast enough. +*/ +void QDeclarativeGeoMapItemView::createItemForIndex(const QModelIndex &index, bool batched) +{ + // Expected to be already tested by caller. + Q_ASSERT(delegate_); + Q_ASSERT(itemModel_); + + QDeclarativeGeoMapItemViewItemData *itemData = new QDeclarativeGeoMapItemViewItemData; + + itemData->modelData = new QObject; + itemData->modelDataMeta = new QQmlOpenMetaObject(itemData->modelData, m_metaObjectType, false); + itemData->context = new QQmlContext(qmlContext(this)); + + QHashIterator iterator(itemModel_->roleNames()); + while (iterator.hasNext()) { + iterator.next(); + + QVariant modelData = itemModel_->data(index, iterator.key()); + if (!modelData.isValid()) + continue; + + itemData->context->setContextProperty(QString::fromLatin1(iterator.value().constData()), + modelData); + + itemData->modelDataMeta->setValue(iterator.value(), modelData); + } + + itemData->context->setContextProperty(QLatin1String("model"), itemData->modelData); + itemData->context->setContextProperty(QLatin1String("index"), index.row()); + + if (batched || m_repopulating) { + if (index.row() < m_itemDataBatched.size()) + m_itemDataBatched.replace(index.row(), itemData); + else + m_itemDataBatched.insert(index.row(), itemData); + } else + m_itemData.insert(index.row(), itemData); + itemData->incubator = new MapItemViewDelegateIncubator(this, itemData, batched || m_repopulating); + + delegate_->create(*itemData->incubator, itemData->context); +} + +QDeclarativeGeoMapItemViewItemData::~QDeclarativeGeoMapItemViewItemData() +{ + delete incubator; + delete item; + delete context; + delete modelData; +} + +#include "moc_qdeclarativegeomapitemview_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeomapitemview_p.h b/src/imports/location/qdeclarativegeomapitemview_p.h new file mode 100644 index 0000000..6a4a292 --- /dev/null +++ b/src/imports/location/qdeclarativegeomapitemview_p.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPITEMVIEW_H +#define QDECLARATIVEGEOMAPITEMVIEW_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include "qdeclarativegeomapitemview_p_p.h" + +QT_BEGIN_NAMESPACE + +class QAbstractItemModel; +class QQmlComponent; +class QQuickItem; +class QDeclarativeGeoMap; +class QDeclarativeGeoMapItemBase; +class QQmlOpenMetaObject; +class QQmlOpenMetaObjectType; +class MapItemViewDelegateIncubator; + +class QDeclarativeGeoMapItemView : public QObject, public QQmlParserStatus +{ + Q_OBJECT + + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(bool autoFitViewport READ autoFitViewport WRITE setAutoFitViewport NOTIFY autoFitViewportChanged) + +public: + explicit QDeclarativeGeoMapItemView(QQuickItem *parent = 0); + ~QDeclarativeGeoMapItemView(); + + QVariant model() const; + void setModel(const QVariant &); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *); + + bool autoFitViewport() const; + void setAutoFitViewport(const bool &); + + void setMap(QDeclarativeGeoMap *); + void repopulate(); + void removeInstantiatedItems(); + void instantiateAllItems(); + + qreal zValue(); + void setZValue(qreal zValue); + + // From QQmlParserStatus + virtual void componentComplete(); + void classBegin() {} + +Q_SIGNALS: + void modelChanged(); + void delegateChanged(); + void autoFitViewportChanged(); + +protected: + void incubatorStatusChanged(MapItemViewDelegateIncubator *incubator, + QQmlIncubator::Status status, + bool batched); + +private Q_SLOTS: + void itemModelReset(); + void itemModelRowsInserted(const QModelIndex &index, int start, int end); + void itemModelRowsRemoved(const QModelIndex &index, int start, int end); + void itemModelRowsMoved(const QModelIndex &parent, int start, int end, + const QModelIndex &destination, int row); + void itemModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles); + +private: + void createItemForIndex(const QModelIndex &index, bool batched = false); + void fitViewport(); + void terminateOngoingRepopulation(); + void removeItemData(QDeclarativeGeoMapItemViewItemData *itemData); + + bool componentCompleted_; + QQmlComponent *delegate_; + QAbstractItemModel *itemModel_; + QDeclarativeGeoMap *map_; + QVector m_itemData; + QVector m_itemDataBatched; + bool fitViewport_; + + QQmlOpenMetaObjectType *m_metaObjectType; + int m_readyIncubators; + bool m_repopulating; + + friend class QDeclarativeGeoMapItemViewItemData; + friend class MapItemViewDelegateIncubator; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMapItemView) + +#endif diff --git a/src/imports/location/qdeclarativegeomapitemview_p_p.h b/src/imports/location/qdeclarativegeomapitemview_p_p.h new file mode 100644 index 0000000..5a4e3b2 --- /dev/null +++ b/src/imports/location/qdeclarativegeomapitemview_p_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPITEMVIEW_P_P_H +#define QDECLARATIVEGEOMAPITEMVIEW_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class MapItemViewDelegateIncubator; +class QDeclarativeGeoMapItemView; +class QDeclarativeGeoMapItemBase; + +struct QDeclarativeGeoMapItemViewItemData { + QDeclarativeGeoMapItemViewItemData() + : incubator(0), item(0), context(0), modelData(0), modelDataMeta(0) + { + } + + ~QDeclarativeGeoMapItemViewItemData(); + + MapItemViewDelegateIncubator *incubator; + QDeclarativeGeoMapItemBase *item; + QQmlContext *context; + QObject *modelData; + QQmlOpenMetaObject *modelDataMeta; + + friend class MapItemViewDelegateIncubator; + friend class QDeclarativeGeoMapItemView; +}; + +Q_DECLARE_TYPEINFO(QDeclarativeGeoMapItemViewItemData, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +#endif // QDECLARATIVEGEOMAPITEMVIEW_P_P_H diff --git a/src/imports/location/qdeclarativegeomapquickitem.cpp b/src/imports/location/qdeclarativegeomapquickitem.cpp new file mode 100644 index 0000000..a66efb1 --- /dev/null +++ b/src/imports/location/qdeclarativegeomapquickitem.cpp @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapquickitem_p.h" + +#include +#include +#include +#include "qdoublevector2d_p.h" +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapQuickItem + \instantiates QDeclarativeGeoMapQuickItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapQuickItem type displays an arbitrary Qt Quick object + on a Map. + + The MapQuickItem type is used to place an arbitrary Qt Quick object + on a Map at a specified location and size. Compared to floating an item + above the Map, a MapQuickItem will follow the panning (and optionally, the + zooming) of the Map as if it is on the Map surface. + + The \l{sourceItem} property contains the Qt Quick item to be drawn, which + can be any kind of visible type. + + \section2 Positioning and Sizing + + The positioning of the MapQuickItem on the Map is controlled by two + properties: \l coordinate and \l anchorPoint. If only \l coordinate is set, + it specifies a longitude/latitude coordinate for the item to be placed at. + The set coordinate will line up with the top-left corner of the contained + item when shown on the screen. + + The \l anchorPoint property provides a way to line up the coordinate with + other parts of the item than just the top-left corner, by setting a number + of pixels the item will be offset by. A simple way to think about it is + to note that the point given by \l anchorPoint on the item itself is the + point that will line up with the given \l coordinate when displayed. + + In addition to being anchored to the map, the MapQuickItem can optionally + follow the scale of the map, and change size when the Map is zoomed in or + zoomed out. This behaviour is controlled by the \l zoomLevel property. The + default behaviour if \l zoomLevel is not set is for the item to be drawn + "on the screen" rather than "on the map", so that its size remains the same + regardless of the zoom level of the Map. + + \section2 Performance + + Performance of a MapQuickItem is normally in the same ballpark as the + contained Qt Quick item alone. Overheads added amount to a translation + and (possibly) scaling of the original item, as well as a transformation + from longitude and latitude to screen position. + + \section2 Limitations + + \note Due to an implementation detail, items placed inside a + MapQuickItem will have a \c{parent} item which is not the MapQuickItem. + Refer to the MapQuickItem by its \c{id}, and avoid the use of \c{anchor} + in the \c{sourceItem}. + + \section2 Example Usage + + The following snippet shows a MapQuickItem containing an Image object, + to display a Marker on the Map. This strategy is used to show the map + markers in the MapViewer example. + + \snippet mapviewer/map/Marker.qml mqi-top + \snippet mapviewer/map/Marker.qml mqi-anchor + \snippet mapviewer/map/Marker.qml mqi-closeimage + \snippet mapviewer/map/Marker.qml mqi-close + + \image api-mapquickitem.png +*/ + +QDeclarativeGeoMapQuickItem::QDeclarativeGeoMapQuickItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), zoomLevel_(0.0), + mapAndSourceItemSet_(false), updatingGeometry_(false) +{ + setFlag(ItemHasContents, true); + opacityContainer_ = new QQuickItem(this); + opacityContainer_->setParentItem(this); + opacityContainer_->setFlag(ItemHasContents, true); +} + +QDeclarativeGeoMapQuickItem::~QDeclarativeGeoMapQuickItem() {} + +/*! + \qmlproperty coordinate MapQuickItem::coordinate + + This property holds the anchor coordinate of the MapQuickItem. The point + on the sourceItem that is specified by anchorPoint is kept aligned with + this coordinate when drawn on the map. + + In the image below, there are 3 MapQuickItems that are identical except + for the value of their anchorPoint properties. The values of anchorPoint + for each are written on top of the item. + + \image api-mapquickitem-anchor.png +*/ +void QDeclarativeGeoMapQuickItem::setCoordinate(const QGeoCoordinate &coordinate) +{ + if (coordinate_ == coordinate) + return; + + coordinate_ = coordinate; + + polishAndUpdate(); + emit coordinateChanged(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (map && quickMap) { + connect(map, SIGNAL(cameraDataChanged(QGeoCameraData)), + this, SLOT(polishAndUpdate())); + polishAndUpdate(); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (!mapAndSourceItemSet_ || updatingGeometry_ || + newGeometry.topLeft() == oldGeometry.topLeft()) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + + QGeoCoordinate newCoordinate = map()->itemPositionToCoordinate(QDoubleVector2D(x(), y()) + (scaleFactor() * QDoubleVector2D(anchorPoint_)), false); + if (newCoordinate.isValid()) + setCoordinate(newCoordinate); + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +/*! + \internal +*/ +QGeoCoordinate QDeclarativeGeoMapQuickItem::coordinate() +{ + return coordinate_; +} + +/*! + \qmlproperty object MapQuickItem::sourceItem + + This property holds the source item that will be drawn on the map. +*/ +void QDeclarativeGeoMapQuickItem::setSourceItem(QQuickItem *sourceItem) +{ + if (sourceItem_.data() == sourceItem) + return; + sourceItem_ = sourceItem; + + polishAndUpdate(); + emit sourceItemChanged(); +} + +QQuickItem *QDeclarativeGeoMapQuickItem::sourceItem() +{ + return sourceItem_.data(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::afterChildrenChanged() +{ + QList kids = childItems(); + if (kids.size() > 0) { + bool printedWarning = false; + foreach (QQuickItem *i, kids) { + if (i->flags() & QQuickItem::ItemHasContents + && !qobject_cast(i) + && sourceItem_.data() != i + && opacityContainer_ != i) { + if (!printedWarning) { + qmlInfo(this) << "Use the sourceItem property for the contained item, direct children are not supported"; + printedWarning = true; + } + + qmlInfo(i) << "deleting this child"; + i->deleteLater(); + } + } + } +} + +/*! + \qmlproperty QPointF MapQuickItem::anchorPoint + + This property determines which point on the sourceItem that will be lined + up with the coordinate on the map. +*/ +void QDeclarativeGeoMapQuickItem::setAnchorPoint(const QPointF &anchorPoint) +{ + if (anchorPoint == anchorPoint_) + return; + anchorPoint_ = anchorPoint; + polishAndUpdate(); + emit anchorPointChanged(); +} + +QPointF QDeclarativeGeoMapQuickItem::anchorPoint() const +{ + return anchorPoint_; +} + +/*! + \qmlproperty real MapQuickItem::zoomLevel + + This property controls the scaling behaviour of the contents of the + MapQuickItem. In particular, by setting this property it is possible + to choose between objects that are drawn on the screen (and sized in + screen pixels), and those drawn on the map surface (which change size + with the zoom level of the map). + + The default value for this property is 0.0, which corresponds to drawing + the object on the screen surface. If set to another value, the object will + be drawn on the map surface instead. The value (if not zero) specifies the + zoomLevel at which the object will be visible at a scale of 1:1 (ie, where + object pixels and screen pixels are the same). At zoom levels lower than + this, the object will appear smaller, and at higher zoom levels, appear + larger. This is in contrast to when this property is set to zero, where + the object remains the same size on the screen at all zoom levels. +*/ +void QDeclarativeGeoMapQuickItem::setZoomLevel(qreal zoomLevel) +{ + if (zoomLevel == zoomLevel_) + return; + zoomLevel_ = zoomLevel; + polishAndUpdate(); + emit zoomLevelChanged(); +} + +qreal QDeclarativeGeoMapQuickItem::zoomLevel() const +{ + return zoomLevel_; +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::updatePolish() +{ + if (!quickMap() && sourceItem_) { + mapAndSourceItemSet_ = false; + sourceItem_.data()->setParentItem(0); + return; + } + + if (!quickMap() || !map() || !sourceItem_) { + mapAndSourceItemSet_ = false; + return; + } + + if (!mapAndSourceItemSet_ && quickMap() && map() && sourceItem_) { + mapAndSourceItemSet_ = true; + sourceItem_.data()->setParentItem(opacityContainer_); + sourceItem_.data()->setTransformOrigin(QQuickItem::TopLeft); + connect(sourceItem_.data(), SIGNAL(xChanged()), + this, SLOT(polishAndUpdate())); + connect(sourceItem_.data(), SIGNAL(yChanged()), + this, SLOT(polishAndUpdate())); + connect(sourceItem_.data(), SIGNAL(widthChanged()), + this, SLOT(polishAndUpdate())); + connect(sourceItem_.data(), SIGNAL(heightChanged()), + this, SLOT(polishAndUpdate())); + } + + QScopedValueRollback rollback(updatingGeometry_); + updatingGeometry_ = true; + + opacityContainer_->setOpacity(zoomLevelOpacity()); + + sourceItem_.data()->setScale(scaleFactor()); + sourceItem_.data()->setPosition(QPointF(0,0)); + setWidth(sourceItem_.data()->width()); + setHeight(sourceItem_.data()->height()); + setPositionOnMap(coordinate(), scaleFactor() * anchorPoint_); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + Q_UNUSED(event); +} + +/*! + \internal +*/ +qreal QDeclarativeGeoMapQuickItem::scaleFactor() +{ + qreal scale = 1.0; + // use 1+x to avoid fuzzy compare against zero + if (!qFuzzyCompare(1.0 + zoomLevel_, 1.0)) + scale = std::pow(0.5, zoomLevel_ - map()->cameraData().zoomLevel()); + return scale; +} + +#include "moc_qdeclarativegeomapquickitem_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeomapquickitem_p.h b/src/imports/location/qdeclarativegeomapquickitem_p.h new file mode 100644 index 0000000..0410f06 --- /dev/null +++ b/src/imports/location/qdeclarativegeomapquickitem_p.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPQUICKITEM_H +#define QDECLARATIVEGEOMAPQUICKITEM_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include "qdeclarativegeomap_p.h" +#include "qdeclarativegeomapitembase_p.h" + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoMapQuickItem : public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + Q_PROPERTY(QGeoCoordinate coordinate READ coordinate WRITE setCoordinate NOTIFY coordinateChanged) + Q_PROPERTY(QPointF anchorPoint READ anchorPoint WRITE setAnchorPoint NOTIFY anchorPointChanged) + Q_PROPERTY(qreal zoomLevel READ zoomLevel WRITE setZoomLevel NOTIFY zoomLevelChanged) + Q_PROPERTY(QQuickItem *sourceItem READ sourceItem WRITE setSourceItem NOTIFY sourceItemChanged) + +public: + explicit QDeclarativeGeoMapQuickItem(QQuickItem *parent = 0); + ~QDeclarativeGeoMapQuickItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + + void setCoordinate(const QGeoCoordinate &coordinate); + QGeoCoordinate coordinate(); + + void setSourceItem(QQuickItem *sourceItem); + QQuickItem *sourceItem(); + + void setAnchorPoint(const QPointF &anchorPoint); + QPointF anchorPoint() const; + + void setZoomLevel(qreal zoomLevel); + qreal zoomLevel() const; + +Q_SIGNALS: + void coordinateChanged(); + void sourceItemChanged(); + void anchorPointChanged(); + void zoomLevelChanged(); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void updatePolish() Q_DECL_OVERRIDE; + +protected Q_SLOTS: + virtual void afterChildrenChanged() Q_DECL_OVERRIDE; + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + qreal scaleFactor(); + QGeoCoordinate coordinate_; + QPointer sourceItem_; + QQuickItem *opacityContainer_; + QPointF anchorPoint_; + qreal zoomLevel_; + bool mapAndSourceItemSet_; + bool updatingGeometry_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMapQuickItem) + +#endif diff --git a/src/imports/location/qdeclarativegeomaptype.cpp b/src/imports/location/qdeclarativegeomaptype.cpp new file mode 100644 index 0000000..0b90ec3 --- /dev/null +++ b/src/imports/location/qdeclarativegeomaptype.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomaptype_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapType + \instantiates QDeclarativeGeoMapType + \inherits QObject + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapType type holds information about a map type. + + This includes the map type's \l name and \l description, the \l style and + a flag to indicate if the map type is optimized for mobile devices (\l mobile). +*/ + +QDeclarativeGeoMapType::QDeclarativeGeoMapType(const QGeoMapType mapType, QObject *parent) + : QObject(parent), + mapType_(mapType) {} + +QDeclarativeGeoMapType::~QDeclarativeGeoMapType() {} + +/*! + \qmlproperty enumeration MapType::style + + This read-only property gives access to the style of the map type. + + \list + \li MapType.NoMap - No map. + \li MapType.StreetMap - A street map. + \li MapType.SatelliteMapDay - A map with day-time satellite imagery. + \li MapType.SatelliteMapNight - A map with night-time satellite imagery. + \li MapType.TerrainMap - A terrain map. + \li MapType.HybridMap - A map with satellite imagery and street information. + \li MapType.GrayStreetMap - A gray-shaded street map. + \li MapType.PedestrianMap - A street map suitable for pedestriants. + \li MapType.CarNavigationMap - A street map suitable for car navigation. + \li MapType.CycleMap - A street map suitable for cyclists. + \li MapType.CustomMap - A custom map type. + \endlist +*/ +QDeclarativeGeoMapType::MapStyle QDeclarativeGeoMapType::style() const +{ + return QDeclarativeGeoMapType::MapStyle(mapType_.style()); +} + +/*! + \qmlproperty string MapType::name + + This read-only property holds the name of the map type as a single formatted string. +*/ +QString QDeclarativeGeoMapType::name() const +{ + return mapType_.name(); +} + +/*! + \qmlproperty string MapType::description + + This read-only property holds the description of the map type as a single formatted string. +*/ +QString QDeclarativeGeoMapType::description() const +{ + return mapType_.description(); +} + +/*! + \qmlproperty bool MapType::mobile + + \brief Whether the map type is optimized for the use on a mobile device. + + Map types for mobile devices usually have higher constrast to counteract the + effects of sunlight and a reduced color for improved readability. +*/ +bool QDeclarativeGeoMapType::mobile() const +{ + return mapType_.mobile(); +} + +/*! + \qmlproperty bool MapType::night + \since Qt Location 5.4 + + \brief Whether the map type is optimized for use at night. + + Map types suitable for use at night usually have a dark background. +*/ +bool QDeclarativeGeoMapType::night() const +{ + return mapType_.night(); +} + +#include "moc_qdeclarativegeomaptype_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeomaptype_p.h b/src/imports/location/qdeclarativegeomaptype_p.h new file mode 100644 index 0000000..980c594 --- /dev/null +++ b/src/imports/location/qdeclarativegeomaptype_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPTYPE_H +#define QDECLARATIVEGEOMAPTYPE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoMapType : public QObject +{ + Q_OBJECT + Q_ENUMS(MapStyle) + + Q_PROPERTY(MapStyle style READ style CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(bool mobile READ mobile CONSTANT) + Q_PROPERTY(bool night READ night CONSTANT REVISION 1) + +public: + enum MapStyle { + NoMap = QGeoMapType::NoMap, + StreetMap = QGeoMapType::StreetMap, + SatelliteMapDay = QGeoMapType::SatelliteMapDay, + SatelliteMapNight = QGeoMapType::SatelliteMapNight, + TerrainMap = QGeoMapType::TerrainMap, + HybridMap = QGeoMapType::HybridMap, + TransitMap = QGeoMapType::TransitMap, + GrayStreetMap = QGeoMapType::GrayStreetMap, + PedestrianMap = QGeoMapType::PedestrianMap, + CarNavigationMap = QGeoMapType::CarNavigationMap, + CycleMap = QGeoMapType::CycleMap, + CustomMap = 100 + }; + + QDeclarativeGeoMapType(const QGeoMapType mapType, QObject *parent = 0); + ~QDeclarativeGeoMapType(); + + MapStyle style() const; + QString name() const; + QString description() const; + bool mobile() const; + bool night() const; + + const QGeoMapType mapType() { return mapType_; } + +private: + QGeoMapType mapType_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMapType) + +#endif diff --git a/src/imports/location/qdeclarativegeoroute.cpp b/src/imports/location/qdeclarativegeoroute.cpp new file mode 100644 index 0000000..9fe2909 --- /dev/null +++ b/src/imports/location/qdeclarativegeoroute.cpp @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeoroute_p.h" +#include "locationvaluetypehelper_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Route + \instantiates QDeclarativeGeoRoute + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The Route type represents one geographical route. + + A Route type contains high level information about a route, such + as the length the route, the estimated travel time for the route, + and enough information to render a basic image of the route on a map. + + The QGeoRoute object also contains a list of \l RouteSegment objects which + describe subsections of the route in greater detail. + + The primary means of acquiring Route objects is \l RouteModel. + + \section1 Example + + This example shows how to display a route's maneuvers in a ListView: + + \snippet declarative/routing.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/routing.qml Route Maneuver List1 + \snippet declarative/routing.qml Route Maneuver List2 + \snippet declarative/routing.qml Route Maneuver List3 + +*/ + +QDeclarativeGeoRoute::QDeclarativeGeoRoute(QObject *parent) + : QObject(parent) +{ + this->init(); +} + +QDeclarativeGeoRoute::QDeclarativeGeoRoute(const QGeoRoute &route, QObject *parent) + : QObject(parent), + route_(route) +{ + this->init(); +} + +QDeclarativeGeoRoute::~QDeclarativeGeoRoute() {} + +void QDeclarativeGeoRoute::init() +{ + QGeoRouteSegment segment = route_.firstRouteSegment(); + while (segment.isValid()) { + QDeclarativeGeoRouteSegment *routeSegment = new QDeclarativeGeoRouteSegment(segment, this); + QQmlEngine::setContextForObject(routeSegment, QQmlEngine::contextForObject(this)); + segments_.append(routeSegment); + segment = segment.nextRouteSegment(); + } +} + +/*! + \internal +*/ +QList QDeclarativeGeoRoute::routePath() +{ + return route_.path(); +} + +/*! + \qmlproperty georectangle QtLocation::Route::bounds + + Read-only property which holds a bounding box which encompasses the entire route. + +*/ + +QGeoRectangle QDeclarativeGeoRoute::bounds() const +{ + return route_.bounds(); +} + +/*! + \qmlproperty int QtLocation::Route::travelTime + + Read-only property which holds the estimated amount of time it will take to + traverse this route, in seconds. + +*/ + +int QDeclarativeGeoRoute::travelTime() const +{ + return route_.travelTime(); +} + +/*! + \qmlproperty real QtLocation::Route::distance + + Read-only property which holds distance covered by this route, in meters. +*/ + +qreal QDeclarativeGeoRoute::distance() const +{ + return route_.distance(); +} + +/*! + \qmlproperty QJSValue QtLocation::Route::path + + Read-only property which holds the geographical coordinates of this route. + Coordinates are listed in the order in which they would be traversed by someone + traveling along this segment of the route. + + To access individual segments you can use standard list accessors: 'path.length' + indicates the number of objects and 'path[index starting from zero]' gives + the actual object. + + \sa QtPositioning::coordinate +*/ + +QJSValue QDeclarativeGeoRoute::path() const +{ + QQmlContext *context = QQmlEngine::contextForObject(parent()); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped pathArray(scope, v4->newArrayObject(route_.path().length())); + for (int i = 0; i < route_.path().length(); ++i) { + const QGeoCoordinate &c = route_.path().at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + pathArray->putIndexed(i, cv); + } + + return QJSValue(v4, pathArray.asReturnedValue()); +} + +void QDeclarativeGeoRoute::setPath(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList pathList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoCoordinate c = parseCoordinate(value.property(i), &ok); + + if (!ok || !c.isValid()) { + qmlInfo(this) << "Unsupported path type"; + return; + } + + pathList.append(c); + } + + if (route_.path() == pathList) + return; + + route_.setPath(pathList); + + emit pathChanged(); +} + +/*! + \qmlproperty list QtLocation::Route::segments + + Read-only property which holds the list of \l RouteSegment objects of this route. + + To access individual segments you can use standard list accessors: 'segments.length' + indicates the number of objects and 'segments[index starting from zero]' gives + the actual objects. + + \sa RouteSegment +*/ + +QQmlListProperty QDeclarativeGeoRoute::segments() +{ + return QQmlListProperty(this, 0, segments_append, segments_count, + segments_at, segments_clear); +} + +/*! + \internal +*/ +void QDeclarativeGeoRoute::segments_append(QQmlListProperty *prop, + QDeclarativeGeoRouteSegment *segment) +{ + static_cast(prop->object)->appendSegment(segment); +} + +/*! + \internal +*/ +int QDeclarativeGeoRoute::segments_count(QQmlListProperty *prop) +{ + return static_cast(prop->object)->segments_.count(); +} + +/*! + \internal +*/ +QDeclarativeGeoRouteSegment *QDeclarativeGeoRoute::segments_at(QQmlListProperty *prop, int index) +{ + return static_cast(prop->object)->segments_.at(index); +} + +/*! + \internal +*/ +void QDeclarativeGeoRoute::segments_clear(QQmlListProperty *prop) +{ + static_cast(prop->object)->clearSegments(); +} + +/*! + \internal +*/ +void QDeclarativeGeoRoute::appendSegment(QDeclarativeGeoRouteSegment *segment) +{ + segments_.append(segment); +} + +/*! + \internal +*/ +void QDeclarativeGeoRoute::clearSegments() +{ + segments_.clear(); +} + +#include "moc_qdeclarativegeoroute_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeoroute_p.h b/src/imports/location/qdeclarativegeoroute_p.h new file mode 100644 index 0000000..9bb4608 --- /dev/null +++ b/src/imports/location/qdeclarativegeoroute_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOROUTE_H +#define QDECLARATIVEGEOROUTE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeoroutesegment_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoRoute : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QGeoRectangle bounds READ bounds CONSTANT) + Q_PROPERTY(int travelTime READ travelTime CONSTANT) + Q_PROPERTY(qreal distance READ distance CONSTANT) + Q_PROPERTY(QJSValue path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QQmlListProperty segments READ segments CONSTANT) + +public: + explicit QDeclarativeGeoRoute(QObject *parent = 0); + QDeclarativeGeoRoute(const QGeoRoute &route, QObject *parent = 0); + ~QDeclarativeGeoRoute(); + + QGeoRectangle bounds() const; + int travelTime() const; + qreal distance() const; + + QJSValue path() const; + void setPath(const QJSValue &value); + + QQmlListProperty segments(); + + void appendSegment(QDeclarativeGeoRouteSegment *segment); + void clearSegments(); + +Q_SIGNALS: + void pathChanged(); + +private: + static void segments_append(QQmlListProperty *prop, QDeclarativeGeoRouteSegment *segment); + static int segments_count(QQmlListProperty *prop); + static QDeclarativeGeoRouteSegment *segments_at(QQmlListProperty *prop, int index); + static void segments_clear(QQmlListProperty *prop); + + void init(); + QList routePath(); + + QGeoRoute route_; + QList segments_; + friend class QDeclarativeRouteMapItem; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/qdeclarativegeoroutemodel.cpp b/src/imports/location/qdeclarativegeoroutemodel.cpp new file mode 100644 index 0000000..0c64c23 --- /dev/null +++ b/src/imports/location/qdeclarativegeoroutemodel.cpp @@ -0,0 +1,1322 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeoroutemodel_p.h" +#include "qdeclarativegeoroute_p.h" +#include "error_messages.h" +#include "locationvaluetypehelper_p.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RouteModel + \instantiates QDeclarativeGeoRouteModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The RouteModel type provides access to routes. + + The RouteModel type is used as part of a model/view grouping to retrieve + geographic routes from a backend provider. Routes include data about driving + directions between two points, walking directions with multiple waypoints, + and various other similar concepts. It functions much like other Model + types in QML (see for example \l {Models and Views in Qt Quick#ListModel}{ListModel} and + \l XmlListModel), and interacts with views such as \l MapItemView, and \l{ListView}. + + Like \l Map and \l GeocodeModel, all the data for a RouteModel to work comes + from a services plugin. This is contained in the \l{plugin} property, and + this must be set before the RouteModel can do any useful work. + + Once the plugin is set, create a \l RouteQuery with the appropriate + waypoints and other settings, and set the RouteModel's \l{query} + property. If \l autoUpdate is enabled, the update will being automatically. + Otherwise, the \l{update} method may be used. By default, autoUpdate is + disabled. + + The data stored and returned in the RouteModel consists of \l Route objects, + as a list with the role name "routeData". See the documentation for \l Route + for further details on its structure and contents. + + \section2 Example Usage + + The following snippet is two-part, showing firstly the declaration of + objects, and secondly a short piece of procedural code using it. We set + the routeModel's \l{autoUpdate} property to false, and call \l{update} once + the query is set up, to avoid useless extra requests halfway through the + set up of the query. + + \code + Plugin { + id: aPlugin + name: "osm" + } + + RouteQuery { + id: aQuery + } + + RouteModel { + id: routeModel + plugin: aPlugin + query: aQuery + autoUpdate: false + } + \endcode + + \code + { + aQuery.addWaypoint(...) + aQuery.addWaypoint(...) + aQuery.travelModes = ... + routeModel.update() + } + \endcode + +*/ + +QDeclarativeGeoRouteModel::QDeclarativeGeoRouteModel(QObject *parent) + : QAbstractListModel(parent), + complete_(false), + plugin_(0), + routeQuery_(0), + reply_(0), + autoUpdate_(false), + status_(QDeclarativeGeoRouteModel::Null), + error_(QDeclarativeGeoRouteModel::NoError) +{ +} + +QDeclarativeGeoRouteModel::~QDeclarativeGeoRouteModel() +{ + if (!routes_.empty()) { + qDeleteAll(routes_); + routes_.clear(); + } + delete reply_; +} + +/*! + \qmlproperty int QtLocation::RouteModel::count + + This property holds how many routes the model currently has. + Amongst other uses, you can use this value when accessing routes + via the QtLocation::RouteModel::get -method. +*/ + +int QDeclarativeGeoRouteModel::count() const +{ + return routes_.count(); +} + +/*! + \qmlmethod void QtLocation::RouteModel::reset() + + Resets the model. All route data is cleared, any outstanding requests + are aborted and possible errors are cleared. Model status will be set + to RouteModel.Null +*/ + +void QDeclarativeGeoRouteModel::reset() +{ + if (!routes_.isEmpty()) { + beginResetModel(); + qDeleteAll(routes_); + routes_.clear(); + emit countChanged(); + emit routesChanged(); + endResetModel(); + } + + abortRequest(); + setError(NoError, QString()); + setStatus(QDeclarativeGeoRouteModel::Null); +} + +/*! + \qmlmethod void QtLocation::RouteModel::cancel() + + Cancels any outstanding requests and clears errors. Model status will be set to either + RouteModel.Null or RouteModel.Ready. +*/ +void QDeclarativeGeoRouteModel::cancel() +{ + abortRequest(); + setError(NoError, QString()); + setStatus(routes_.isEmpty() ? Null : Ready); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::abortRequest() +{ + if (reply_) { + reply_->abort(); + reply_->deleteLater(); + reply_ = 0; + } +} + + +/*! + \qmlmethod void QtLocation::RouteModel::get(int) + + Returns the Route at given index. Use \l count property to check the + amount of routes available. The routes are indexed from zero, so the accessible range + is 0...(count - 1). + + If you access out of bounds, a zero (null object) is returned and a warning is issued. +*/ + +QDeclarativeGeoRoute *QDeclarativeGeoRouteModel::get(int index) +{ + if (index < 0 || index >= routes_.count()) { + qmlInfo(this) << QStringLiteral("Index '%1' out of range").arg(index); + return 0; + } + return routes_.at(index); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::componentComplete() +{ + complete_ = true; + if (autoUpdate_) { + update(); + } +} + +/*! + \internal +*/ +int QDeclarativeGeoRouteModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return routes_.count(); +} + +/*! + \internal +*/ +QVariant QDeclarativeGeoRouteModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + qmlInfo(this) << QStringLiteral("Error in indexing route model's data (invalid index)."); + return QVariant(); + } + + if (index.row() >= routes_.count()) { + qmlInfo(this) << QStringLiteral("Fatal error in indexing route model's data (index overflow)."); + return QVariant(); + } + + if (role == RouteRole) { + QObject *route = routes_.at(index.row()); + return QVariant::fromValue(route); + } + return QVariant(); +} + +QHash QDeclarativeGeoRouteModel::roleNames() const +{ + QHash roleNames = QAbstractListModel::roleNames(); + roleNames.insert(RouteRole, "routeData"); + return roleNames; +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (plugin_ == plugin) + return; + + reset(); // reset the model + + if (plugin_) + disconnect(plugin_, SIGNAL(localesChanged()), this, SIGNAL(measurementSystemChanged())); + if (plugin) + connect(plugin, SIGNAL(localesChanged()), this, SIGNAL(measurementSystemChanged())); + + plugin_ = plugin; + + if (complete_) + emit pluginChanged(); + + if (!plugin) + return; + + if (plugin_->isAttached()) { + pluginReady(); + } else { + connect(plugin_, SIGNAL(attached()), + this, SLOT(pluginReady())); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::pluginReady() +{ + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + QGeoRoutingManager *routingManager = serviceProvider->routingManager(); + + if (serviceProvider->error() != QGeoServiceProvider::NoError) { + QDeclarativeGeoRouteModel::RouteError newError = UnknownError; + switch (serviceProvider->error()) { + case QGeoServiceProvider::NotSupportedError: + newError = EngineNotSetError; break; + case QGeoServiceProvider::UnknownParameterError: + newError = UnknownParameterError; break; + case QGeoServiceProvider::MissingRequiredParameterError: + newError = MissingRequiredParameterError; break; + case QGeoServiceProvider::ConnectionError: + newError = CommunicationError; break; + default: + break; + } + + setError(newError, serviceProvider->errorString()); + return; + } + + if (!routingManager) { + setError(EngineNotSetError, tr("Plugin does not support routing.")); + return; + } + + connect(routingManager, SIGNAL(finished(QGeoRouteReply*)), + this, SLOT(routingFinished(QGeoRouteReply*))); + connect(routingManager, SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString)), + this, SLOT(routingError(QGeoRouteReply*,QGeoRouteReply::Error,QString))); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::queryDetailsChanged() +{ + if (autoUpdate_ && complete_) + update(); +} + +/*! + \qmlproperty Plugin QtLocation::RouteModel::plugin + + This property holds the plugin that providers the actual + routing service. Note that all plugins do not necessarily + provide routing (could for example provide only geocoding or maps). + + A valid plugin must be set before the RouteModel can perform any useful + operations. + + \sa Plugin +*/ + +QDeclarativeGeoServiceProvider *QDeclarativeGeoRouteModel::plugin() const +{ + return plugin_; +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::setQuery(QDeclarativeGeoRouteQuery *query) +{ + if (!query || query == routeQuery_) + return; + if (routeQuery_) + routeQuery_->disconnect(this); + routeQuery_ = query; + connect(query, SIGNAL(queryDetailsChanged()), this, SLOT(queryDetailsChanged())); + if (complete_) { + emit queryChanged(); + if (autoUpdate_) + update(); + } +} + +/*! + \qmlproperty RouteQuery QtLocation::RouteModel::query + + This property holds the data of the route request. + The primary data are the waypoint coordinates and possible further + preferences (means of traveling, things to avoid on route etc). +*/ + +QDeclarativeGeoRouteQuery *QDeclarativeGeoRouteModel::query() const +{ + return routeQuery_; +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::setAutoUpdate(bool autoUpdate) +{ + if (autoUpdate_ == autoUpdate) + return; + autoUpdate_ = autoUpdate; + if (complete_) + emit autoUpdateChanged(); +} + +/*! + \qmlproperty bool QtLocation::RouteModel::autoUpdate + + This property controls whether the Model automatically updates in response + to changes in its attached RouteQuery. The default value of this property + is false. + + If setting this value to 'true', note that any change at all in + the RouteQuery object set in the \l{query} property will trigger a new + request to be sent. If you are adjusting many properties of the RouteQuery + with autoUpdate enabled, this can generate large numbers of useless (and + later discarded) requests. +*/ + +bool QDeclarativeGeoRouteModel::autoUpdate() const +{ + return autoUpdate_; +} + +/*! + \qmlproperty Locale::MeasurementSystem QtLocation::RouteModel::measurementSystem + + This property holds the measurement system which will be used when calculating the route. This + property is changed when the \l {QtLocation::Plugin::locales}{Plugin::locales} property of + \l {QtLocation::RouteModel::plugin}{plugin} changes. + + If setting this property it must be set after the \l {QtLocation::RouteModel::plugin}{plugin} + property is set. +*/ +void QDeclarativeGeoRouteModel::setMeasurementSystem(QLocale::MeasurementSystem ms) +{ + if (!plugin_) + return; + + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + if (!serviceProvider) + return; + + QGeoRoutingManager *routingManager = serviceProvider->routingManager(); + if (!routingManager) + return; + + if (routingManager->measurementSystem() == ms) + return; + + routingManager->setMeasurementSystem(ms); + emit measurementSystemChanged(); +} + +QLocale::MeasurementSystem QDeclarativeGeoRouteModel::measurementSystem() const +{ + if (!plugin_) + return QLocale().measurementSystem(); + + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + if (!serviceProvider) { + if (plugin_->locales().isEmpty()) + return QLocale().measurementSystem(); + + return QLocale(plugin_->locales().first()).measurementSystem(); + } + + QGeoRoutingManager *routingManager = serviceProvider->routingManager(); + if (!routingManager) { + if (plugin_->locales().isEmpty()) + return QLocale().measurementSystem(); + + return QLocale(plugin_->locales().first()).measurementSystem(); + } + + return routingManager->measurementSystem(); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::setStatus(QDeclarativeGeoRouteModel::Status status) +{ + if (status_ == status) + return; + + status_ = status; + + if (complete_) + emit statusChanged(); +} + +/*! + \qmlproperty enumeration QtLocation::RouteModel::status + + This read-only property holds the current status of the model. + + \list + \li RouteModel.Null - No route requests have been issued or \l reset has been called. + \li RouteModel.Ready - Route request(s) have finished successfully. + \li RouteModel.Loading - Route request has been issued but not yet finished + \li RouteModel.Error - Routing error has occurred, details are in \l error and \l errorString + \endlist +*/ + +QDeclarativeGeoRouteModel::Status QDeclarativeGeoRouteModel::status() const +{ + return status_; +} + +/*! + \qmlproperty string QtLocation::RouteModel::errorString + + This read-only property holds the textual presentation of the latest routing error. + If no error has occurred or the model has been reset, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. +*/ + +QString QDeclarativeGeoRouteModel::errorString() const +{ + return errorString_; +} + +/*! + \qmlproperty enumeration QtLocation::RouteModel::error + + This read-only property holds the latest error value of the routing request. + + \list + \li RouteModel.NoError - No error has occurred. + \li RouteModel.CommunicationError - An error occurred while communicating with the service provider. + \li RouteModel.EngineNotSetError - The model's plugin property was not set or there is no routing manager associated with the plugin. + \li RouteModel.MissingRequiredParameterError - A required parameter was not specified. + \li RouteModel.ParseError - The response from the service provider was in an unrecognizable format. + \li RouteModel.UnknownError - An error occurred which does not fit into any of the other categories. + \li RouteModel.UnknownParameterError - The plugin did not recognize one of the parameters it was given. + \li RouteModel.UnsupportedOptionError - The requested operation is not supported by the routing provider. + This may happen when the loaded engine does not support a particular + type of routing request. + \endlist +*/ + +QDeclarativeGeoRouteModel::RouteError QDeclarativeGeoRouteModel::error() const +{ + return error_; +} + +void QDeclarativeGeoRouteModel::setError(RouteError error, const QString& errorString) +{ + if (error_ == error && errorString_ == errorString) + return; + error_ = error; + errorString_ = errorString; + emit errorChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteModel::update() + + Instructs the RouteModel to update its data. This is most useful + when \l autoUpdate is disabled, to force a refresh when the query + has been changed. +*/ +void QDeclarativeGeoRouteModel::update() +{ + if (!complete_) + return; + + if (!plugin_) { + setError(EngineNotSetError, tr("Cannot route, plugin not set.")); + return; + } + + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + if (!serviceProvider) + return; + + QGeoRoutingManager *routingManager = serviceProvider->routingManager(); + if (!routingManager) { + setError(EngineNotSetError, tr("Cannot route, route manager not set.")); + return; + } + if (!routeQuery_) { + setError(ParseError,"Cannot route, valid query not set."); + return; + } + abortRequest(); // Clear previus requests + QGeoRouteRequest request = routeQuery_->routeRequest(); + if (request.waypoints().count() < 2) { + setError(ParseError,tr("Not enough waypoints for routing.")); + return; + } + + setError(NoError, QString()); + + reply_ = routingManager->calculateRoute(request); + setStatus(QDeclarativeGeoRouteModel::Loading); + if (reply_->isFinished()) { + if (reply_->error() == QGeoRouteReply::NoError) { + routingFinished(reply_); + } else { + routingError(reply_, reply_->error(), reply_->errorString()); + } + } +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::routingFinished(QGeoRouteReply *reply) +{ + if (reply != reply_ || reply->error() != QGeoRouteReply::NoError) + return; + + beginResetModel(); + int oldCount = routes_.count(); + qDeleteAll(routes_); + // Convert routes to declarative + routes_.clear(); + for (int i = 0; i < reply->routes().size(); ++i) { + QDeclarativeGeoRoute *route = new QDeclarativeGeoRoute(reply->routes().at(i), this); + QQmlEngine::setContextForObject(route, QQmlEngine::contextForObject(this)); + routes_.append(route); + } + endResetModel(); + + setError(NoError, QString()); + setStatus(QDeclarativeGeoRouteModel::Ready); + + reply->deleteLater(); + reply_ = 0; + + if (oldCount != 0 || routes_.count() != 0) + emit routesChanged(); + if (oldCount != routes_.count()) + emit countChanged(); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::routingError(QGeoRouteReply *reply, + QGeoRouteReply::Error error, + const QString &errorString) +{ + if (reply != reply_) + return; + setError(static_cast(error), errorString); + setStatus(QDeclarativeGeoRouteModel::Error); + reply->deleteLater(); + reply_ = 0; +} + + +/*! + \qmltype RouteQuery + \instantiates QDeclarativeGeoRouteQuery + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The RouteQuery type is used to provide query parameters to a + RouteModel. + + A RouteQuery contains all the parameters necessary to make a request + to a routing service, which can then populate the contents of a RouteModel. + + These parameters describe key details of the route, such as \l waypoints to + pass through, \l excludedAreas to avoid, the \l travelModes in use, as well + as detailed preferences on how to optimize the route and what features + to prefer or avoid along the path (such as toll roads, highways, etc). + + RouteQuery objects are used exclusively to fill out the value of a + RouteModel's \l{RouteModel::query}{query} property, which can then begin + the retrieval process to populate the model. + + \section2 Example Usage + + The following snipped shows an incomplete example of creating a RouteQuery + object and setting it as the value of a RouteModel's \l{RouteModel::query}{query} + property. + + \code + RouteQuery { + id: aQuery + } + + RouteModel { + query: aQuery + autoUpdate: false + } + \endcode + + For a more complete example, see the documentation for the \l{RouteModel} + type, and the Mapviewer example. + + \sa RouteModel + +*/ + +QDeclarativeGeoRouteQuery::QDeclarativeGeoRouteQuery(QObject *parent) +: QObject(parent), complete_(false), m_excludedAreaCoordinateChanged(false) +{ +} + +QDeclarativeGeoRouteQuery::~QDeclarativeGeoRouteQuery() +{ +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteQuery::componentComplete() +{ + complete_ = true; +} + +/*! + \qmlproperty QList RouteQuery::featureTypes + + List of features that will be considered when planning the + route. Features with a weight of NeutralFeatureWeight will not be returned. + + \list + \li RouteQuery.NoFeature - No features will be taken into account when planning the route + \li RouteQuery.TollFeature - Consider tollways when planning the route + \li RouteQuery.HighwayFeature - Consider highways when planning the route + \li RouteQuery.PublicTransitFeature - Consider public transit when planning the route + \li RouteQuery.FerryFeature - Consider ferries when planning the route + \li RouteQuery.TunnelFeature - Consider tunnels when planning the route + \li RouteQuery.DirtRoadFeature - Consider dirt roads when planning the route + \li RouteQuery.ParksFeature - Consider parks when planning the route + \li RouteQuery.MotorPoolLaneFeature - Consider motor pool lanes when planning the route + \endlist + + \sa setFeatureWeight, featureWeight +*/ + +QList QDeclarativeGeoRouteQuery::featureTypes() +{ + QList list; + + for (int i = 0; i < request_.featureTypes().count(); ++i) { + list.append(static_cast(request_.featureTypes().at(i))); + } + return list; +} + +/*! + \qmlproperty int RouteQuery::numberAlternativeRoutes + + The number of alternative routes requested when requesting routes. + The default value is 0. +*/ + + +int QDeclarativeGeoRouteQuery::numberAlternativeRoutes() const +{ + return request_.numberAlternativeRoutes(); +} + +void QDeclarativeGeoRouteQuery::setNumberAlternativeRoutes(int numberAlternativeRoutes) +{ + if (numberAlternativeRoutes == request_.numberAlternativeRoutes()) + return; + + request_.setNumberAlternativeRoutes(numberAlternativeRoutes); + + if (complete_) { + emit numberAlternativeRoutesChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlproperty QJSValue RouteQuery::waypoints + + + The waypoint coordinates of the desired route. + The waypoints should be given in order from origin to destination. + Two or more coordinates are needed. + + Waypoints can be set as part of the RouteQuery type declaration or + dynamically with the functions provided. + + \sa addWaypoint, removeWaypoint, clearWaypoints +*/ + +QJSValue QDeclarativeGeoRouteQuery::waypoints() +{ + QQmlContext *context = QQmlEngine::contextForObject(parent()); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped waypointArray(scope, v4->newArrayObject(request_.waypoints().length())); + for (int i = 0; i < request_.waypoints().length(); ++i) { + const QGeoCoordinate &c = request_.waypoints().at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + waypointArray->putIndexed(i, cv); + } + + return QJSValue(v4, waypointArray.asReturnedValue()); +} + +void QDeclarativeGeoRouteQuery::setWaypoints(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList waypointList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoCoordinate c = parseCoordinate(value.property(i), &ok); + + if (!ok || !c.isValid()) { + qmlInfo(this) << "Unsupported waypoint type"; + return; + } + + waypointList.append(c); + } + + if (request_.waypoints() == waypointList) + return; + + request_.setWaypoints(waypointList); + + emit waypointsChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlproperty list RouteQuery::excludedAreas + + Areas that the route must not cross. + + Excluded areas can be set as part of the \l RouteQuery type declaration or + dynamically with the functions provided. + + \sa addExcludedArea, removeExcludedArea, clearExcludedAreas +*/ +QJSValue QDeclarativeGeoRouteQuery::excludedAreas() const +{ + QQmlContext *context = QQmlEngine::contextForObject(parent()); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped excludedAreasArray(scope, v4->newArrayObject(request_.excludeAreas().length())); + for (int i = 0; i < request_.excludeAreas().length(); ++i) { + const QGeoRectangle &r = request_.excludeAreas().at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(r))); + excludedAreasArray->putIndexed(i, cv); + } + + return QJSValue(v4, excludedAreasArray.asReturnedValue()); +} + +void QDeclarativeGeoRouteQuery::setExcludedAreas(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList excludedAreasList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoRectangle r = parseRectangle(value.property(i), &ok); + + if (!ok || !r.isValid()) { + qmlInfo(this) << "Unsupported area type"; + return; + } + + excludedAreasList.append(r); + } + + if (request_.excludeAreas() == excludedAreasList) + return; + + request_.setExcludeAreas(excludedAreasList); + + emit excludedAreasChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::addExcludedArea(georectangle) + + Adds the given area to excluded areas (areas that the route must not cross). + Same area can only be added once. + + \sa removeExcludedArea, clearExcludedAreas +*/ + + +void QDeclarativeGeoRouteQuery::addExcludedArea(const QGeoRectangle &area) +{ + if (!area.isValid()) + return; + + QList excludedAreas = request_.excludeAreas(); + + if (excludedAreas.contains(area)) + return; + + excludedAreas.append(area); + + request_.setExcludeAreas(excludedAreas); + + if (complete_) { + emit excludedAreasChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlmethod void QtLocation::RouteQuery::removeExcludedArea(georectangle) + + Removes the given area to excluded areas (areas that the route must not cross). + + \sa addExcludedArea, clearExcludedAreas +*/ + +void QDeclarativeGeoRouteQuery::removeExcludedArea(const QGeoRectangle &area) +{ + if (!area.isValid()) + return; + + QList excludedAreas = request_.excludeAreas(); + + int index = excludedAreas.lastIndexOf(area); + if (index == -1) { + qmlInfo(this) << QStringLiteral("Cannot remove nonexistent area."); + return; + } + excludedAreas.removeAt(index); + request_.setExcludeAreas(excludedAreas); + + emit excludedAreasChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::clearExcludedAreas() + + Clears all excluded areas (areas that the route must not cross). + + \sa addExcludedArea, removeExcludedArea +*/ + +void QDeclarativeGeoRouteQuery::clearExcludedAreas() +{ + if (request_.excludeAreas().isEmpty()) + return; + + request_.setExcludeAreas(QList()); + + emit excludedAreasChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::addWaypoint(coordinate) + + Appends a coordinate to the list of waypoints. Same coordinate + can be set multiple times. + + \sa removeWaypoint, clearWaypoints +*/ +void QDeclarativeGeoRouteQuery::addWaypoint(const QGeoCoordinate &waypoint) +{ + if (!waypoint.isValid()) { + qmlInfo(this) << QStringLiteral("Not adding invalid waypoint."); + return; + } + + QList waypoints = request_.waypoints(); + waypoints.append(waypoint); + request_.setWaypoints(waypoints); + + if (complete_) { + emit waypointsChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlmethod void QtLocation::RouteQuery::removeWaypoint(coordinate) + + Removes the given from the list of waypoints. In case same coordinate + appears multiple times, the most recently added coordinate instance is + removed. + + \sa addWaypoint, clearWaypoints +*/ +void QDeclarativeGeoRouteQuery::removeWaypoint(const QGeoCoordinate &waypoint) +{ + QList waypoints = request_.waypoints(); + + int index = waypoints.lastIndexOf(waypoint); + if (index == -1) { + qmlInfo(this) << QStringLiteral("Cannot remove nonexistent waypoint."); + return; + } + + waypoints.removeAt(index); + + request_.setWaypoints(waypoints); + + emit waypointsChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::clearWaypoints() + + Clears all waypoints. + + \sa removeWaypoint, addWaypoint +*/ +void QDeclarativeGeoRouteQuery::clearWaypoints() +{ + if (request_.waypoints().isEmpty()) + return; + + request_.setWaypoints(QList()); + + emit waypointsChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::setFeatureWeight(FeatureType, FeatureWeight) + + Defines the weight to associate with a feature during the planning of a + route. + + Following lists the possible feature weights: + + \list + \li RouteQuery.NeutralFeatureWeight - The presence or absence of the feature will not affect the planning of the route + \li RouteQuery.PreferFeatureWeight - Routes which contain the feature will be preferred over those that do not + \li RouteQuery.RequireFeatureWeight - Only routes which contain the feature will be considered, otherwise no route will be returned + \li RouteQuery.AvoidFeatureWeight - Routes which do not contain the feature will be preferred over those that do + \li RouteQuery.DisallowFeatureWeight - Only routes which do not contain the feature will be considered, otherwise no route will be returned + \endlist + + \sa featureTypes, resetFeatureWeights, featureWeight + +*/ + +void QDeclarativeGeoRouteQuery::setFeatureWeight(FeatureType featureType, FeatureWeight featureWeight) +{ + if (featureType == NoFeature && !request_.featureTypes().isEmpty()) { + resetFeatureWeights(); + return; + } + + // Check if the weight changes, as we need to signal it + FeatureWeight originalWeight = static_cast(request_.featureWeight(static_cast(featureType))); + if (featureWeight == originalWeight) + return; + + request_.setFeatureWeight(static_cast(featureType), + static_cast(featureWeight)); + if (complete_ && ((originalWeight == NeutralFeatureWeight) || (featureWeight == NeutralFeatureWeight))) { + // featureTypes should now give a different list, because the original and new weight + // were not same, and other one was neutral weight + emit featureTypesChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlmethod void QtLocation::RouteQuery::resetFeatureWeights() + + Resets all feature weights to their default state (NeutralFeatureWeight). + + \sa featureTypes, setFeatureWeight, featureWeight +*/ +void QDeclarativeGeoRouteQuery::resetFeatureWeights() +{ + // reset all feature types. + QList featureTypes = request_.featureTypes(); + for (int i = 0; i < featureTypes.count(); ++i) { + request_.setFeatureWeight(featureTypes.at(i), QGeoRouteRequest::NeutralFeatureWeight); + } + if (complete_) { + emit featureTypesChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlmethod FeatureWeight QtLocation::RouteQuery::featureWeight(FeatureType featureType) + + Gets the weight for the \a featureType. + + \sa featureTypes, setFeatureWeight, resetFeatureWeights +*/ + +int QDeclarativeGeoRouteQuery::featureWeight(FeatureType featureType) +{ + return request_.featureWeight(static_cast(featureType)); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteQuery::setTravelModes(QDeclarativeGeoRouteQuery::TravelModes travelModes) +{ + QGeoRouteRequest::TravelModes reqTravelModes; + + if (travelModes & QDeclarativeGeoRouteQuery::CarTravel) + reqTravelModes |= QGeoRouteRequest::CarTravel; + if (travelModes & QDeclarativeGeoRouteQuery::PedestrianTravel) + reqTravelModes |= QGeoRouteRequest::PedestrianTravel; + if (travelModes & QDeclarativeGeoRouteQuery::BicycleTravel) + reqTravelModes |= QGeoRouteRequest::BicycleTravel; + if (travelModes & QDeclarativeGeoRouteQuery::PublicTransitTravel) + reqTravelModes |= QGeoRouteRequest::PublicTransitTravel; + if (travelModes & QDeclarativeGeoRouteQuery::TruckTravel) + reqTravelModes |= QGeoRouteRequest::TruckTravel; + + if (reqTravelModes == request_.travelModes()) + return; + + request_.setTravelModes(reqTravelModes); + + if (complete_) { + emit travelModesChanged(); + emit queryDetailsChanged(); + } +} + + +/*! + \qmlproperty enumeration RouteQuery::segmentDetail + + The level of detail which will be used in the representation of routing segments. + + \list + \li RouteQuery.NoSegmentData - No segment data should be included with the route + \li RouteQuery.BasicSegmentData - Basic segment data will be included with the route + \endlist + + The default value is RouteQuery.BasicSegmentData +*/ + +void QDeclarativeGeoRouteQuery::setSegmentDetail(SegmentDetail segmentDetail) +{ + if (static_cast(segmentDetail) == request_.segmentDetail()) + return; + request_.setSegmentDetail(static_cast(segmentDetail)); + if (complete_) { + emit segmentDetailChanged(); + emit queryDetailsChanged(); + } +} + +QDeclarativeGeoRouteQuery::SegmentDetail QDeclarativeGeoRouteQuery::segmentDetail() const +{ + return static_cast(request_.segmentDetail()); +} + +/*! + \qmlproperty enumeration RouteQuery::maneuverDetail + + The level of detail which will be used in the representation of routing maneuvers. + + \list + \li RouteQuery.NoManeuvers - No maneuvers should be included with the route + \li RouteQuery.BasicManeuvers - Basic maneuvers will be included with the route + \endlist + + The default value is RouteQuery.BasicManeuvers +*/ + +void QDeclarativeGeoRouteQuery::setManeuverDetail(ManeuverDetail maneuverDetail) +{ + if (static_cast(maneuverDetail) == request_.maneuverDetail()) + return; + request_.setManeuverDetail(static_cast(maneuverDetail)); + if (complete_) { + emit maneuverDetailChanged(); + emit queryDetailsChanged(); + } +} + +QDeclarativeGeoRouteQuery::ManeuverDetail QDeclarativeGeoRouteQuery::maneuverDetail() const +{ + return static_cast(request_.maneuverDetail()); +} + +/*! + \qmlproperty enumeration RouteQuery::travelModes + + The travel modes which should be considered during the planning of the route. + Values can be combined with OR ('|') -operator. + + \list + \li RouteQuery.CarTravel - The route will be optimized for someone who is driving a car + \li RouteQuery.PedestrianTravel - The route will be optimized for someone who is walking + \li RouteQuery.BicycleTravel - The route will be optimized for someone who is riding a bicycle + \li RouteQuery.PublicTransitTravel - The route will be optimized for someone who is making use of public transit + \li RouteQuery.TruckTravel - The route will be optimized for someone who is driving a truck + \endlist + + The default value is RouteQuery.CarTravel +*/ + +QDeclarativeGeoRouteQuery::TravelModes QDeclarativeGeoRouteQuery::travelModes() const +{ + QGeoRouteRequest::TravelModes reqTravelModes = request_.travelModes(); + QDeclarativeGeoRouteQuery::TravelModes travelModes; + + if (reqTravelModes & QGeoRouteRequest::CarTravel) + travelModes |= QDeclarativeGeoRouteQuery::CarTravel; + if (reqTravelModes & QGeoRouteRequest::PedestrianTravel) + travelModes |= QDeclarativeGeoRouteQuery::PedestrianTravel; + if (reqTravelModes & QGeoRouteRequest::BicycleTravel) + travelModes |= QDeclarativeGeoRouteQuery::BicycleTravel; + if (reqTravelModes & QGeoRouteRequest::PublicTransitTravel) + travelModes |= QDeclarativeGeoRouteQuery::PublicTransitTravel; + if (reqTravelModes & QGeoRouteRequest::TruckTravel) + travelModes |= QDeclarativeGeoRouteQuery::TruckTravel; + + return travelModes; +} + +/*! + \qmlproperty enumeration RouteQuery::routeOptimizations + + The route optimizations which should be considered during the planning of the route. + Values can be combined with OR ('|') -operator. + + \list + \li RouteQuery.ShortestRoute - Minimize the length of the journey + \li RouteQuery.FastestRoute - Minimize the traveling time for the journey + \li RouteQuery.MostEconomicRoute - Minimize the cost of the journey + \li RouteQuery.MostScenicRoute - Maximize the scenic potential of the journey + \endlist + + The default value is RouteQuery.FastestRoute +*/ + +QDeclarativeGeoRouteQuery::RouteOptimizations QDeclarativeGeoRouteQuery::routeOptimizations() const +{ + QGeoRouteRequest::RouteOptimizations reqOptimizations = request_.routeOptimization(); + QDeclarativeGeoRouteQuery::RouteOptimizations optimization; + + if (reqOptimizations & QGeoRouteRequest::ShortestRoute) + optimization |= QDeclarativeGeoRouteQuery::ShortestRoute; + if (reqOptimizations & QGeoRouteRequest::FastestRoute) + optimization |= QDeclarativeGeoRouteQuery::FastestRoute; + if (reqOptimizations & QGeoRouteRequest::MostEconomicRoute) + optimization |= QDeclarativeGeoRouteQuery::MostEconomicRoute; + if (reqOptimizations & QGeoRouteRequest::MostScenicRoute) + optimization |= QDeclarativeGeoRouteQuery::MostScenicRoute; + + return optimization; +} + +void QDeclarativeGeoRouteQuery::setRouteOptimizations(QDeclarativeGeoRouteQuery::RouteOptimizations optimization) +{ + QGeoRouteRequest::RouteOptimizations reqOptimizations; + + if (optimization & QDeclarativeGeoRouteQuery::ShortestRoute) + reqOptimizations |= QGeoRouteRequest::ShortestRoute; + if (optimization & QDeclarativeGeoRouteQuery::FastestRoute) + reqOptimizations |= QGeoRouteRequest::FastestRoute; + if (optimization & QDeclarativeGeoRouteQuery::MostEconomicRoute) + reqOptimizations |= QGeoRouteRequest::MostEconomicRoute; + if (optimization & QDeclarativeGeoRouteQuery::MostScenicRoute) + reqOptimizations |= QGeoRouteRequest::MostScenicRoute; + + if (reqOptimizations == request_.routeOptimization()) + return; + + request_.setRouteOptimization(reqOptimizations); + + if (complete_) { + emit routeOptimizationsChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \internal +*/ +QGeoRouteRequest QDeclarativeGeoRouteQuery::routeRequest() const +{ + return request_; +} + +void QDeclarativeGeoRouteQuery::excludedAreaCoordinateChanged() +{ + if (!m_excludedAreaCoordinateChanged) { + m_excludedAreaCoordinateChanged = true; + QMetaObject::invokeMethod(this, "doCoordinateChanged", Qt::QueuedConnection); + } +} + +void QDeclarativeGeoRouteQuery::doCoordinateChanged() +{ + m_excludedAreaCoordinateChanged = false; + emit queryDetailsChanged(); +} + +#include "moc_qdeclarativegeoroutemodel_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeoroutemodel_p.h b/src/imports/location/qdeclarativegeoroutemodel_p.h new file mode 100644 index 0000000..66769ea --- /dev/null +++ b/src/imports/location/qdeclarativegeoroutemodel_p.h @@ -0,0 +1,344 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOROUTEMODEL_H +#define QDECLARATIVEGEOROUTEMODEL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeoserviceprovider_p.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoServiceProvider; +class QGeoRoutingManager; +class QDeclarativeGeoRoute; +class QDeclarativeGeoRouteQuery; + +class QDeclarativeGeoRouteModel : public QAbstractListModel, public QQmlParserStatus +{ + Q_OBJECT + Q_ENUMS(Status) + Q_ENUMS(RouteError) + + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(QDeclarativeGeoRouteQuery *query READ query WRITE setQuery NOTIFY queryChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(bool autoUpdate READ autoUpdate WRITE setAutoUpdate NOTIFY autoUpdateChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged) + Q_PROPERTY(RouteError error READ error NOTIFY errorChanged) + Q_PROPERTY(QLocale::MeasurementSystem measurementSystem READ measurementSystem WRITE setMeasurementSystem NOTIFY measurementSystemChanged) + + Q_INTERFACES(QQmlParserStatus) + +public: + enum Roles { + RouteRole = Qt::UserRole + 500 + }; + + enum Status { + Null, + Ready, + Loading, + Error + }; + + enum RouteError { + NoError = QGeoRouteReply::NoError, + EngineNotSetError = QGeoRouteReply::EngineNotSetError, + CommunicationError = QGeoRouteReply::CommunicationError, + ParseError = QGeoRouteReply::ParseError, + UnsupportedOptionError = QGeoRouteReply::UnsupportedOptionError, + UnknownError = QGeoRouteReply::UnknownError, + //we leave gap for future QGeoRouteReply errors + + //QGeoServiceProvider related errors start here + UnknownParameterError = 100, + MissingRequiredParameterError + }; + + explicit QDeclarativeGeoRouteModel(QObject *parent = 0); + ~QDeclarativeGeoRouteModel(); + + // From QQmlParserStatus + void classBegin() {} + void componentComplete(); + + // From QAbstractListModel + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + virtual QHash roleNames() const; + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + + void setQuery(QDeclarativeGeoRouteQuery *query); + QDeclarativeGeoRouteQuery *query() const; + + void setAutoUpdate(bool autoUpdate); + bool autoUpdate() const; + + void setMeasurementSystem(QLocale::MeasurementSystem ms); + QLocale::MeasurementSystem measurementSystem() const; + + Status status() const; + QString errorString() const; + RouteError error() const; + + int count() const; + Q_INVOKABLE QDeclarativeGeoRoute *get(int index); + Q_INVOKABLE void reset(); + Q_INVOKABLE void cancel(); + +Q_SIGNALS: + void countChanged(); + void pluginChanged(); + void queryChanged(); + void autoUpdateChanged(); + void statusChanged(); + void errorChanged(); //emitted also for errorString notification + void routesChanged(); + void measurementSystemChanged(); + +public Q_SLOTS: + void update(); + +private Q_SLOTS: + void routingFinished(QGeoRouteReply *reply); + void routingError(QGeoRouteReply *reply, + QGeoRouteReply::Error error, + const QString &errorString); + void queryDetailsChanged(); + void pluginReady(); + +private: + void setStatus(Status status); + void setError(RouteError error, const QString &errorString); + void abortRequest(); + + bool complete_; + + QDeclarativeGeoServiceProvider *plugin_; + QDeclarativeGeoRouteQuery *routeQuery_; + QGeoRouteReply *reply_; + + QList routes_; + bool autoUpdate_; + Status status_; + QString errorString_; + RouteError error_; +}; + +class QDeclarativeGeoRouteQuery : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_ENUMS(TravelMode) + Q_ENUMS(FeatureType) + Q_ENUMS(FeatureWeight) + Q_ENUMS(SegmentDetail) + Q_ENUMS(ManeuverDetail) + Q_ENUMS(RouteOptimization) + Q_FLAGS(RouteOptimizations) + Q_FLAGS(ManeuverDetails) + Q_FLAGS(SegmentDetails) + Q_FLAGS(TravelModes) + + Q_PROPERTY(int numberAlternativeRoutes READ numberAlternativeRoutes WRITE setNumberAlternativeRoutes NOTIFY numberAlternativeRoutesChanged) + Q_PROPERTY(TravelModes travelModes READ travelModes WRITE setTravelModes NOTIFY travelModesChanged) + Q_PROPERTY(RouteOptimizations routeOptimizations READ routeOptimizations WRITE setRouteOptimizations NOTIFY routeOptimizationsChanged) + Q_PROPERTY(SegmentDetail segmentDetail READ segmentDetail WRITE setSegmentDetail NOTIFY segmentDetailChanged) + Q_PROPERTY(ManeuverDetail maneuverDetail READ maneuverDetail WRITE setManeuverDetail NOTIFY maneuverDetailChanged) + Q_PROPERTY(QJSValue waypoints READ waypoints WRITE setWaypoints NOTIFY waypointsChanged) + Q_PROPERTY(QJSValue excludedAreas READ excludedAreas WRITE setExcludedAreas NOTIFY excludedAreasChanged) + Q_PROPERTY(QList featureTypes READ featureTypes NOTIFY featureTypesChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + + explicit QDeclarativeGeoRouteQuery(QObject *parent = 0); + ~QDeclarativeGeoRouteQuery(); + + // From QQmlParserStatus + void classBegin() {} + void componentComplete(); + + QGeoRouteRequest routeRequest() const; + + enum TravelMode { + CarTravel = QGeoRouteRequest::CarTravel, + PedestrianTravel = QGeoRouteRequest::PedestrianTravel, + BicycleTravel = QGeoRouteRequest::BicycleTravel, + PublicTransitTravel = QGeoRouteRequest::PublicTransitTravel, + TruckTravel = QGeoRouteRequest::TruckTravel + }; + Q_DECLARE_FLAGS(TravelModes, TravelMode) + + enum FeatureType { + NoFeature = QGeoRouteRequest::NoFeature, + TollFeature = QGeoRouteRequest::TollFeature, + HighwayFeature = QGeoRouteRequest::HighwayFeature, + PublicTransitFeature = QGeoRouteRequest::PublicTransitFeature, + FerryFeature = QGeoRouteRequest::FerryFeature, + TunnelFeature = QGeoRouteRequest::TunnelFeature, + DirtRoadFeature = QGeoRouteRequest::DirtRoadFeature, + ParksFeature = QGeoRouteRequest::ParksFeature, + MotorPoolLaneFeature = QGeoRouteRequest::MotorPoolLaneFeature + }; + Q_DECLARE_FLAGS(FeatureTypes, FeatureType) + + enum FeatureWeight { + NeutralFeatureWeight = QGeoRouteRequest::NeutralFeatureWeight, + PreferFeatureWeight = QGeoRouteRequest::PreferFeatureWeight, + RequireFeatureWeight = QGeoRouteRequest::RequireFeatureWeight, + AvoidFeatureWeight = QGeoRouteRequest::AvoidFeatureWeight, + DisallowFeatureWeight = QGeoRouteRequest::DisallowFeatureWeight + }; + Q_DECLARE_FLAGS(FeatureWeights, FeatureWeight) + + enum RouteOptimization { + ShortestRoute = QGeoRouteRequest::ShortestRoute, + FastestRoute = QGeoRouteRequest::FastestRoute, + MostEconomicRoute = QGeoRouteRequest::MostEconomicRoute, + MostScenicRoute = QGeoRouteRequest::MostScenicRoute + }; + Q_DECLARE_FLAGS(RouteOptimizations, RouteOptimization) + + enum SegmentDetail { + NoSegmentData = 0x0000, + BasicSegmentData = 0x0001 + }; + Q_DECLARE_FLAGS(SegmentDetails, SegmentDetail) + + enum ManeuverDetail { + NoManeuvers = 0x0000, + BasicManeuvers = 0x0001 + }; + Q_DECLARE_FLAGS(ManeuverDetails, ManeuverDetail) + + void setNumberAlternativeRoutes(int numberAlternativeRoutes); + int numberAlternativeRoutes() const; + + //QList featureTypes(); + QList featureTypes(); + + + QJSValue waypoints(); + void setWaypoints(const QJSValue &value); + + // READ functions for list properties + QJSValue excludedAreas() const; + void setExcludedAreas(const QJSValue &value); + + Q_INVOKABLE void addWaypoint(const QGeoCoordinate &waypoint); + Q_INVOKABLE void removeWaypoint(const QGeoCoordinate &waypoint); + Q_INVOKABLE void clearWaypoints(); + + Q_INVOKABLE void addExcludedArea(const QGeoRectangle &area); + Q_INVOKABLE void removeExcludedArea(const QGeoRectangle &area); + Q_INVOKABLE void clearExcludedAreas(); + + Q_INVOKABLE void setFeatureWeight(FeatureType featureType, FeatureWeight featureWeight); + Q_INVOKABLE int featureWeight(FeatureType featureType); + Q_INVOKABLE void resetFeatureWeights(); + + /* + feature weights + */ + + void setTravelModes(TravelModes travelModes); + TravelModes travelModes() const; + + void setSegmentDetail(SegmentDetail segmentDetail); + SegmentDetail segmentDetail() const; + + void setManeuverDetail(ManeuverDetail maneuverDetail); + ManeuverDetail maneuverDetail() const; + + void setRouteOptimizations(RouteOptimizations optimization); + RouteOptimizations routeOptimizations() const; + +Q_SIGNALS: + void numberAlternativeRoutesChanged(); + void travelModesChanged(); + void routeOptimizationsChanged(); + + void waypointsChanged(); + void excludedAreasChanged(); + + void featureTypesChanged(); + void maneuverDetailChanged(); + void segmentDetailChanged(); + + void queryDetailsChanged(); + +private Q_SLOTS: + void excludedAreaCoordinateChanged(); + +private: + Q_INVOKABLE void doCoordinateChanged(); + + QGeoRouteRequest request_; + bool complete_; + bool m_excludedAreaCoordinateChanged; + +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/qdeclarativegeoroutesegment.cpp b/src/imports/location/qdeclarativegeoroutesegment.cpp new file mode 100644 index 0000000..acfe244 --- /dev/null +++ b/src/imports/location/qdeclarativegeoroutesegment.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeoroutesegment_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RouteSegment + \instantiates QDeclarativeGeoRouteSegment + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The RouteSegment type represents a segment of a Route. + + A RouteSegment instance has information about the physical layout + of the route segment, the length of the route and estimated time required + to traverse the route segment and optional RouteManeuvers associated with + the end of the route segment. + + RouteSegment instances can be thought of as edges on a routing + graph, with RouteManeuver instances as optional labels attached to the + vertices of the graph. + + The primary means of acquiring Route objects is via Routes via \l RouteModel. + + \section1 Example + + The following QML snippet demonstrates how to print information about a + route segment: + + \snippet declarative/routing.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/routing.qml RouteSegment +*/ + +QDeclarativeGeoRouteSegment::QDeclarativeGeoRouteSegment(QObject *parent) + : QObject(parent) +{ + maneuver_ = new QDeclarativeGeoManeuver(this); +} + +QDeclarativeGeoRouteSegment::QDeclarativeGeoRouteSegment(const QGeoRouteSegment &segment, + QObject *parent) + : QObject(parent), + segment_(segment) +{ + maneuver_ = new QDeclarativeGeoManeuver(segment_.maneuver(), this); +} + +QDeclarativeGeoRouteSegment::~QDeclarativeGeoRouteSegment() {} + +/*! + \qmlproperty int QtLocation::RouteSegment::travelTime + + Read-only property which holds the estimated amount of time it will take to + traverse this segment, in seconds. + +*/ + +int QDeclarativeGeoRouteSegment::travelTime() const +{ + return segment_.travelTime(); +} + +/*! + \qmlproperty real QtLocation::RouteSegment::distance + + Read-only property which holds the distance covered by this segment of the route, in meters. + +*/ + +qreal QDeclarativeGeoRouteSegment::distance() const +{ + return segment_.distance(); +} + +/*! + \qmlproperty RouteManeuver QtLocation::RouteSegment::maneuver + + Read-only property which holds the maneuver for this route segment. + + Will return invalid maneuver if no information has been attached to the endpoint + of this route segment. +*/ + +QDeclarativeGeoManeuver *QDeclarativeGeoRouteSegment::maneuver() const +{ + return maneuver_; +} + +/*! + \qmlproperty QJSValue QtLocation::RouteSegment::path + + Read-only property which holds the geographical coordinates of this segment. + Coordinates are listed in the order in which they would be traversed by someone + traveling along this segment of the route. + + To access individual segments you can use standard list accessors: 'path.length' + indicates the number of objects and 'path[index starting from zero]' gives + the actual object. + + \sa QtPositioning::coordinate +*/ + +QJSValue QDeclarativeGeoRouteSegment::path() const +{ + QQmlContext *context = QQmlEngine::contextForObject(parent()); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped pathArray(scope, v4->newArrayObject(segment_.path().length())); + for (int i = 0; i < segment_.path().length(); ++i) { + const QGeoCoordinate &c = segment_.path().at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + pathArray->putIndexed(i, cv); + } + + return QJSValue(v4, pathArray.asReturnedValue()); +} + +#include "moc_qdeclarativegeoroutesegment_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativegeoroutesegment_p.h b/src/imports/location/qdeclarativegeoroutesegment_p.h new file mode 100644 index 0000000..e6cf5aa --- /dev/null +++ b/src/imports/location/qdeclarativegeoroutesegment_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOROUTESEGMENT_H +#define QDECLARATIVEGEOROUTESEGMENT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeomaneuver_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoRouteSegment : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int travelTime READ travelTime CONSTANT) + Q_PROPERTY(qreal distance READ distance CONSTANT) + Q_PROPERTY(QJSValue path READ path CONSTANT) + Q_PROPERTY(QDeclarativeGeoManeuver *maneuver READ maneuver CONSTANT) + +public: + explicit QDeclarativeGeoRouteSegment(QObject *parent = 0); + QDeclarativeGeoRouteSegment(const QGeoRouteSegment &segment, QObject *parent = 0); + ~QDeclarativeGeoRouteSegment(); + + int travelTime() const; + qreal distance() const; + QJSValue path() const; + QDeclarativeGeoManeuver *maneuver() const; + +private: + QGeoRouteSegment segment_; + QDeclarativeGeoManeuver *maneuver_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/location/qdeclarativegeoserviceprovider.cpp b/src/imports/location/qdeclarativegeoserviceprovider.cpp new file mode 100644 index 0000000..5b655f7 --- /dev/null +++ b/src/imports/location/qdeclarativegeoserviceprovider.cpp @@ -0,0 +1,822 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeoserviceprovider_p.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Plugin + \instantiates QDeclarativeGeoServiceProvider + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-common + \since Qt Location 5.5 + + \brief The Plugin type describes a Location based services plugin. + + The Plugin type is used to declaratively specify which available + GeoServices plugin should be used for various tasks in the Location API. + Plugins are used by \l Map, \l RouteModel, and \l GeocodeModel + types, as well as a variety of others. + + Plugins recognized by the system have a \l name property, a simple string + normally indicating the name of the service that the Plugin retrieves + data from. They also have a variety of features, which can be test for using the + \l {supportsRouting()}, \l {supportsGeocoding()}, \l {supportsMapping()} and + \l {supportsPlaces()} methods. + + When a Plugin object is created, it is "detached" and not associated with + any actual service plugin. Once it has received information via setting + its \l name, \l preferred, or \l required properties, it will choose an + appropriate service plugin to attach to. Plugin objects can only be + attached once; to use multiple plugins, create multiple Plugin objects. + + \section2 Example Usage + + The following snippet shows a Plugin object being created with the + \l required and \l preferred properties set. This Plugin will attach to the + first found plugin that supports both mapping and geocoding, and will + prefer plugins named "here" or "osm" to any others. + + \code + Plugin { + id: plugin + preferred: ["here", "osm"] + required: Plugin.AnyMappingFeatures | Plugin.AnyGeocodingFeatures + } + \endcode +*/ + +QDeclarativeGeoServiceProvider::QDeclarativeGeoServiceProvider(QObject *parent) +: QObject(parent), + sharedProvider_(0), + required_(new QDeclarativeGeoServiceProviderRequirements), + complete_(false), + experimental_(false) +{ + locales_.append(QLocale().name()); +} + +QDeclarativeGeoServiceProvider::~QDeclarativeGeoServiceProvider() +{ + delete required_; + delete sharedProvider_; +} + + + +/*! + \qmlproperty string Plugin::name + + This property holds the name of the plugin. Setting this property + will cause the Plugin to only attach to a plugin with exactly this + name. The value of \l required will be ignored. +*/ +void QDeclarativeGeoServiceProvider::setName(const QString &name) +{ + if (name_ == name) + return; + + name_ = name; + delete sharedProvider_; + sharedProvider_ = new QGeoServiceProvider(name_, parameterMap()); + sharedProvider_->setLocale(locales_.at(0)); + sharedProvider_->setAllowExperimental(experimental_); + + emit nameChanged(name_); + emit attached(); +} + +QString QDeclarativeGeoServiceProvider::name() const +{ + return name_; +} + + +/*! + \qmlproperty stringlist Plugin::availableServiceProviders + + This property holds a list of all available service plugins' names. This + can be used to manually enumerate the available plugins if the + control provided by \l name and \l required is not sufficient for your + needs. +*/ +QStringList QDeclarativeGeoServiceProvider::availableServiceProviders() +{ + return QGeoServiceProvider::availableServiceProviders(); +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProvider::componentComplete() +{ + complete_ = true; + if (!name_.isEmpty()) { + return; + } + + if (!prefer_.isEmpty() + || required_->mappingRequirements() != NoMappingFeatures + || required_->routingRequirements() != NoRoutingFeatures + || required_->geocodingRequirements() != NoGeocodingFeatures + || required_->placesRequirements() != NoPlacesFeatures) { + + QStringList providers = QGeoServiceProvider::availableServiceProviders(); + + /* first check any preferred plugins */ + foreach (const QString &name, prefer_) { + if (providers.contains(name)) { + // so we don't try it again later + providers.removeAll(name); + + QGeoServiceProvider sp(name, parameterMap(), experimental_); + if (required_->matches(&sp)) { + setName(name); + return; + } + } + } + + /* then try the rest */ + foreach (const QString &name, providers) { + QGeoServiceProvider sp(name, parameterMap(), experimental_); + if (required_->matches(&sp)) { + setName(name); + return; + } + } + + qmlInfo(this) << "Could not find a plugin with the required features to attach to"; + } +} + +/*! + \qmlmethod bool Plugin::supportsGeocoding(GeocodingFeatures features) + + This method returns a boolean indicating whether the specified set of \a features are supported + by the geo service provider plugin. True is returned if all specified \a features are + supported; otherwise false is returned. + + The \a features parameter can be any flag combination of: + \table + \header + \li Feature + \li Description + \row + \li Plugin.NoGeocodingFeatures + \li No geocoding features are supported. + \row + \li Plugin.OnlineGeocodingFeature + \li Online geocoding is supported. + \row + \li Plugin.OfflineGeocodingFeature + \li Offline geocoding is supported. + \row + \li Plugin.ReverseGeocodingFeature + \li Reverse geocoding is supported. + \row + \li Plugin.LocalizedGeocodingFeature + \li Supports returning geocoding results with localized addresses. + \row + \li Plugin.AnyGeocodingFeatures + \li Matches a geo service provider that provides any geocoding features. + \endtable +*/ +bool QDeclarativeGeoServiceProvider::supportsGeocoding(const GeocodingFeatures &feature) const +{ + QGeoServiceProvider *sp = sharedGeoServiceProvider(); + QGeoServiceProvider::GeocodingFeatures f = + static_cast(int(feature)); + if (f == QGeoServiceProvider::AnyGeocodingFeatures) + return (sp && (sp->geocodingFeatures() != QGeoServiceProvider::NoGeocodingFeatures)); + else + return (sp && (sp->geocodingFeatures() & f) == f); +} + +/*! + \qmlmethod bool Plugin::supportsMapping(MappingFeatures features) + + This method returns a boolean indicating whether the specified set of \a features are supported + by the geo service provider plugin. True is returned if all specified \a features are + supported; otherwise false is returned. + + The \a features parameter can be any flag combination of: + \table + \header + \li Feature + \li Description + \row + \li Plugin.NoMappingFeatures + \li No mapping features are supported. + \row + \li Plugin.OnlineMappingFeature + \li Online mapping is supported. + \row + \li Plugin.OfflineMappingFeature + \li Offline mapping is supported. + \row + \li Plugin.LocalizedMappingFeature + \li Supports returning localized map data. + \row + \li Plugin.AnyMappingFeatures + \li Matches a geo service provider that provides any mapping features. + \endtable +*/ +bool QDeclarativeGeoServiceProvider::supportsMapping(const MappingFeatures &feature) const +{ + QGeoServiceProvider *sp = sharedGeoServiceProvider(); + QGeoServiceProvider::MappingFeatures f = + static_cast(int(feature)); + if (f == QGeoServiceProvider::AnyMappingFeatures) + return (sp && (sp->mappingFeatures() != QGeoServiceProvider::NoMappingFeatures)); + else + return (sp && (sp->mappingFeatures() & f) == f); +} + +/*! + \qmlmethod bool Plugin::supportsRouting(RoutingFeatures features) + + This method returns a boolean indicating whether the specified set of \a features are supported + by the geo service provider plugin. True is returned if all specified \a features are + supported; otherwise false is returned. + + The \a features parameter can be any flag combination of: + \table + \header + \li Feature + \li Description + \row + \li Plugin.NoRoutingFeatures + \li No routing features are supported. + \row + \li Plugin.OnlineRoutingFeature + \li Online routing is supported. + \row + \li Plugin.OfflineRoutingFeature + \li Offline routing is supported. + \row + \li Plugin.LocalizedRoutingFeature + \li Supports returning routes with localized addresses and instructions. + \row + \li Plugin.RouteUpdatesFeature + \li Updating an existing route based on the current position is supported. + \row + \li Plugin.AlternativeRoutesFeature + \li Supports returning alternative routes. + \row + \li Plugin.ExcludeAreasRoutingFeature + \li Supports specifying a areas which the returned route must not cross. + \row + \li Plugin.AnyRoutingFeatures + \li Matches a geo service provider that provides any routing features. + \endtable +*/ +bool QDeclarativeGeoServiceProvider::supportsRouting(const RoutingFeatures &feature) const +{ + QGeoServiceProvider *sp = sharedGeoServiceProvider(); + QGeoServiceProvider::RoutingFeatures f = + static_cast(int(feature)); + if (f == QGeoServiceProvider::AnyRoutingFeatures) + return (sp && (sp->routingFeatures() != QGeoServiceProvider::NoRoutingFeatures)); + else + return (sp && (sp->routingFeatures() & f) == f); +} + +/*! + \qmlmethod bool Plugin::supportsPlaces(PlacesFeatures features) + + This method returns a boolean indicating whether the specified set of \a features are supported + by the geo service provider plugin. True is returned if all specified \a features are + supported; otherwise false is returned. + + The \a features parameter can be any flag combination of: + \table + \header + \li Feature + \li Description + \row + \li Plugin.NoPlacesFeatures + \li No places features are supported. + \row + \li Plugin.OnlinePlacesFeature + \li Online places is supported. + \row + \li Plugin.OfflinePlacesFeature + \li Offline places is supported. + \row + \li Plugin.SavePlaceFeature + \li Saving categories is supported. + \row + \li Plugin.RemovePlaceFeature + \li Removing or deleting places is supported. + \row + \li Plugin.PlaceRecommendationsFeature + \li Searching for recommended places similar to another place is supported. + \row + \li Plugin.SearchSuggestionsFeature + \li Search suggestions is supported. + \row + \li Plugin.LocalizedPlacesFeature + \li Supports returning localized place data. + \row + \li Plugin.NotificationsFeature + \li Notifications of place and category changes is supported. + \row + \li Plugin.PlaceMatchingFeature + \li Supports matching places from two different geo service providers. + \row + \li Plugin.AnyPlacesFeatures + \li Matches a geo service provider that provides any places features. + \endtable +*/ +bool QDeclarativeGeoServiceProvider::supportsPlaces(const PlacesFeatures &features) const +{ + QGeoServiceProvider *sp = sharedGeoServiceProvider(); + QGeoServiceProvider::PlacesFeatures f = + static_cast(int(features)); + if (f == QGeoServiceProvider::AnyPlacesFeatures) + return (sp && (sp->placesFeatures() != QGeoServiceProvider::NoPlacesFeatures)); + else + return (sp && (sp->placesFeatures() & f) == f); +} + +/*! + \qmlproperty enumeration Plugin::required + + This property contains the set of features that will be required by the + Plugin object when choosing which service plugin to attach to. If the + \l name property is set, this has no effect. + + Any of the following values or a bitwise combination of multiple values + may be set: + + \list + \li Plugin.NoFeatures + \li Plugin.GeocodingFeature + \li Plugin.ReverseGeocodingFeature + \li Plugin.RoutingFeature + \li Plugin.MappingFeature + \li Plugin.AnyPlacesFeature + \endlist +*/ +QDeclarativeGeoServiceProviderRequirements *QDeclarativeGeoServiceProvider::requirements() const +{ + return required_; +} + +/*! + \qmlproperty stringlist Plugin::preferred + + This property contains an ordered list of preferred plugin names, which + will be checked for the required features set in \l{Plugin::required}{required} + before any other available plugins are checked. +*/ +QStringList QDeclarativeGeoServiceProvider::preferred() const +{ + return prefer_; +} + +void QDeclarativeGeoServiceProvider::setPreferred(const QStringList &val) +{ + prefer_ = val; + emit preferredChanged(prefer_); +} + +/*! + \qmlproperty bool Plugin::isAttached + + This property indicates if the Plugin is attached to another Plugin. +*/ +bool QDeclarativeGeoServiceProvider::isAttached() const +{ + return (sharedProvider_ != 0); +} + +/*! + \qmlproperty bool Plugin::allowExperimental + + This property indicates if experimental plugins can be used. +*/ +bool QDeclarativeGeoServiceProvider::allowExperimental() const +{ + return experimental_; +} + +void QDeclarativeGeoServiceProvider::setAllowExperimental(bool allow) +{ + if (experimental_ == allow) + return; + + experimental_ = allow; + if (sharedProvider_) + sharedProvider_->setAllowExperimental(allow); + + emit allowExperimentalChanged(allow); +} + +/*! + \internal +*/ +QGeoServiceProvider *QDeclarativeGeoServiceProvider::sharedGeoServiceProvider() const +{ + return sharedProvider_; +} + +/*! + \qmlproperty stringlist Plugin::locales + + This property contains an ordered list of preferred plugin locales. If the first locale cannot be accommodated, then + the backend falls back to using the second, and so on. By default the locales property contains the system locale. + + The locales are specified as strings which have the format + "language[_script][_country]" or "C", where: + + \list + \li language is a lowercase, two-letter, ISO 639 language code, + \li script is a titlecase, four-letter, ISO 15924 script code, + \li country is an uppercase, two- or three-letter, ISO 3166 country code (also "419" as defined by United Nations), + \li the "C" locale is identical in behavior to English/UnitedStates as per QLocale + \endlist + + If the first specified locale cannot be accommodated, the \l {Plugin} falls back to the next and so forth. + Some \l {Plugin} backends may not support a set of locales which are rigidly defined. An arbitrary + example is that some \l {Place}'s in France could have French and English localizations, while + certain areas in America may only have the English localization available. In the above scenario, + the set of supported locales is context dependent on the search location. + + If the \l {Plugin} cannot accommodate any of the preferred locales, the manager falls + back to using a supported language that is backend specific. + + For \l {Plugin}'s that do not support locales, the locales list is always empty. + + The following code demonstrates how to set a single and multiple locales: + \snippet declarative/plugin.qml Plugin locale +*/ +QStringList QDeclarativeGeoServiceProvider::locales() const +{ + return locales_; +} + +void QDeclarativeGeoServiceProvider::setLocales(const QStringList &locales) +{ + if (locales_ == locales) + return; + + locales_ = locales; + + if (locales_.isEmpty()) + locales_.append(QLocale().name()); + + if (sharedProvider_) + sharedProvider_->setLocale(locales_.at(0)); + + emit localesChanged(); +} + +/*! + \qmlproperty list Plugin::parameters + \default + + This property holds the list of plugin parameters. +*/ +QQmlListProperty QDeclarativeGeoServiceProvider::parameters() +{ + return QQmlListProperty(this, + 0, + parameter_append, + parameter_count, + parameter_at, + parameter_clear); +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProvider::parameter_append(QQmlListProperty *prop, QDeclarativeGeoServiceProviderParameter *parameter) +{ + QDeclarativeGeoServiceProvider *p = static_cast(prop->object); + p->parameters_.append(parameter); + if (p->sharedProvider_) + p->sharedProvider_->setParameters(p->parameterMap()); +} + +/*! + \internal +*/ +int QDeclarativeGeoServiceProvider::parameter_count(QQmlListProperty *prop) +{ + return static_cast(prop->object)->parameters_.count(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProviderParameter *QDeclarativeGeoServiceProvider::parameter_at(QQmlListProperty *prop, int index) +{ + return static_cast(prop->object)->parameters_[index]; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProvider::parameter_clear(QQmlListProperty *prop) +{ + QDeclarativeGeoServiceProvider *p = static_cast(prop->object); + p->parameters_.clear(); + if (p->sharedProvider_) + p->sharedProvider_->setParameters(p->parameterMap()); +} + +/*! + \internal +*/ +QVariantMap QDeclarativeGeoServiceProvider::parameterMap() const +{ + QVariantMap map; + + for (int i = 0; i < parameters_.size(); ++i) { + QDeclarativeGeoServiceProviderParameter *parameter = parameters_.at(i); + map.insert(parameter->name(), parameter->value()); + } + + return map; +} + +/******************************************************************************* +*******************************************************************************/ + +QDeclarativeGeoServiceProviderRequirements::QDeclarativeGeoServiceProviderRequirements(QObject *parent) + : QObject(parent), + mapping_(QDeclarativeGeoServiceProvider::NoMappingFeatures), + routing_(QDeclarativeGeoServiceProvider::NoRoutingFeatures), + geocoding_(QDeclarativeGeoServiceProvider::NoGeocodingFeatures), + places_(QDeclarativeGeoServiceProvider::NoPlacesFeatures) +{ +} + +QDeclarativeGeoServiceProviderRequirements::~QDeclarativeGeoServiceProviderRequirements() +{ +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider::MappingFeatures QDeclarativeGeoServiceProviderRequirements::mappingRequirements() const +{ + return mapping_; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProviderRequirements::setMappingRequirements(const QDeclarativeGeoServiceProvider::MappingFeatures &features) +{ + if (mapping_ == features) + return; + + mapping_ = features; + emit mappingRequirementsChanged(mapping_); + emit requirementsChanged(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider::RoutingFeatures QDeclarativeGeoServiceProviderRequirements::routingRequirements() const +{ + return routing_; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProviderRequirements::setRoutingRequirements(const QDeclarativeGeoServiceProvider::RoutingFeatures &features) +{ + if (routing_ == features) + return; + + routing_ = features; + emit routingRequirementsChanged(routing_); + emit requirementsChanged(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider::GeocodingFeatures QDeclarativeGeoServiceProviderRequirements::geocodingRequirements() const +{ + return geocoding_; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProviderRequirements::setGeocodingRequirements(const QDeclarativeGeoServiceProvider::GeocodingFeatures &features) +{ + if (geocoding_ == features) + return; + + geocoding_ = features; + emit geocodingRequirementsChanged(geocoding_); + emit requirementsChanged(); +} + +/*! + \internal + + */ +QDeclarativeGeoServiceProvider::PlacesFeatures QDeclarativeGeoServiceProviderRequirements::placesRequirements() const +{ + return places_; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProviderRequirements::setPlacesRequirements(const QDeclarativeGeoServiceProvider::PlacesFeatures &features) +{ + if (places_ == features) + return; + + places_ = features; + emit placesRequirementsChanged(places_); + emit requirementsChanged(); +} + +/*! + \internal +*/ +bool QDeclarativeGeoServiceProviderRequirements::matches(const QGeoServiceProvider *provider) const +{ + QGeoServiceProvider::MappingFeatures mapping = + static_cast(int(mapping_)); + + // extra curlies here to avoid "dangling" else, which could belong to either if + // same goes for all the rest of these blocks + if (mapping == QGeoServiceProvider::AnyMappingFeatures) { + if (provider->mappingFeatures() == QGeoServiceProvider::NoMappingFeatures) + return false; + } else { + if ((provider->mappingFeatures() & mapping) != mapping) + return false; + } + + QGeoServiceProvider::RoutingFeatures routing = + static_cast(int(routing_)); + + if (routing == QGeoServiceProvider::AnyRoutingFeatures) { + if (provider->routingFeatures() == QGeoServiceProvider::NoRoutingFeatures) + return false; + } else { + if ((provider->routingFeatures() & routing) != routing) + return false; + } + + QGeoServiceProvider::GeocodingFeatures geocoding = + static_cast(int(geocoding_)); + + if (geocoding == QGeoServiceProvider::AnyGeocodingFeatures) { + if (provider->geocodingFeatures() == QGeoServiceProvider::NoGeocodingFeatures) + return false; + } else { + if ((provider->geocodingFeatures() & geocoding) != geocoding) + return false; + } + + QGeoServiceProvider::PlacesFeatures places = + static_cast(int(places_)); + + if (places == QGeoServiceProvider::AnyPlacesFeatures) { + if (provider->placesFeatures() == QGeoServiceProvider::NoPlacesFeatures) + return false; + } else { + if ((provider->placesFeatures() & places) != places) + return false; + } + + return true; +} + +/******************************************************************************* +*******************************************************************************/ + +/*! + \qmltype PluginParameter + \instantiates QDeclarativeGeoServiceProviderParameter + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-common + \since Qt Location 5.5 + + \brief The PluginParameter type describes a parameter to a \l Plugin. + + The PluginParameter object is used to provide a parameter of some kind + to a Plugin. Typically these parameters contain details like an application + token for access to a service, or a proxy server to use for network access. + + To set such a parameter, declare a PluginParameter inside a \l Plugin + object, and give it \l{name} and \l{value} properties. A list of valid + parameter names for each plugin is available from the + \l {Qt Location#Plugin References and Parameters}{plugin reference pages}. + + \section2 Example Usage + + The following example shows an instantiation of the \l {Qt Location HERE Plugin}{HERE} plugin + with a mapping API \e app_id and \e token pair specific to the application. + + \code + Plugin { + name: "here" + PluginParameter { name: "here.app_id"; value: "EXAMPLE_API_ID" } + PluginParameter { name: "here.token"; value: "EXAMPLE_TOKEN_123" } + } + \endcode +*/ + +QDeclarativeGeoServiceProviderParameter::QDeclarativeGeoServiceProviderParameter(QObject *parent) + : QObject(parent) {} + +QDeclarativeGeoServiceProviderParameter::~QDeclarativeGeoServiceProviderParameter() {} + +/*! + \qmlproperty string PluginParameter::name + + This property holds the name of the plugin parameter as a single formatted string. +*/ +void QDeclarativeGeoServiceProviderParameter::setName(const QString &name) +{ + if (name_ == name) + return; + + name_ = name; + + emit nameChanged(name_); +} + +QString QDeclarativeGeoServiceProviderParameter::name() const +{ + return name_; +} + +/*! + \qmlproperty QVariant PluginParameter::value + + This property holds the value of the plugin parameter which support different types of values (variant). +*/ +void QDeclarativeGeoServiceProviderParameter::setValue(const QVariant &value) +{ + if (value_ == value) + return; + + value_ = value; + + emit valueChanged(value_); +} + +QVariant QDeclarativeGeoServiceProviderParameter::value() const +{ + return value_; +} + +/******************************************************************************* +*******************************************************************************/ + +#include "moc_qdeclarativegeoserviceprovider_p.cpp" + +QT_END_NAMESPACE + diff --git a/src/imports/location/qdeclarativegeoserviceprovider_p.h b/src/imports/location/qdeclarativegeoserviceprovider_p.h new file mode 100644 index 0000000..f7a2ee8 --- /dev/null +++ b/src/imports/location/qdeclarativegeoserviceprovider_p.h @@ -0,0 +1,282 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEQGEOSERVICEPROVIDER_H +#define QDECLARATIVEQGEOSERVICEPROVIDER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoServiceProviderParameter : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) + +public: + explicit QDeclarativeGeoServiceProviderParameter(QObject *parent = 0); + ~QDeclarativeGeoServiceProviderParameter(); + + void setName(const QString &name); + QString name() const; + + void setValue(const QVariant &value); + QVariant value() const; + +Q_SIGNALS: + void nameChanged(const QString &name); + void valueChanged(const QVariant &value); + +private: + QString name_; + QVariant value_; +}; + +class QDeclarativeGeoServiceProviderRequirements; + +class QDeclarativeGeoServiceProvider : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_ENUMS(RoutingFeature) + Q_ENUMS(GeocodingFeature) + Q_ENUMS(MappingFeature) + Q_ENUMS(PlacesFeature) + + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QStringList availableServiceProviders READ availableServiceProviders CONSTANT) + Q_PROPERTY(QQmlListProperty parameters READ parameters) + Q_PROPERTY(QDeclarativeGeoServiceProviderRequirements *required READ requirements) + Q_PROPERTY(QStringList locales READ locales WRITE setLocales NOTIFY localesChanged) + Q_PROPERTY(QStringList preferred READ preferred WRITE setPreferred NOTIFY preferredChanged) + Q_PROPERTY(bool allowExperimental READ allowExperimental WRITE setAllowExperimental NOTIFY allowExperimentalChanged) + Q_PROPERTY(bool isAttached READ isAttached NOTIFY attached) + + Q_CLASSINFO("DefaultProperty", "parameters") + Q_INTERFACES(QQmlParserStatus) + +public: + explicit QDeclarativeGeoServiceProvider(QObject *parent = 0); + ~QDeclarativeGeoServiceProvider(); + + enum RoutingFeature { + NoRoutingFeatures = QGeoServiceProvider::NoRoutingFeatures, + OnlineRoutingFeature = QGeoServiceProvider::OnlineRoutingFeature, + OfflineRoutingFeature = QGeoServiceProvider::OfflineRoutingFeature, + LocalizedRoutingFeature = QGeoServiceProvider::LocalizedRoutingFeature, + RouteUpdatesFeature = QGeoServiceProvider::RouteUpdatesFeature, + AlternativeRoutesFeature = QGeoServiceProvider::AlternativeRoutesFeature, + ExcludeAreasRoutingFeature = QGeoServiceProvider::ExcludeAreasRoutingFeature, + AnyRoutingFeatures = QGeoServiceProvider::AnyRoutingFeatures + }; + + enum GeocodingFeature { + NoGeocodingFeatures = QGeoServiceProvider::NoGeocodingFeatures, + OnlineGeocodingFeature = QGeoServiceProvider::OnlineGeocodingFeature, + OfflineGeocodingFeature = QGeoServiceProvider::OfflineGeocodingFeature, + ReverseGeocodingFeature = QGeoServiceProvider::ReverseGeocodingFeature, + LocalizedGeocodingFeature = QGeoServiceProvider::LocalizedGeocodingFeature, + AnyGeocodingFeatures = QGeoServiceProvider::AnyGeocodingFeatures + }; + + enum MappingFeature { + NoMappingFeatures = QGeoServiceProvider::NoMappingFeatures, + OnlineMappingFeature = QGeoServiceProvider::OnlineMappingFeature, + OfflineMappingFeature = QGeoServiceProvider::OfflineMappingFeature, + LocalizedMappingFeature = QGeoServiceProvider::LocalizedMappingFeature, + AnyMappingFeatures = QGeoServiceProvider::AnyMappingFeatures + }; + + enum PlacesFeature { + NoPlacesFeatures = QGeoServiceProvider::NoPlacesFeatures, + OnlinePlacesFeature = QGeoServiceProvider::OnlinePlacesFeature, + OfflinePlacesFeature = QGeoServiceProvider::OfflinePlacesFeature, + SavePlaceFeature = QGeoServiceProvider::SavePlaceFeature, + RemovePlaceFeature = QGeoServiceProvider::RemovePlaceFeature, + SaveCategoryFeature = QGeoServiceProvider::SaveCategoryFeature, + RemoveCategoryFeature = QGeoServiceProvider::RemoveCategoryFeature, + PlaceRecommendationsFeature = QGeoServiceProvider::PlaceRecommendationsFeature, + SearchSuggestionsFeature = QGeoServiceProvider::SearchSuggestionsFeature, + LocalizedPlacesFeature = QGeoServiceProvider::LocalizedPlacesFeature, + NotificationsFeature = QGeoServiceProvider::NotificationsFeature, + PlaceMatchingFeature = QGeoServiceProvider::PlaceMatchingFeature, + AnyPlacesFeatures = QGeoServiceProvider::AnyPlacesFeatures + }; + + Q_DECLARE_FLAGS(RoutingFeatures, RoutingFeature) + Q_FLAGS(RoutingFeatures) + + Q_DECLARE_FLAGS(GeocodingFeatures, GeocodingFeature) + Q_FLAGS(GeocodingFeatures) + + Q_DECLARE_FLAGS(MappingFeatures, MappingFeature) + Q_FLAGS(MappingFeatures) + + Q_DECLARE_FLAGS(PlacesFeatures, PlacesFeature) + Q_FLAGS(PlacesFeatures) + + // From QQmlParserStatus + virtual void classBegin() {} + virtual void componentComplete(); + + void setName(const QString &name); + QString name() const; + + QQmlListProperty parameters(); + QVariantMap parameterMap() const; + + QStringList availableServiceProviders(); + + QDeclarativeGeoServiceProviderRequirements *requirements() const; + + QStringList preferred() const; + void setPreferred(const QStringList &val); + + QGeoServiceProvider *sharedGeoServiceProvider() const; + + Q_INVOKABLE bool supportsRouting(const RoutingFeatures &feature = AnyRoutingFeatures) const; + Q_INVOKABLE bool supportsGeocoding(const GeocodingFeatures &feature = AnyGeocodingFeatures) const; + Q_INVOKABLE bool supportsMapping(const MappingFeatures &feature = AnyMappingFeatures) const; + Q_INVOKABLE bool supportsPlaces(const PlacesFeatures &feature = AnyPlacesFeatures) const; + + QStringList locales() const; + void setLocales(const QStringList &locales); + + bool isAttached() const; + + void setAllowExperimental(bool allow); + bool allowExperimental() const; + +Q_SIGNALS: + void nameChanged(const QString &name); + void localesChanged(); + void attached(); + void preferredChanged(const QStringList &preferences); + void allowExperimentalChanged(bool allow); + +private: + static void parameter_append(QQmlListProperty *prop, QDeclarativeGeoServiceProviderParameter *mapObject); + static int parameter_count(QQmlListProperty *prop); + static QDeclarativeGeoServiceProviderParameter *parameter_at(QQmlListProperty *prop, int index); + static void parameter_clear(QQmlListProperty *prop); + + QGeoServiceProvider *sharedProvider_; + QString name_; + QList parameters_; + QDeclarativeGeoServiceProviderRequirements *required_; + bool complete_; + bool experimental_; + QStringList locales_; + QStringList prefer_; + Q_DISABLE_COPY(QDeclarativeGeoServiceProvider) +}; + +class QDeclarativeGeoServiceProviderRequirements : public QObject +{ + Q_OBJECT + Q_PROPERTY(QDeclarativeGeoServiceProvider::MappingFeatures mapping + READ mappingRequirements WRITE setMappingRequirements + NOTIFY mappingRequirementsChanged) + Q_PROPERTY(QDeclarativeGeoServiceProvider::RoutingFeatures routing + READ routingRequirements WRITE setRoutingRequirements + NOTIFY routingRequirementsChanged) + Q_PROPERTY(QDeclarativeGeoServiceProvider::GeocodingFeatures geocoding + READ geocodingRequirements WRITE setGeocodingRequirements + NOTIFY geocodingRequirementsChanged) + Q_PROPERTY(QDeclarativeGeoServiceProvider::PlacesFeatures places + READ placesRequirements WRITE setPlacesRequirements + NOTIFY placesRequirementsChanged) + +public: + explicit QDeclarativeGeoServiceProviderRequirements(QObject *parent = 0); + ~QDeclarativeGeoServiceProviderRequirements(); + + QDeclarativeGeoServiceProvider::MappingFeatures mappingRequirements() const; + void setMappingRequirements(const QDeclarativeGeoServiceProvider::MappingFeatures &features); + + QDeclarativeGeoServiceProvider::RoutingFeatures routingRequirements() const; + void setRoutingRequirements(const QDeclarativeGeoServiceProvider::RoutingFeatures &features); + + QDeclarativeGeoServiceProvider::GeocodingFeatures geocodingRequirements() const; + void setGeocodingRequirements(const QDeclarativeGeoServiceProvider::GeocodingFeatures &features); + + QDeclarativeGeoServiceProvider::PlacesFeatures placesRequirements() const; + void setPlacesRequirements(const QDeclarativeGeoServiceProvider::PlacesFeatures &features); + + Q_INVOKABLE bool matches(const QGeoServiceProvider *provider) const; + +Q_SIGNALS: + void mappingRequirementsChanged(const QDeclarativeGeoServiceProvider::MappingFeatures &features); + void routingRequirementsChanged(const QDeclarativeGeoServiceProvider::RoutingFeatures &features); + void geocodingRequirementsChanged(const QDeclarativeGeoServiceProvider::GeocodingFeatures &features); + void placesRequirementsChanged(const QDeclarativeGeoServiceProvider::PlacesFeatures &features); + + void requirementsChanged(); + +private: + QDeclarativeGeoServiceProvider::MappingFeatures mapping_; + QDeclarativeGeoServiceProvider::RoutingFeatures routing_; + QDeclarativeGeoServiceProvider::GeocodingFeatures geocoding_; + QDeclarativeGeoServiceProvider::PlacesFeatures places_; + +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoServiceProviderParameter) +QML_DECLARE_TYPE(QDeclarativeGeoServiceProviderRequirements) +QML_DECLARE_TYPE(QDeclarativeGeoServiceProvider) + +#endif diff --git a/src/imports/location/qdeclarativepolygonmapitem.cpp b/src/imports/location/qdeclarativepolygonmapitem.cpp new file mode 100644 index 0000000..3d98140 --- /dev/null +++ b/src/imports/location/qdeclarativepolygonmapitem.cpp @@ -0,0 +1,724 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 The Qt Company Ltd. + ** Contact: http://www.qt.io/licensing/ + ** + ** This file is part of the QtLocation module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL3$ + ** Commercial License Usage + ** Licensees holding valid commercial Qt licenses may use this file in + ** accordance with the commercial license agreement provided with the + ** Software or, alternatively, in accordance with the terms contained in + ** a written agreement between you and The Qt Company. For licensing terms + ** and conditions see http://www.qt.io/terms-conditions. For further + ** information use the contact form at http://www.qt.io/contact-us. + ** + ** GNU Lesser General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU Lesser + ** General Public License version 3 as published by the Free Software + ** Foundation and appearing in the file LICENSE.LGPLv3 included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU Lesser General Public License version 3 requirements + ** will be met: https://www.gnu.org/licenses/lgpl.html. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU + ** General Public License version 2.0 or later as published by the Free + ** Software Foundation and appearing in the file LICENSE.GPL included in + ** the packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 2.0 requirements will be + ** met: http://www.gnu.org/licenses/gpl-2.0.html. + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include "qdeclarativepolygonmapitem_p.h" +#include "qgeocameracapabilities_p.h" +#include "qlocationutils_p.h" +#include "error_messages.h" +#include "locationvaluetypehelper_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "qdoublevector2d_p.h" + +/* poly2tri triangulator includes */ +#include "../../3rdparty/clip2tri/clip2tri.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapPolygon + \instantiates QDeclarativePolygonMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapPolygon type displays a polygon on a Map + + The MapPolygon type displays a polygon on a Map, specified in terms of an ordered list of + \l {QtPositioning::coordinate}{coordinates}. For best appearance and results, polygons should be + simple (not self-intersecting). + + The \l {QtPositioning::coordinate}{coordinates} on the path cannot be directly changed after + being added to the Polygon. Instead, copy the \l path into a var, modify the copy and reassign + the copy back to the \l path. + + \code + var path = mapPolygon.path; + path[0].latitude = 5; + mapPolygon.path = path; + \endcode + + Coordinates can also be added and removed at any time using the \l addCoordinate and + \l removeCoordinate methods. + + For drawing rectangles with "straight" edges (same latitude across one + edge, same latitude across the other), the \l MapRectangle type provides + a simpler, two-point API. + + By default, the polygon is displayed as a 1 pixel black border with no + fill. To change its appearance, use the \l color, \l border.color and + \l border.width properties. + + \note Since MapPolygons are geographic items, dragging a MapPolygon + (through the use of \l MouseArea) causes its vertices to be + recalculated in the geographic coordinate space. The edges retain the + same geographic lengths (latitude and longitude differences between the + vertices), but they remain straight. Apparent stretching of the item occurs + when dragged to a different latitude. + + \section2 Performance + + MapPolygons have a rendering cost that is O(n) with respect to the number + of vertices. This means that the per frame cost of having a Polygon on the + Map grows in direct proportion to the number of points on the Polygon. There + is an additional triangulation cost (approximately O(n log n)) which is + currently paid with each frame, but in future may be paid only upon adding + or removing points. + + Like the other map objects, MapPolygon is normally drawn without a smooth + appearance. Setting the \l {Item::opacity}{opacity} property will force the object to + be blended, which decreases performance considerably depending on the hardware in use. + + \section2 Example Usage + + The following snippet shows a MapPolygon being used to display a triangle, + with three vertices near Brisbane, Australia. The triangle is filled in + green, with a 1 pixel black border. + + \code + Map { + MapPolygon { + color: 'green' + path: [ + { latitude: -27, longitude: 153.0 }, + { latitude: -27, longitude: 154.1 }, + { latitude: -28, longitude: 153.5 } + ] + } + } + \endcode + + \image api-mappolygon.png +*/ + +struct Vertex +{ + QVector2D position; +}; + +QGeoMapPolygonGeometry::QGeoMapPolygonGeometry() +: assumeSimple_(false) +{ +} + +/*! + \internal +*/ +void QGeoMapPolygonGeometry::updateSourcePoints(const QGeoMap &map, + const QList &path) +{ + if (!sourceDirty_) + return; + + double minX = -1.0; + + // build the actual path + QDoubleVector2D origin; + QDoubleVector2D lastPoint; + srcPath_ = QPainterPath(); + + double unwrapBelowX = 0; + if (preserveGeometry_ ) + unwrapBelowX = map.coordinateToItemPosition(geoLeftBound_, false).x(); + + for (int i = 0; i < path.size(); ++i) { + const QGeoCoordinate &coord = path.at(i); + + if (!coord.isValid()) + continue; + + QDoubleVector2D point = map.coordinateToItemPosition(coord, false); + + // We can get NaN if the map isn't set up correctly, or the projection + // is faulty -- probably best thing to do is abort + if (!qIsFinite(point.x()) || !qIsFinite(point.y())) + return; + + // unwrap x to preserve geometry if moved to border of map + if (preserveGeometry_ && point.x() < unwrapBelowX + && !qFuzzyCompare(point.x(), unwrapBelowX) + && !qFuzzyCompare(geoLeftBound_.longitude(), coord.longitude())) + point.setX(unwrapBelowX + geoDistanceToScreenWidth(map, geoLeftBound_, coord)); + + if (i == 0) { + origin = point; + minX = point.x(); + srcOrigin_ = coord; + srcPath_.moveTo(point.toPointF() - origin.toPointF()); + lastPoint = point; + } else { + if (point.x() <= minX) + minX = point.x(); + const QDoubleVector2D diff = (point - lastPoint); + if (diff.x() * diff.x() + diff.y() * diff.y() >= 3.0) { + srcPath_.lineTo(point.toPointF() - origin.toPointF()); + lastPoint = point; + } + } + } + + srcPath_.closeSubpath(); + + if (!assumeSimple_) + srcPath_ = srcPath_.simplified(); + + sourceBounds_ = srcPath_.boundingRect(); + geoLeftBound_ = map.itemPositionToCoordinate(QDoubleVector2D(minX, 0), false); +} + +/*! + \internal +*/ +void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map) +{ + if (!screenDirty_) + return; + + if (map.width() == 0 || map.height() == 0) { + clear(); + return; + } + + QDoubleVector2D origin = map.coordinateToItemPosition(srcOrigin_, false); + + // Create the viewport rect in the same coordinate system + // as the actual points + QRectF viewport(0, 0, map.width(), map.height()); + viewport.translate(-1 * origin.toPointF()); + + QPainterPath vpPath; + vpPath.addRect(viewport); + + QPainterPath ppi; + if (clipToViewport_) + ppi = srcPath_.intersected(vpPath); // get the clipped version of the path + else ppi = srcPath_; + + clear(); + + // a polygon requires at least 3 points; + if (ppi.elementCount() < 3) + return; + + // Intersection between the viewport and a concave polygon can create multiple polygons + // joined by a line at the viewport border, and poly2tri does not triangulate this very well + // so use the full src path if the resulting polygon is concave. + if (clipToViewport_) { + int changeInX = 0; + int changeInY = 0; + QPainterPath::Element e1 = ppi.elementAt(1); + QPainterPath::Element e = ppi.elementAt(0); + QVector2D edgeA(e1.x - e.x ,e1.y - e.y); + for (int i = 2; i <= ppi.elementCount(); ++i) { + e = ppi.elementAt(i % ppi.elementCount()); + if (e.x == e1.x && e.y == e1.y) + continue; + QVector2D edgeB(e.x - e1.x, e.y - e1.y); + if ((edgeA.x() < 0) == (edgeB.x() >= 0)) + changeInX++; + if ((edgeA.y() < 0) == (edgeB.y() >= 0)) + changeInY++; + edgeA = edgeB; + e1 = e; + } + if (changeInX > 2 || changeInY > 2) // polygon is concave + ppi = srcPath_; + } + + // translate the path into top-left-centric coordinates + QRectF bb = ppi.boundingRect(); + ppi.translate(-bb.left(), -bb.top()); + firstPointOffset_ = -1 * bb.topLeft(); + + ppi.closeSubpath(); + + screenOutline_ = ppi; + +#if 1 + std::vector> clipperPoints; + clipperPoints.push_back(std::vector()); + std::vector &curPts = clipperPoints.front(); + curPts.reserve(ppi.elementCount()); + for (int i = 0; i < ppi.elementCount(); ++i) { + const QPainterPath::Element e = ppi.elementAt(i); + if (e.isMoveTo() || i == ppi.elementCount() - 1 + || (qAbs(e.x - curPts.front().x) < 0.1 + && qAbs(e.y - curPts.front().y) < 0.1)) { + if (curPts.size() > 2) { + c2t::clip2tri *cdt = new c2t::clip2tri(); + std::vector outputTriangles; + cdt->triangulate(clipperPoints, outputTriangles, std::vector()); + for (size_t i = 0; i < outputTriangles.size(); ++i) { + screenVertices_ << QPointF(outputTriangles[i].x, outputTriangles[i].y); + } + delete cdt; + } + curPts.clear(); + curPts.reserve(ppi.elementCount() - i); + curPts.push_back( c2t::Point(e.x, e.y)); + } else if (e.isLineTo()) { + curPts.push_back( c2t::Point(e.x, e.y)); + } else { + qWarning("Unhandled element type in polygon painterpath"); + } + } +#else // Old qTriangulate()-based code. + QTriangleSet ts = qTriangulate(ppi); + qreal *vx = ts.vertices.data(); + + screenIndices_.reserve(ts.indices.size()); + screenVertices_.reserve(ts.vertices.size()); + + if (ts.indices.type() == QVertexIndexVector::UnsignedInt) { + const quint32 *ix = reinterpret_cast(ts.indices.data()); + for (int i = 0; i < (ts.indices.size()/3*3); ++i) + screenIndices_ << ix[i]; + } else { + const quint16 *ix = reinterpret_cast(ts.indices.data()); + for (int i = 0; i < (ts.indices.size()/3*3); ++i) + screenIndices_ << ix[i]; + } + for (int i = 0; i < (ts.vertices.size()/2*2); i += 2) + screenVertices_ << QPointF(vx[i], vx[i + 1]); +#endif + + screenBounds_ = ppi.boundingRect(); +} + +QDeclarativePolygonMapItem::QDeclarativePolygonMapItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), color_(Qt::transparent), dirtyMaterial_(true), + updatingGeometry_(false) +{ + setFlag(ItemHasContents, true); + QObject::connect(&border_, SIGNAL(colorChanged(QColor)), + this, SLOT(handleBorderUpdated())); + QObject::connect(&border_, SIGNAL(widthChanged(qreal)), + this, SLOT(handleBorderUpdated())); +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::handleBorderUpdated() +{ + borderGeometry_.markSourceDirty(); + polishAndUpdate(); +} + +QDeclarativePolygonMapItem::~QDeclarativePolygonMapItem() +{ +} + +/*! + \qmlpropertygroup Location::MapPolygon::border + \qmlproperty int MapPolygon::border.width + \qmlproperty color MapPolygon::border.color + + This property is part of the border property group. The border property + group holds the width and color used to draw the border of the polygon. + + The width is in pixels and is independent of the zoom level of the map. + + The default values correspond to a black border with a width of 1 pixel. + For no line, use a width of 0 or a transparent color. +*/ + +QDeclarativeMapLineProperties *QDeclarativePolygonMapItem::border() +{ + return &border_; +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (map) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); + } +} + +/*! + \qmlproperty list MapPolygon::path + + This property holds the ordered list of coordinates which + define the polygon. + + \sa addCoordinate, removeCoordinate +*/ +QJSValue QDeclarativePolygonMapItem::path() const +{ + QQmlContext *context = QQmlEngine::contextForObject(parent()); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped pathArray(scope, v4->newArrayObject(path_.length())); + for (int i = 0; i < path_.length(); ++i) { + const QGeoCoordinate &c = path_.at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + pathArray->putIndexed(i, cv); + } + + return QJSValue(v4, pathArray.asReturnedValue()); +} + +void QDeclarativePolygonMapItem::setPath(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList pathList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoCoordinate c = parseCoordinate(value.property(i), &ok); + + if (!ok || !c.isValid()) { + qmlInfo(this) << "Unsupported path type"; + return; + } + + pathList.append(c); + } + + if (path_ == pathList) + return; + + path_ = pathList; + geoLeftBound_ = QDeclarativePolylineMapItem::getLeftBound(path_, deltaXs_, minX_); + geometry_.setPreserveGeometry(true, geoLeftBound_); + borderGeometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod void MapPolygon::addCoordinate(coordinate) + + Adds a coordinate to the path. + + \sa removeCoordinate, path +*/ + +void QDeclarativePolygonMapItem::addCoordinate(const QGeoCoordinate &coordinate) +{ + path_.append(coordinate); + geoLeftBound_ = QDeclarativePolylineMapItem::getLeftBound(path_, deltaXs_, minX_, geoLeftBound_); + geometry_.setPreserveGeometry(true, geoLeftBound_); + borderGeometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod void MapPolygon::removeCoordinate(coordinate) + + Removes \a coordinate from the path. If there are multiple instances of the + same coordinate, the one added last is removed. + + If \a coordinate is not in the path this method does nothing. + + \sa addCoordinate, path +*/ +void QDeclarativePolygonMapItem::removeCoordinate(const QGeoCoordinate &coordinate) +{ + int index = path_.lastIndexOf(coordinate); + if (index == -1) + return; + + path_.removeAt(index); + geoLeftBound_ = QDeclarativePolylineMapItem::getLeftBound(path_, deltaXs_, minX_); + geometry_.setPreserveGeometry(true, geoLeftBound_); + borderGeometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlproperty color MapPolygon::color + + This property holds the color used to fill the polygon. + + The default value is transparent. +*/ + +QColor QDeclarativePolygonMapItem::color() const +{ + return color_; +} + +void QDeclarativePolygonMapItem::setColor(const QColor &color) +{ + if (color_ == color) + return; + + color_ = color; + dirtyMaterial_ = true; + update(); + emit colorChanged(color_); +} + +/*! + \internal +*/ +QSGNode *QDeclarativePolygonMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + MapPolygonNode *node = static_cast(oldNode); + + if (!node) + node = new MapPolygonNode(); + + //TODO: update only material + if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { + node->update(color_, border_.color(), &geometry_, &borderGeometry_); + geometry_.setPreserveGeometry(false); + borderGeometry_.setPreserveGeometry(false); + geometry_.markClean(); + borderGeometry_.markClean(); + dirtyMaterial_ = false; + } + return node; +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::updatePolish() +{ + if (!map() || path_.count() == 0) + return; + + QScopedValueRollback rollback(updatingGeometry_); + updatingGeometry_ = true; + + geometry_.updateSourcePoints(*map(), path_); + geometry_.updateScreenPoints(*map()); + + QList closedPath = path_; + closedPath << closedPath.first(); + borderGeometry_.clear(); + borderGeometry_.updateSourcePoints(*map(), closedPath, geoLeftBound_); + + if (border_.color() != Qt::transparent && border_.width() > 0) + borderGeometry_.updateScreenPoints(*map(), border_.width()); + + QList geoms; + geoms << &geometry_ << &borderGeometry_; + QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); + + setWidth(combined.width()); + setHeight(combined.height()); + + setPositionOnMap(path_.at(0), -1 * geometry_.sourceBoundingBox().topLeft()); +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + return; + + // if the scene is tilted, we must regenerate our geometry every frame + if (map()->cameraCapabilities().supportsTilting() + && (event.cameraData.tilt() > 0.1 + || event.cameraData.tilt() < -0.1)) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + + // if the scene is rolled, we must regen too + if (map()->cameraCapabilities().supportsRolling() + && (event.cameraData.roll() > 0.1 + || event.cameraData.roll() < -0.1)) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + + // otherwise, only regen on rotate, resize and zoom + if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + geometry_.setPreserveGeometry(true, geometry_.geoLeftBound()); + borderGeometry_.setPreserveGeometry(true, borderGeometry_.geoLeftBound()); + geometry_.markScreenDirty(); + borderGeometry_.markScreenDirty(); + polishAndUpdate(); +} + +/*! + \internal +*/ +bool QDeclarativePolygonMapItem::contains(const QPointF &point) const +{ + return (geometry_.contains(point) || borderGeometry_.contains(point)); +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + + QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(geometry_.firstPointOffset()); + QGeoCoordinate newCoordinate = map()->itemPositionToCoordinate(newPoint, false); + if (newCoordinate.isValid()) { + double firstLongitude = path_.at(0).longitude(); + double firstLatitude = path_.at(0).latitude(); + double minMaxLatitude = firstLatitude; + // prevent dragging over valid min and max latitudes + for (int i = 0; i < path_.count(); ++i) { + double newLatitude = path_.at(i).latitude() + + newCoordinate.latitude() - firstLatitude; + if (!QLocationUtils::isValidLat(newLatitude)) { + if (qAbs(newLatitude) > qAbs(minMaxLatitude)) { + minMaxLatitude = newLatitude; + } + } + } + // calculate offset needed to re-position the item within map border + double offsetLatitude = minMaxLatitude - QLocationUtils::clipLat(minMaxLatitude); + for (int i = 0; i < path_.count(); ++i) { + QGeoCoordinate coord = path_.at(i); + // handle dateline crossing + coord.setLongitude(QLocationUtils::wrapLong(coord.longitude() + + newCoordinate.longitude() - firstLongitude)); + coord.setLatitude(coord.latitude() + + newCoordinate.latitude() - firstLatitude - offsetLatitude); + + path_.replace(i, coord); + } + geoLeftBound_.setLongitude(QLocationUtils::wrapLong(geoLeftBound_.longitude() + + newCoordinate.longitude() - firstLongitude)); + geometry_.setPreserveGeometry(true, geoLeftBound_); + borderGeometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); + } + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +////////////////////////////////////////////////////////////////////// + +MapPolygonNode::MapPolygonNode() : + border_(new MapPolylineNode()), + geometry_(QSGGeometry::defaultAttributes_Point2D(), 0), + blocked_(true) +{ + geometry_.setDrawingMode(GL_TRIANGLES); + QSGGeometryNode::setMaterial(&fill_material_); + QSGGeometryNode::setGeometry(&geometry_); + + appendChildNode(border_); +} + +MapPolygonNode::~MapPolygonNode() +{ +} + +/*! + \internal +*/ +bool MapPolygonNode::isSubtreeBlocked() const +{ + return blocked_; +} + +/*! + \internal +*/ +void MapPolygonNode::update(const QColor &fillColor, const QColor &borderColor, + const QGeoMapItemGeometry *fillShape, + const QGeoMapItemGeometry *borderShape) +{ + /* Do the border update first */ + border_->update(borderColor, borderShape); + + /* If we have neither fill nor border with valid points, block the whole + * tree. We can't just block the fill without blocking the border too, so + * we're a little conservative here (maybe at the expense of rendering + * accuracy) */ + if (fillShape->size() == 0) { + if (borderShape->size() == 0) { + blocked_ = true; + return; + } else { + blocked_ = false; + } + } else { + blocked_ = false; + } + + QSGGeometry *fill = QSGGeometryNode::geometry(); + fillShape->allocateAndFill(fill); + markDirty(DirtyGeometry); + + if (fillColor != fill_material_.color()) { + fill_material_.setColor(fillColor); + setMaterial(&fill_material_); + markDirty(DirtyMaterial); + } +} +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativepolygonmapitem_p.h b/src/imports/location/qdeclarativepolygonmapitem_p.h new file mode 100644 index 0000000..19ac508 --- /dev/null +++ b/src/imports/location/qdeclarativepolygonmapitem_p.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPOLYGONMAPITEM +#define QDECLARATIVEPOLYGONMAPITEM + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeomapitembase_p.h" +#include "qdeclarativepolylinemapitem_p.h" +#include "qgeomapitemgeometry_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class MapPolygonNode; + +class QGeoMapPolygonGeometry : public QGeoMapItemGeometry +{ +public: + QGeoMapPolygonGeometry(); + + inline void setAssumeSimple(bool value) { assumeSimple_ = value; } + + void updateSourcePoints(const QGeoMap &map, + const QList &path); + + void updateScreenPoints(const QGeoMap &map); + +protected: + QPainterPath srcPath_; + bool assumeSimple_; +}; + +class QDeclarativePolygonMapItem : public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + + Q_PROPERTY(QJSValue path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QDeclarativeMapLineProperties *border READ border CONSTANT) + +public: + explicit QDeclarativePolygonMapItem(QQuickItem *parent = 0); + ~QDeclarativePolygonMapItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + //from QuickItem + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + + Q_INVOKABLE void addCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void removeCoordinate(const QGeoCoordinate &coordinate); + + QJSValue path() const; + void setPath(const QJSValue &value); + + QColor color() const; + void setColor(const QColor &color); + + QDeclarativeMapLineProperties *border(); + + bool contains(const QPointF &point) const Q_DECL_OVERRIDE; + +Q_SIGNALS: + void pathChanged(); + void colorChanged(const QColor &color); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void updatePolish() Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void handleBorderUpdated(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + void pathPropertyChanged(); + + QDeclarativeMapLineProperties border_; + QList path_; + QGeoCoordinate geoLeftBound_; + QColor color_; + bool dirtyMaterial_; + QGeoMapPolygonGeometry geometry_; + QGeoMapPolylineGeometry borderGeometry_; + bool updatingGeometry_; + // for the left bound calculation + QVector deltaXs_; // longitude deltas from path_[0] + double minX_; // minimum value inside deltaXs_ +}; + +////////////////////////////////////////////////////////////////////// + +class MapPolygonNode : public QSGGeometryNode +{ + +public: + MapPolygonNode(); + ~MapPolygonNode(); + + void update(const QColor &fillColor, const QColor &borderColor, + const QGeoMapItemGeometry *fillShape, + const QGeoMapItemGeometry *borderShape); + + bool isSubtreeBlocked() const; + +private: + QSGFlatColorMaterial fill_material_; + MapPolylineNode *border_; + QSGGeometry geometry_; + bool blocked_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativePolygonMapItem) + +#endif /* QDECLARATIVEPOLYGONMAPITEM_H_ */ diff --git a/src/imports/location/qdeclarativepolylinemapitem.cpp b/src/imports/location/qdeclarativepolylinemapitem.cpp new file mode 100644 index 0000000..3c29ad2 --- /dev/null +++ b/src/imports/location/qdeclarativepolylinemapitem.cpp @@ -0,0 +1,1013 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 The Qt Company Ltd. + ** Contact: http://www.qt.io/licensing/ + ** + ** This file is part of the QtLocation module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL3$ + ** Commercial License Usage + ** Licensees holding valid commercial Qt licenses may use this file in + ** accordance with the commercial license agreement provided with the + ** Software or, alternatively, in accordance with the terms contained in + ** a written agreement between you and The Qt Company. For licensing terms + ** and conditions see http://www.qt.io/terms-conditions. For further + ** information use the contact form at http://www.qt.io/contact-us. + ** + ** GNU Lesser General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU Lesser + ** General Public License version 3 as published by the Free Software + ** Foundation and appearing in the file LICENSE.LGPLv3 included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU Lesser General Public License version 3 requirements + ** will be met: https://www.gnu.org/licenses/lgpl.html. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU + ** General Public License version 2.0 or later as published by the Free + ** Software Foundation and appearing in the file LICENSE.GPL included in + ** the packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 2.0 requirements will be + ** met: http://www.gnu.org/licenses/gpl-2.0.html. + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include "qdeclarativepolylinemapitem_p.h" +#include "qgeocameracapabilities_p.h" +#include "qlocationutils_p.h" +#include "error_messages.h" +#include "locationvaluetypehelper_p.h" +#include "qdoublevector2d_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapPolyline + \instantiates QDeclarativePolylineMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 + + \brief The MapPolyline type displays a polyline on a map. + + The MapPolyline type displays a polyline on a map, specified in terms of an ordered list of + \l {coordinate}{coordinates}. The \l {coordinate}{coordinates} on + the path cannot be directly changed after being added to the Polyline. Instead, copy the + \l path into a var, modify the copy and reassign the copy back to the \l path. + + \code + var path = mapPolyline.path; + path[0].latitude = 5; + mapPolyline.path = path; + \endcode + + Coordinates can also be added and removed at any time using the \l addCoordinate and + \l removeCoordinate methods. + + By default, the polyline is displayed as a 1-pixel thick black line. This + can be changed using the \l line.width and \l line.color properties. + + \section2 Performance + + MapPolylines have a rendering cost that is O(n) with respect to the number + of vertices. This means that the per frame cost of having a polyline on + the Map grows in direct proportion to the number of points in the polyline. + + Like the other map objects, MapPolyline is normally drawn without a smooth + appearance. Setting the \l {Item::opacity}{opacity} property will force the object to + be blended, which decreases performance considerably depending on the hardware in use. + + \note MapPolylines are implemented using the OpenGL GL_LINES + primitive. There have been occasional reports of issues and rendering + inconsistencies on some (particularly quite old) platforms. No workaround + is yet available for these issues. + + \section2 Example Usage + + The following snippet shows a MapPolyline with 4 points, making a shape + like the top part of a "question mark" (?), near Brisbane, Australia. + The line drawn is 3 pixels in width and green in color. + + \code + Map { + MapPolyline { + line.width: 3 + line.color: 'green' + path: [ + { latitude: -27, longitude: 153.0 }, + { latitude: -27, longitude: 154.1 }, + { latitude: -28, longitude: 153.5 }, + { latitude: -29, longitude: 153.5 } + ] + } + } + \endcode + + \image api-mappolyline.png +*/ + +QDeclarativeMapLineProperties::QDeclarativeMapLineProperties(QObject *parent) : + QObject(parent), + width_(1.0), + color_(Qt::black) +{ +} + +/*! + \internal +*/ +QColor QDeclarativeMapLineProperties::color() const +{ + return color_; +} + +/*! + \internal +*/ +void QDeclarativeMapLineProperties::setColor(const QColor &color) +{ + if (color_ == color) + return; + + color_ = color; + emit colorChanged(color_); +} + +/*! + \internal +*/ +qreal QDeclarativeMapLineProperties::width() const +{ + return width_; +} + +/*! + \internal +*/ +void QDeclarativeMapLineProperties::setWidth(qreal width) +{ + if (width_ == width) + return; + + width_ = width; + emit widthChanged(width_); +} + +struct Vertex +{ + QVector2D position; +}; + +QGeoMapPolylineGeometry::QGeoMapPolylineGeometry() +{ +} + +/*! + \internal +*/ +void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map, + const QList &path, + const QGeoCoordinate geoLeftBound) +{ + bool foundValid = false; + double minX = -1.0; + double minY = -1.0; + double maxX = -1.0; + double maxY = -1.0; + + if (!sourceDirty_) + return; + + geoLeftBound_ = geoLeftBound; + + // clear the old data and reserve enough memory + srcPoints_.clear(); + srcPoints_.reserve(path.size() * 2); + srcPointTypes_.clear(); + srcPointTypes_.reserve(path.size()); + + QDoubleVector2D origin, lastPoint, lastAddedPoint; + + const double mapWidthHalf = map.width()/2.0; + double unwrapBelowX = 0; + if (preserveGeometry_) + unwrapBelowX = map.coordinateToItemPosition(geoLeftBound_, false).x(); + + for (int i = 0; i < path.size(); ++i) { + const QGeoCoordinate &coord = path.at(i); + + if (!coord.isValid()) + continue; + + QDoubleVector2D point = map.coordinateToItemPosition(coord, false); + + // We can get NaN if the map isn't set up correctly, or the projection + // is faulty -- probably best thing to do is abort + if (!qIsFinite(point.x()) || !qIsFinite(point.y())) + return; + + bool isPointLessThanUnwrapBelowX = (point.x() < unwrapBelowX); + bool isCoordNotLeftBound = !qFuzzyCompare(geoLeftBound_.longitude(), coord.longitude()); + bool isPointNotUnwrapBelowX = !qFuzzyCompare(point.x(), unwrapBelowX); + bool isPointNotMapWidthHalf = !qFuzzyCompare(mapWidthHalf, point.x()); + + // unwrap x to preserve geometry if moved to border of map + if (preserveGeometry_ && isPointLessThanUnwrapBelowX + && isCoordNotLeftBound + && isPointNotUnwrapBelowX + && isPointNotMapWidthHalf) { + double distance = geoDistanceToScreenWidth(map, geoLeftBound_, coord); + point.setX(unwrapBelowX + distance); + } + + if (!foundValid) { + foundValid = true; + srcOrigin_ = coord; // TODO: Make this consistent with the left bound + origin = point; + point = QDoubleVector2D(0,0); + + minX = point.x(); + maxX = minX; + minY = point.y(); + maxY = minY; + + srcPoints_ << point.x() << point.y(); + srcPointTypes_ << QPainterPath::MoveToElement; + lastAddedPoint = point; + } else { + point -= origin; + + minX = qMin(point.x(), minX); + minY = qMin(point.y(), minY); + maxX = qMax(point.x(), maxX); + maxY = qMax(point.y(), maxY); + + if ((point - lastAddedPoint).manhattanLength() > 3 || + i == path.size() - 1) { + srcPoints_ << point.x() << point.y(); + srcPointTypes_ << QPainterPath::LineToElement; + lastAddedPoint = point; + } + } + + lastPoint = point; + } + + sourceBounds_ = QRectF(QPointF(minX, minY), QPointF(maxX, maxY)); +} + +//////////////////////////////////////////////////////////////////////////// +/* Polyline clip */ + +enum ClipPointType { + InsidePoint = 0x00, + LeftPoint = 0x01, + RightPoint = 0x02, + BottomPoint = 0x04, + TopPoint = 0x08 +}; + +static inline int clipPointType(qreal x, qreal y, const QRectF &rect) +{ + int type = InsidePoint; + if (x < rect.left()) + type |= LeftPoint; + else if (x > rect.right()) + type |= RightPoint; + if (y < rect.top()) + type |= TopPoint; + else if (y > rect.bottom()) + type |= BottomPoint; + return type; +} + +static void clipSegmentToRect(qreal x0, qreal y0, qreal x1, qreal y1, + const QRectF &clipRect, + QVector &outPoints, + QVector &outTypes) +{ + int type0 = clipPointType(x0, y0, clipRect); + int type1 = clipPointType(x1, y1, clipRect); + bool accept = false; + + while (true) { + if (!(type0 | type1)) { + accept = true; + break; + } else if (type0 & type1) { + break; + } else { + qreal x = 0.0; + qreal y = 0.0; + int outsideType = type0 ? type0 : type1; + + if (outsideType & BottomPoint) { + x = x0 + (x1 - x0) * (clipRect.bottom() - y0) / (y1 - y0); + y = clipRect.bottom() - 0.1; + } else if (outsideType & TopPoint) { + x = x0 + (x1 - x0) * (clipRect.top() - y0) / (y1 - y0); + y = clipRect.top() + 0.1; + } else if (outsideType & RightPoint) { + y = y0 + (y1 - y0) * (clipRect.right() - x0) / (x1 - x0); + x = clipRect.right() - 0.1; + } else if (outsideType & LeftPoint) { + y = y0 + (y1 - y0) * (clipRect.left() - x0) / (x1 - x0); + x = clipRect.left() + 0.1; + } + + if (outsideType == type0) { + x0 = x; + y0 = y; + type0 = clipPointType(x0, y0, clipRect); + } else { + x1 = x; + y1 = y; + type1 = clipPointType(x1, y1, clipRect); + } + } + } + + if (accept) { + if (outPoints.size() >= 2) { + qreal lastX, lastY; + lastY = outPoints.at(outPoints.size() - 1); + lastX = outPoints.at(outPoints.size() - 2); + + if (!qFuzzyCompare(lastY, y0) || !qFuzzyCompare(lastX, x0)) { + outTypes << QPainterPath::MoveToElement; + outPoints << x0 << y0; + } + } else { + outTypes << QPainterPath::MoveToElement; + outPoints << x0 << y0; + } + + outTypes << QPainterPath::LineToElement; + outPoints << x1 << y1; + } +} + +static void clipPathToRect(const QVector &points, + const QVector &types, + const QRectF &clipRect, + QVector &outPoints, + QVector &outTypes) +{ + outPoints.clear(); + outPoints.reserve(points.size()); + outTypes.clear(); + outTypes.reserve(types.size()); + + qreal lastX, lastY; + for (int i = 0; i < types.size(); ++i) { + if (i > 0 && types[i] != QPainterPath::MoveToElement) { + qreal x = points[i * 2], y = points[i * 2 + 1]; + clipSegmentToRect(lastX, lastY, x, y, clipRect, outPoints, outTypes); + } + + lastX = points[i * 2]; + lastY = points[i * 2 + 1]; + } +} + +/*! + \internal +*/ +void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map, + qreal strokeWidth) +{ + if (!screenDirty_) + return; + + QPointF origin = map.coordinateToItemPosition(srcOrigin_, false).toPointF(); + + if (!qIsFinite(origin.x()) || !qIsFinite(origin.y())) { + clear(); + return; + } + + // Create the viewport rect in the same coordinate system + // as the actual points + QRectF viewport(0, 0, map.width(), map.height()); + viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth, strokeWidth); + viewport.translate(-1 * origin); + + // Perform clipping to the viewport limits + QVector points; + QVector types; + + if (clipToViewport_) { + clipPathToRect(srcPoints_, srcPointTypes_, viewport, points, types); + } else { + points = srcPoints_; + types = srcPointTypes_; + } + + QVectorPath vp(points.data(), types.size(), types.data()); + QTriangulatingStroker ts; + ts.process(vp, QPen(QBrush(Qt::black), strokeWidth), viewport, QPainter::Qt4CompatiblePainting); + + clear(); + + // Nothing is on the screen + if (ts.vertexCount() == 0) + return; + + // QTriangulatingStroker#vertexCount is actually the length of the array, + // not the number of vertices + screenVertices_.reserve(ts.vertexCount()); + + QRectF bb; + + QPointF pt; + const float *vs = ts.vertices(); + for (int i = 0; i < (ts.vertexCount()/2*2); i += 2) { + pt = QPointF(vs[i], vs[i + 1]); + screenVertices_ << pt; + + if (!qIsFinite(pt.x()) || !qIsFinite(pt.y())) + break; + + if (!bb.contains(pt)) { + if (pt.x() < bb.left()) + bb.setLeft(pt.x()); + + if (pt.x() > bb.right()) + bb.setRight(pt.x()); + + if (pt.y() < bb.top()) + bb.setTop(pt.y()); + + if (pt.y() > bb.bottom()) + bb.setBottom(pt.y()); + } + } + + screenBounds_ = bb; + this->translate( -1 * sourceBounds_.topLeft()); +} + +QDeclarativePolylineMapItem::QDeclarativePolylineMapItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), dirtyMaterial_(true), updatingGeometry_(false) +{ + setFlag(ItemHasContents, true); + QObject::connect(&line_, SIGNAL(colorChanged(QColor)), + this, SLOT(updateAfterLinePropertiesChanged())); + QObject::connect(&line_, SIGNAL(widthChanged(qreal)), + this, SLOT(updateAfterLinePropertiesChanged())); +} + +QDeclarativePolylineMapItem::~QDeclarativePolylineMapItem() +{ +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged() +{ + // mark dirty just in case we're a width change + geometry_.markSourceDirty(); + polishAndUpdate(); +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (map) { + geometry_.markSourceDirty(); + polishAndUpdate(); + } +} + +/*! + \qmlproperty list MapPolyline::path + + This property holds the ordered list of coordinates which + define the polyline. +*/ + +QJSValue QDeclarativePolylineMapItem::path() const +{ + QQmlContext *context = QQmlEngine::contextForObject(this); + if (!context) + return QJSValue(); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped pathArray(scope, v4->newArrayObject(path_.length())); + for (int i = 0; i < path_.length(); ++i) { + const QGeoCoordinate &c = path_.at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + pathArray->putIndexed(i, cv); + } + + return QJSValue(v4, pathArray.asReturnedValue()); +} + +void QDeclarativePolylineMapItem::setPath(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList pathList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoCoordinate c = parseCoordinate(value.property(i), &ok); + + if (!ok || !c.isValid()) { + qmlInfo(this) << "Unsupported path type"; + return; + } + + pathList.append(c); + } + + setPathFromGeoList(pathList); +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::setPathFromGeoList(const QList &path) +{ + if (path_ == path) + return; + + path_ = path; + geoLeftBound_ = getLeftBound(path_, deltaXs_, minX_); + geometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod int MapPolyline::pathLength() + + Returns the number of coordinates of the polyline. + + \since Qt Location 5.6 + + \sa path +*/ +int QDeclarativePolylineMapItem::pathLength() const +{ + return path_.size(); +} + +/*! + \qmlmethod void MapPolyline::addCoordinate(coordinate) + + Adds a coordinate to the end of the path. + + \sa insertCoordinate, removeCoordinate, path +*/ +void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate) +{ + path_.append(coordinate); + geoLeftBound_ = getLeftBound(path_, deltaXs_, minX_, geoLeftBound_); + geometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod void MapPolyline::insertCoordinate(index, coordinate) + + Inserts a \a coordinate to the path at the given \a index. + + \since Qt Location 5.6 + + \sa addCoordinate, removeCoordinate, path +*/ +void QDeclarativePolylineMapItem::insertCoordinate(int index, const QGeoCoordinate &coordinate) +{ + if (index < 0 || index > path_.size()) + return; + + path_.insert(index, coordinate); + geoLeftBound_ = getLeftBound(path_, deltaXs_, minX_); + geometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod void MapPolyline::replaceCoordinate(index, coordinate) + + Replaces the coordinate in the current path at the given \a index + with the new \a coordinate. + + \since Qt Location 5.6 + + \sa addCoordinate, insertCoordinate, removeCoordinate, path +*/ +void QDeclarativePolylineMapItem::replaceCoordinate(int index, const QGeoCoordinate &coordinate) +{ + if (index < 0 || index >= path_.size()) + return; + + path_[index] = coordinate; + geoLeftBound_ = getLeftBound(path_, deltaXs_, minX_); + geometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod coordinate MapPolyline::coordinateAt(index) + + Gets the coordinate of the polyline at the given \a index. + If the index is outside the path's bounds then an invalid + coordinate is returned. + + \since Qt Location 5.6 +*/ +QGeoCoordinate QDeclarativePolylineMapItem::coordinateAt(int index) const +{ + if (index < 0 || index >= path_.size()) + return QGeoCoordinate(); + + return path_.at(index); +} + +/*! + \qmlmethod coordinate MapPolyline::containsCoordinate(coordinate) + + Returns true if the given \a coordinate is part of the path. + + \since Qt Location 5.6 +*/ +bool QDeclarativePolylineMapItem::containsCoordinate(const QGeoCoordinate &coordinate) +{ + return path_.indexOf(coordinate) > -1; +} + +/*! + \qmlmethod void MapPolyline::removeCoordinate(coordinate) + + Removes \a coordinate from the path. If there are multiple instances of the + same coordinate, the one added last is removed. + + If \a coordinate is not in the path this method does nothing. + + \sa addCoordinate, insertCoordinate, path +*/ +void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordinate) +{ + int index = path_.lastIndexOf(coordinate); + removeCoordinate(index); +} + +/*! + \qmlmethod void MapPolyline::removeCoordinate(index) + + Removes a coordinate from the path at the given \a index. + + If \a index is invalid then this method does nothing. + + \since Qt Location 5.6 + + \sa addCoordinate, insertCoordinate, path +*/ +void QDeclarativePolylineMapItem::removeCoordinate(int index) +{ + if (index < 0 || index >= path_.size()) + return; + + path_.removeAt(index); + geoLeftBound_ = getLeftBound(path_, deltaXs_, minX_); + geometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlpropertygroup Location::MapPolyline::line + \qmlproperty int MapPolyline::line.width + \qmlproperty color MapPolyline::line.color + + This property is part of the line property group. The line + property group holds the width and color used to draw the line. + + The width is in pixels and is independent of the zoom level of the map. + The default values correspond to a black border with a width of 1 pixel. + + For no line, use a width of 0 or a transparent color. +*/ + +QDeclarativeMapLineProperties *QDeclarativePolylineMapItem::line() +{ + return &line_; +} + +QGeoCoordinate QDeclarativePolylineMapItem::computeLeftBound(const QList &path, + QVector &deltaXs, + double &minX) +{ + if (path.isEmpty()) { + minX = qInf(); + return QGeoCoordinate(); + } + deltaXs.resize(path.size()); + + deltaXs[0] = minX = 0.0; + int minId = 0; + + for (int i = 1; i < path.size(); i++) { + const QGeoCoordinate &geoFrom = path.at(i-1); + const QGeoCoordinate &geoTo = path.at(i); + double longiFrom = geoFrom.longitude(); + double longiTo = geoTo.longitude(); + double deltaLongi = longiTo - longiFrom; + if (qAbs(deltaLongi) > 180.0) { + if (longiTo > 0.0) + longiTo -= 360.0; + else + longiTo += 360.0; + deltaLongi = longiTo - longiFrom; + } + deltaXs[i] = deltaXs[i-1] + deltaLongi; + if (deltaXs[i] < minX) { + minX = deltaXs[i]; + minId = i; + } + } + return path.at(minId); +} + +QGeoCoordinate QDeclarativePolylineMapItem::updateLeftBound(const QList &path, + QVector &deltaXs, + double &minX, + QGeoCoordinate currentLeftBound) +{ + if (path.isEmpty()) { + deltaXs.clear(); + minX = qInf(); + return QGeoCoordinate(); + } else if (path.size() == 1) { + deltaXs.resize(1); + deltaXs[0] = minX = 0.0; + return path.last(); + } else if ( path.size() != deltaXs.size() + 1 ) { // something went wrong. This case should not happen + return computeLeftBound(path, deltaXs, minX); + } + + const QGeoCoordinate &geoFrom = path.at(path.size()-2); + const QGeoCoordinate &geoTo = path.last(); + double longiFrom = geoFrom.longitude(); + double longiTo = geoTo.longitude(); + double deltaLongi = longiTo - longiFrom; + if (qAbs(deltaLongi) > 180.0) { + if (longiTo > 0.0) + longiTo -= 360.0; + else + longiTo += 360.0; + deltaLongi = longiTo - longiFrom; + } + + deltaXs.push_back(deltaXs.last() + deltaLongi); + if (deltaXs.last() < minX) { + minX = deltaXs.last(); + return path.last(); + } else { + return currentLeftBound; + } +} + +QGeoCoordinate QDeclarativePolylineMapItem::getLeftBound(const QList &path, + QVector &deltaXs, + double &minX) +{ + return QDeclarativePolylineMapItem::computeLeftBound(path, deltaXs, minX); +} + +// Optimizing the common addCoordinate() path +QGeoCoordinate QDeclarativePolylineMapItem::getLeftBound(const QList &path, + QVector &deltaXs, + double &minX, + QGeoCoordinate currentLeftBound) +{ + return QDeclarativePolylineMapItem::updateLeftBound(path, deltaXs, minX, currentLeftBound); +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + + QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(geometry_.firstPointOffset()); + QGeoCoordinate newCoordinate = map()->itemPositionToCoordinate(newPoint, false); + if (newCoordinate.isValid()) { + double firstLongitude = path_.at(0).longitude(); + double firstLatitude = path_.at(0).latitude(); + double minMaxLatitude = firstLatitude; + // prevent dragging over valid min and max latitudes + for (int i = 0; i < path_.count(); ++i) { + double newLatitude = path_.at(i).latitude() + + newCoordinate.latitude() - firstLatitude; + if (!QLocationUtils::isValidLat(newLatitude)) { + if (qAbs(newLatitude) > qAbs(minMaxLatitude)) { + minMaxLatitude = newLatitude; + } + } + } + // calculate offset needed to re-position the item within map border + double offsetLatitude = minMaxLatitude - QLocationUtils::clipLat(minMaxLatitude); + for (int i = 0; i < path_.count(); ++i) { + QGeoCoordinate coord = path_.at(i); + // handle dateline crossing + coord.setLongitude(QLocationUtils::wrapLong(coord.longitude() + + newCoordinate.longitude() - firstLongitude)); + coord.setLatitude(coord.latitude() + + newCoordinate.latitude() - firstLatitude - offsetLatitude); + path_.replace(i, coord); + } + + geoLeftBound_.setLongitude(QLocationUtils::wrapLong(geoLeftBound_.longitude() + + newCoordinate.longitude() - firstLongitude)); + geometry_.setPreserveGeometry(true, geoLeftBound_); + geometry_.markSourceDirty(); + polishAndUpdate(); + emit pathChanged(); + } + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + return; + + // if the scene is tilted, we must regenerate our geometry every frame + if (map()->cameraCapabilities().supportsTilting() + && (event.cameraData.tilt() > 0.1 + || event.cameraData.tilt() < -0.1)) { + geometry_.markSourceDirty(); + } + + // if the scene is rolled, we must regen too + if (map()->cameraCapabilities().supportsRolling() + && (event.cameraData.roll() > 0.1 + || event.cameraData.roll() < -0.1)) { + geometry_.markSourceDirty(); + } + + // otherwise, only regen on rotate, resize and zoom + if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) { + geometry_.markSourceDirty(); + } + geometry_.setPreserveGeometry(true, geometry_.geoLeftBound()); + geometry_.markScreenDirty(); + polishAndUpdate(); +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::updatePolish() +{ + if (!map() || path_.count() == 0) + return; + + QScopedValueRollback rollback(updatingGeometry_); + updatingGeometry_ = true; + + geometry_.updateSourcePoints(*map(), path_, geoLeftBound_); + geometry_.updateScreenPoints(*map(), line_.width()); + + setWidth(geometry_.sourceBoundingBox().width()); + setHeight(geometry_.sourceBoundingBox().height()); + + setPositionOnMap(path_.at(0), -1 * geometry_.sourceBoundingBox().topLeft()); +} + +/*! + \internal +*/ +QSGNode *QDeclarativePolylineMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + MapPolylineNode *node = static_cast(oldNode); + + if (!node) { + node = new MapPolylineNode(); + } + + //TODO: update only material + if (geometry_.isScreenDirty() || dirtyMaterial_ || !oldNode) { + node->update(line_.color(), &geometry_); + geometry_.setPreserveGeometry(false); + geometry_.markClean(); + dirtyMaterial_ = false; + } + return node; +} + +bool QDeclarativePolylineMapItem::contains(const QPointF &point) const +{ + QVector vertices = geometry_.vertices(); + QPolygonF tri; + for (int i = 0; i < vertices.size(); ++i) { + tri << vertices[i]; + if (tri.size() == 3) { + if (tri.containsPoint(point,Qt::OddEvenFill)) + return true; + tri.remove(0); + } + } + + return false; +} + +////////////////////////////////////////////////////////////////////// + +/*! + \internal +*/ +MapPolylineNode::MapPolylineNode() : + geometry_(QSGGeometry::defaultAttributes_Point2D(),0), + blocked_(true) +{ + geometry_.setDrawingMode(GL_TRIANGLE_STRIP); + QSGGeometryNode::setMaterial(&fill_material_); + QSGGeometryNode::setGeometry(&geometry_); +} + + +/*! + \internal +*/ +MapPolylineNode::~MapPolylineNode() +{ +} + +/*! + \internal +*/ +bool MapPolylineNode::isSubtreeBlocked() const +{ + return blocked_; +} + +/*! + \internal +*/ +void MapPolylineNode::update(const QColor &fillColor, + const QGeoMapItemGeometry *shape) +{ + if (shape->size() == 0) { + blocked_ = true; + return; + } else { + blocked_ = false; + } + + QSGGeometry *fill = QSGGeometryNode::geometry(); + shape->allocateAndFill(fill); + markDirty(DirtyGeometry); + + if (fillColor != fill_material_.color()) { + fill_material_.setColor(fillColor); + setMaterial(&fill_material_); + markDirty(DirtyMaterial); + } +} + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativepolylinemapitem_p.h b/src/imports/location/qdeclarativepolylinemapitem_p.h new file mode 100644 index 0000000..8c827b6 --- /dev/null +++ b/src/imports/location/qdeclarativepolylinemapitem_p.h @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPOLYLINEMAPITEM +#define QDECLARATIVEPOLYLINEMAPITEM + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeomapitembase_p.h" +#include "qgeomapitemgeometry_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class MapPolylineNode; + +class QDeclarativeMapLineProperties : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + +public: + explicit QDeclarativeMapLineProperties(QObject *parent = 0); + + QColor color() const; + void setColor(const QColor &color); + + qreal width() const; + void setWidth(qreal width); + +Q_SIGNALS: + void widthChanged(qreal width); + void colorChanged(const QColor &color); + +private: + qreal width_; + QColor color_; +}; + +class QGeoMapPolylineGeometry : public QGeoMapItemGeometry +{ +public: + QGeoMapPolylineGeometry(); + + void updateSourcePoints(const QGeoMap &map, + const QList &path, + const QGeoCoordinate geoLeftBound); + + void updateScreenPoints(const QGeoMap &map, + qreal strokeWidth); + +private: + QVector srcPoints_; + QVector srcPointTypes_; + + +}; + +class QDeclarativePolylineMapItem : public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + + Q_PROPERTY(QJSValue path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QDeclarativeMapLineProperties *line READ line CONSTANT) + +public: + explicit QDeclarativePolylineMapItem(QQuickItem *parent = 0); + ~QDeclarativePolylineMapItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + //from QuickItem + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + + Q_INVOKABLE int pathLength() const; + Q_INVOKABLE void addCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void insertCoordinate(int index, const QGeoCoordinate &coordinate); + Q_INVOKABLE void replaceCoordinate(int index, const QGeoCoordinate &coordinate); + Q_INVOKABLE QGeoCoordinate coordinateAt(int index) const; + Q_INVOKABLE bool containsCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void removeCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void removeCoordinate(int index); + + QJSValue path() const; + virtual void setPath(const QJSValue &value); + + bool contains(const QPointF &point) const Q_DECL_OVERRIDE; + + QDeclarativeMapLineProperties *line(); + + static QGeoCoordinate computeLeftBound(const QList &path, QVector &deltaXs, double &minX); + static QGeoCoordinate updateLeftBound(const QList &path, QVector &deltaXs, double &minX, QGeoCoordinate currentLeftBound); + static QGeoCoordinate getLeftBound(const QList &path, QVector &deltaXs, double &minX); + static QGeoCoordinate getLeftBound(const QList &path, QVector &deltaXs, double &minX, QGeoCoordinate currentLeftBound); + +Q_SIGNALS: + void pathChanged(); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void setPathFromGeoList(const QList &path); + void updatePolish() Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void updateAfterLinePropertiesChanged(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + void pathPropertyChanged(); + + QDeclarativeMapLineProperties line_; + QList path_; + QGeoCoordinate geoLeftBound_; + QColor color_; + bool dirtyMaterial_; + QGeoMapPolylineGeometry geometry_; + bool updatingGeometry_; + // for the left bound calculation + QVector deltaXs_; // longitude deltas from path_[0] + double minX_; // minimum value inside deltaXs_ +}; + +////////////////////////////////////////////////////////////////////// + +class MapPolylineNode : public QSGGeometryNode +{ + +public: + MapPolylineNode(); + ~MapPolylineNode(); + + void update(const QColor &fillColor, const QGeoMapItemGeometry *shape); + bool isSubtreeBlocked() const; + +private: + QSGFlatColorMaterial fill_material_; + QSGGeometry geometry_; + bool blocked_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeMapLineProperties) +QML_DECLARE_TYPE(QDeclarativePolylineMapItem) + +#endif /* QDECLARATIVEPOLYLINEMAPITEM_H_ */ diff --git a/src/imports/location/qdeclarativerectanglemapitem.cpp b/src/imports/location/qdeclarativerectanglemapitem.cpp new file mode 100644 index 0000000..f50d034 --- /dev/null +++ b/src/imports/location/qdeclarativerectanglemapitem.cpp @@ -0,0 +1,452 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativerectanglemapitem_p.h" +#include "qdeclarativepolygonmapitem_p.h" +#include "qgeocameracapabilities_p.h" +#include "qlocationutils_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapRectangle + \instantiates QDeclarativeRectangleMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapRectangle type displays a rectangle on a Map. + + The MapRectangle type displays a rectangle on a Map. Rectangles are a + special case of Polygon with exactly 4 vertices and 4 "straight" edges. In + this case, "straight" means that the top-left point has the same latitude + as the top-right point (the top edge), and the bottom-left point has the + same latitude as the bottom-right point (the bottom edge). Similarly, the + points on the left side have the same longitude, and the points on the + right side have the same longitude. + + To specify the rectangle, it requires a \l topLeft and \l bottomRight point, + both given by a \l {coordinate}. + + By default, the rectangle is displayed with transparent fill and a 1-pixel + thick black border. This can be changed using the \l color, \l border.color + and \l border.width properties. + + \note Similar to the \l MapPolygon type, MapRectangles are geographic + items, thus dragging a MapRectangle causes its vertices to be recalculated + in the geographic coordinate space. Apparent stretching of the item + occurs when dragged to the a different latitude, however, its edges + remain straight. + + \section2 Performance + + MapRectangles have a rendering cost identical to a MapPolygon with 4 + vertices. + + Like the other map objects, MapRectangle is normally drawn without a smooth + appearance. Setting the \l opacity property will force the object to be + blended, which decreases performance considerably depending on the hardware + in use. + + \section2 Example Usage + + The following snippet shows a map containing a MapRectangle, spanning + from (-27, 153) to (-28, 153.5), near Brisbane, Australia. The rectangle + is filled in green, with a 2 pixel black border. + + \code + Map { + MapRectangle { + color: 'green' + border.width: 2 + topLeft { + latitude: -27 + longitude: 153 + } + bottomRight { + latitude: -28 + longitude: 153.5 + } + } + } + \endcode + + \image api-maprectangle.png +*/ + +struct Vertex +{ + QVector2D position; +}; + +QGeoMapRectangleGeometry::QGeoMapRectangleGeometry() +{ +} + +/*! + \internal +*/ +void QGeoMapRectangleGeometry::updatePoints(const QGeoMap &map, + const QGeoCoordinate &topLeft, + const QGeoCoordinate &bottomRight) +{ + if (!screenDirty_ && !sourceDirty_) + return; + + QDoubleVector2D tl = map.coordinateToItemPosition(topLeft, false); + QDoubleVector2D br = map.coordinateToItemPosition(bottomRight, false); + + // We can get NaN if the map isn't set up correctly, or the projection + // is faulty -- probably best thing to do is abort + if (!qIsFinite(tl.x()) || !qIsFinite(tl.y())) + return; + if (!qIsFinite(br.x()) || !qIsFinite(br.y())) + return; + + if ( preserveGeometry_ ) { + double unwrapBelowX = map.coordinateToItemPosition(geoLeftBound_, false).x(); + if (br.x() < unwrapBelowX) + br.setX(tl.x() + screenBounds_.width()); + } + + QRectF re(tl.toPointF(), br.toPointF()); + re.translate(-1 * tl.toPointF()); + + clear(); + screenVertices_.reserve(6); + + screenVertices_ << re.topLeft(); + screenVertices_ << re.topRight(); + screenVertices_ << re.bottomLeft(); + + screenVertices_ << re.topRight(); + screenVertices_ << re.bottomLeft(); + screenVertices_ << re.bottomRight(); + + firstPointOffset_ = QPointF(0,0); + srcOrigin_ = topLeft; + screenBounds_ = re; + + screenOutline_ = QPainterPath(); + screenOutline_.addRect(re); + + geoLeftBound_ = topLeft; +} + +QDeclarativeRectangleMapItem::QDeclarativeRectangleMapItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), color_(Qt::transparent), dirtyMaterial_(true), + updatingGeometry_(false) +{ + setFlag(ItemHasContents, true); + QObject::connect(&border_, SIGNAL(colorChanged(QColor)), + this, SLOT(markSourceDirtyAndUpdate())); + QObject::connect(&border_, SIGNAL(widthChanged(qreal)), + this, SLOT(markSourceDirtyAndUpdate())); +} + +QDeclarativeRectangleMapItem::~QDeclarativeRectangleMapItem() +{ +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (map) + markSourceDirtyAndUpdate(); +} + +/*! + \qmlpropertygroup Location::MapRectangle::border + \qmlproperty int MapRectangle::border.width + \qmlproperty color MapRectangle::border.color + + This property is part of the border property group. The border property group + holds the width and color used to draw the border of the rectangle. + The width is in pixels and is independent of the zoom level of the map. + + The default values correspond to a black border with a width of 1 pixel. + For no line, use a width of 0 or a transparent color. +*/ +QDeclarativeMapLineProperties *QDeclarativeRectangleMapItem::border() +{ + return &border_; +} + +/*! + \qmlproperty coordinate MapRectangle::topLeft + + This property holds the top-left coordinate of the MapRectangle which + can be used to retrieve its longitude, latitude and altitude. +*/ +void QDeclarativeRectangleMapItem::setTopLeft(const QGeoCoordinate &topLeft) +{ + if (topLeft_ == topLeft) + return; + + topLeft_ = topLeft; + + markSourceDirtyAndUpdate(); + emit topLeftChanged(topLeft_); +} + +QGeoCoordinate QDeclarativeRectangleMapItem::topLeft() +{ + return topLeft_; +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::markSourceDirtyAndUpdate() +{ + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); +} + +/*! + \qmlproperty coordinate MapRectangle::bottomRight + + This property holds the bottom-right coordinate of the MapRectangle which + can be used to retrieve its longitude, latitude and altitude. +*/ +void QDeclarativeRectangleMapItem::setBottomRight(const QGeoCoordinate &bottomRight) +{ + if (bottomRight_ == bottomRight) + return; + + bottomRight_ = bottomRight; + + markSourceDirtyAndUpdate(); + emit bottomRightChanged(bottomRight_); +} + +QGeoCoordinate QDeclarativeRectangleMapItem::bottomRight() +{ + return bottomRight_; +} + +/*! + \qmlproperty color MapRectangle::color + + This property holds the fill color of the rectangle. For no fill, use + a transparent color. +*/ +QColor QDeclarativeRectangleMapItem::color() const +{ + return color_; +} + +void QDeclarativeRectangleMapItem::setColor(const QColor &color) +{ + if (color_ == color) + return; + color_ = color; + dirtyMaterial_ = true; + polishAndUpdate(); + emit colorChanged(color_); +} + +/*! + \qmlproperty real MapRectangle::opacity + + This property holds the opacity of the item. Opacity is specified as a + number between 0 (fully transparent) and 1 (fully opaque). The default is 1. + + An item with 0 opacity will still receive mouse events. To stop mouse events, set the + visible property of the item to false. +*/ + +/*! + \internal +*/ +QSGNode *QDeclarativeRectangleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + MapPolygonNode *node = static_cast(oldNode); + + if (!node) { + node = new MapPolygonNode(); + } + + //TODO: update only material + if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { + node->update(color_, border_.color(), &geometry_, &borderGeometry_); + geometry_.setPreserveGeometry(false); + borderGeometry_.setPreserveGeometry(false); + geometry_.markClean(); + borderGeometry_.markClean(); + dirtyMaterial_ = false; + } + return node; +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::updatePolish() +{ + if (!map() || !topLeft().isValid() || !bottomRight().isValid()) + return; + + QScopedValueRollback rollback(updatingGeometry_); + updatingGeometry_ = true; + + geometry_.updatePoints(*map(), topLeft_, bottomRight_); + + QList pathClosed; + pathClosed << topLeft_; + pathClosed << QGeoCoordinate(topLeft_.latitude(), bottomRight_.longitude()); + pathClosed << bottomRight_; + pathClosed << QGeoCoordinate(bottomRight_.latitude(), topLeft_.longitude()); + pathClosed << pathClosed.first(); + + if (border_.color() != Qt::transparent && border_.width() > 0) { + borderGeometry_.updateSourcePoints(*map(), pathClosed, topLeft_); + borderGeometry_.updateScreenPoints(*map(), border_.width()); + + QList geoms; + geoms << &geometry_ << &borderGeometry_; + QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); + + setWidth(combined.width()); + setHeight(combined.height()); + } else { + borderGeometry_.clear(); + + setWidth(geometry_.screenBoundingBox().width()); + setHeight(geometry_.screenBoundingBox().height()); + } + + setPositionOnMap(pathClosed.at(0), geometry_.firstPointOffset()); +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + return; + + // if the scene is tilted, we must regenerate our geometry every frame + if (map()->cameraCapabilities().supportsTilting() + && (event.cameraData.tilt() > 0.1 + || event.cameraData.tilt() < -0.1)) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + + // if the scene is rolled, we must regen too + if (map()->cameraCapabilities().supportsRolling() + && (event.cameraData.roll() > 0.1 + || event.cameraData.roll() < -0.1)) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + + // otherwise, only regen on rotate, resize and zoom + if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) { + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + } + geometry_.setPreserveGeometry(true, topLeft_); + borderGeometry_.setPreserveGeometry(true, topLeft_); + geometry_.markScreenDirty(); + borderGeometry_.markScreenDirty(); + polishAndUpdate(); +} + +/*! + \internal +*/ +bool QDeclarativeRectangleMapItem::contains(const QPointF &point) const +{ + return (geometry_.contains(point) || borderGeometry_.contains(point)); +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + + QDoubleVector2D newTopLeftPoint = QDoubleVector2D(x(),y()); + QGeoCoordinate newTopLeft = map()->itemPositionToCoordinate(newTopLeftPoint, false); + if (newTopLeft.isValid()) { + // calculate new geo width while checking for dateline crossing + const double lonW = bottomRight_.longitude() > topLeft_.longitude() ? + bottomRight_.longitude() - topLeft_.longitude() : + bottomRight_.longitude() + 360 - topLeft_.longitude(); + const double latH = qAbs(bottomRight_.latitude() - topLeft_.latitude()); + QGeoCoordinate newBottomRight; + // prevent dragging over valid min and max latitudes + if (QLocationUtils::isValidLat(newTopLeft.latitude() - latH)) { + newBottomRight.setLatitude(newTopLeft.latitude() - latH); + } else { + newBottomRight.setLatitude(QLocationUtils::clipLat(newTopLeft.latitude() - latH)); + newTopLeft.setLatitude(newBottomRight.latitude() + latH); + } + // handle dateline crossing + newBottomRight.setLongitude(QLocationUtils::wrapLong(newTopLeft.longitude() + lonW)); + newBottomRight.setAltitude(newTopLeft.altitude()); + topLeft_ = newTopLeft; + bottomRight_ = newBottomRight; + geometry_.setPreserveGeometry(true, newTopLeft); + borderGeometry_.setPreserveGeometry(true, newTopLeft); + markSourceDirtyAndUpdate(); + emit topLeftChanged(topLeft_); + emit bottomRightChanged(bottomRight_); + } + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +QT_END_NAMESPACE diff --git a/src/imports/location/qdeclarativerectanglemapitem_p.h b/src/imports/location/qdeclarativerectanglemapitem_p.h new file mode 100644 index 0000000..fb9936b --- /dev/null +++ b/src/imports/location/qdeclarativerectanglemapitem_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVERECTANGLEMAPITEM_H_ +#define QDECLARATIVERECTANGLEMAPITEM_H_ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeomapitembase_p.h" +#include "qgeomapitemgeometry_p.h" +#include "qdeclarativepolylinemapitem_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoMapRectangleGeometry : public QGeoMapItemGeometry +{ +public: + QGeoMapRectangleGeometry(); + + void updatePoints(const QGeoMap &map, + const QGeoCoordinate &topLeft, + const QGeoCoordinate &bottomRight); +}; + +class MapRectangleNode; + +class QDeclarativeRectangleMapItem: public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + + Q_PROPERTY(QGeoCoordinate topLeft READ topLeft WRITE setTopLeft NOTIFY topLeftChanged) + Q_PROPERTY(QGeoCoordinate bottomRight READ bottomRight WRITE setBottomRight NOTIFY bottomRightChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QDeclarativeMapLineProperties *border READ border) + +public: + explicit QDeclarativeRectangleMapItem(QQuickItem *parent = 0); + ~QDeclarativeRectangleMapItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + //from QuickItem + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + + QGeoCoordinate topLeft(); + void setTopLeft(const QGeoCoordinate ¢er); + + QGeoCoordinate bottomRight(); + void setBottomRight(const QGeoCoordinate ¢er); + + QColor color() const; + void setColor(const QColor &color); + + QDeclarativeMapLineProperties *border(); + + bool contains(const QPointF &point) const Q_DECL_OVERRIDE; + +Q_SIGNALS: + void topLeftChanged(const QGeoCoordinate &topLeft); + void bottomRightChanged(const QGeoCoordinate &bottomRight); + void colorChanged(const QColor &color); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void updatePolish() Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void markSourceDirtyAndUpdate(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + QGeoCoordinate topLeft_; + QGeoCoordinate bottomRight_; + QDeclarativeMapLineProperties border_; + QColor color_; + bool dirtyMaterial_; + QGeoMapRectangleGeometry geometry_; + QGeoMapPolylineGeometry borderGeometry_; + bool updatingGeometry_; +}; + +////////////////////////////////////////////////////////////////////// + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeRectangleMapItem) + +#endif /* QDECLARATIVERECTANGLEMAPITEM_H_ */ diff --git a/src/imports/location/qdeclarativeroutemapitem.cpp b/src/imports/location/qdeclarativeroutemapitem.cpp new file mode 100644 index 0000000..19930cc --- /dev/null +++ b/src/imports/location/qdeclarativeroutemapitem.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qdeclarativeroutemapitem_p.h" +#include "qdeclarativepolylinemapitem_p.h" +#include "qgeocameracapabilities_p.h" +#include "qdeclarativegeoroute_p.h" + +#include +#include + +/*! + \qmltype MapRoute + \instantiates QDeclarativeRouteMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 + + \brief The MapRoute type displays a Route on a Map. + + The MapRoute type displays a Route obtained through a RouteModel or + other means, on the Map as a Polyline following the path of the Route. + + MapRoute is really a \l MapPolyline, but with the path specified using the + \l route property instead of directly in \l {coordinate}{coordinates}. + + By default, the route is displayed as a 1-pixel thick black line. This can + be changed using the \l line.width and \l line.color properties. + + \section2 Performance + + For notes about the performance on MapRoute, refer to the documentation for + \l MapPolyline. + + \section2 Example Usage + + Here is how to draw a \l{Route}{route} on a \l{Map}{map}: + + \snippet declarative/maps.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/maps.qml MapRoute +*/ + +/*! + \qmlpropertygroup Location::MapRoute::line + \qmlproperty int MapRoute::line.width + \qmlproperty color MapRoute::line.color + + This property is part of the line property group. The line + property group holds the width and color used to draw the line. + + The width is in pixels and is independent of the zoom level of the map. + The default values correspond to a black border with a width of 1 pixel. + + For no line, use a width of 0 or a transparent color. +*/ + + +QDeclarativeRouteMapItem::QDeclarativeRouteMapItem(QQuickItem *parent) +: QDeclarativePolylineMapItem(parent), route_(0) +{ + setFlag(ItemHasContents, true); +} + +QDeclarativeRouteMapItem::~QDeclarativeRouteMapItem() +{ +} + +/*! + \qmlproperty Route MapRoute::route + + This property holds the route to be drawn which can be used + to represent one geographical route. +*/ +QDeclarativeGeoRoute *QDeclarativeRouteMapItem::route() const +{ + return route_; +} + +void QDeclarativeRouteMapItem::setRoute(QDeclarativeGeoRoute *route) +{ + if (route_ == route) + return; + + route_ = route; + + connect(route_, SIGNAL(pathChanged()), this, SLOT(updateRoutePath())); + + if (route_) + setPathFromGeoList(route_->routePath()); + + emit routeChanged(route_); +} + +void QDeclarativeRouteMapItem::updateRoutePath() +{ + setPathFromGeoList(route_->routePath()); +} + +/*! + \internal void QDeclarativeRouteMapItem::setPath(const QJSValue &value) + + Used to disable path property on the RouteMapItem + */ +void QDeclarativeRouteMapItem::setPath(const QJSValue &value) +{ + Q_UNUSED(value); + qWarning() << "Can not set the path on QDeclarativeRouteMapItem." + << "Please use the route property instead."; +} diff --git a/src/imports/location/qdeclarativeroutemapitem_p.h b/src/imports/location/qdeclarativeroutemapitem_p.h new file mode 100644 index 0000000..3acac3c --- /dev/null +++ b/src/imports/location/qdeclarativeroutemapitem_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEROUTEMAPITEM_H_ +#define QDECLARATIVEROUTEMAPITEM_H_ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativegeomapitembase_p.h" +#include "qdeclarativegeomap_p.h" +#include "qdeclarativepolylinemapitem_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoRoute; + +class QDeclarativeRouteMapItem : public QDeclarativePolylineMapItem +{ + Q_OBJECT + + Q_PROPERTY(QDeclarativeGeoRoute *route READ route WRITE setRoute NOTIFY routeChanged) + +public: + explicit QDeclarativeRouteMapItem(QQuickItem *parent = 0); + ~QDeclarativeRouteMapItem(); + + QDeclarativeGeoRoute *route() const; + void setRoute(QDeclarativeGeoRoute *route); + +Q_SIGNALS: + void routeChanged(const QDeclarativeGeoRoute *route); + +private slots: + void updateRoutePath(); + +protected: + void setPath(const QJSValue &value) Q_DECL_OVERRIDE; + +private: + QDeclarativeGeoRoute *route_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeRouteMapItem) + +#endif /* QDECLARATIVEROUTEMAPITEM_H_ */ diff --git a/src/imports/location/qgeomapitemgeometry.cpp b/src/imports/location/qgeomapitemgeometry.cpp new file mode 100644 index 0000000..e9c490d --- /dev/null +++ b/src/imports/location/qgeomapitemgeometry.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomapitemgeometry_p.h" +#include "qdeclarativegeomap_p.h" +#include "qlocationutils_p.h" +#include +#include "qdoublevector2d_p.h" + +QT_BEGIN_NAMESPACE + +QGeoMapItemGeometry::QGeoMapItemGeometry() +: sourceDirty_(true), screenDirty_(true), clipToViewport_(true), preserveGeometry_(false) +{ +} + +/*! + \internal +*/ +void QGeoMapItemGeometry::translate(const QPointF &offset) +{ + for (int i = 0; i < screenVertices_.size(); ++i) + screenVertices_[i] += offset; + + firstPointOffset_ += offset; + screenOutline_.translate(offset); + screenBounds_.translate(offset); +} + +/*! + \internal +*/ +void QGeoMapItemGeometry::allocateAndFill(QSGGeometry *geom) const +{ + const QVector &vx = screenVertices_; + const QVector &ix = screenIndices_; + + if (isIndexed()) { + geom->allocate(vx.size(), ix.size()); + if (geom->indexType() == GL_UNSIGNED_SHORT) { + quint16 *its = geom->indexDataAsUShort(); + for (int i = 0; i < ix.size(); ++i) + its[i] = ix[i]; + } else if (geom->indexType() == GL_UNSIGNED_INT) { + quint32 *its = geom->indexDataAsUInt(); + for (int i = 0; i < ix.size(); ++i) + its[i] = ix[i]; + } + } else { + geom->allocate(vx.size()); + } + + QSGGeometry::Point2D *pts = geom->vertexDataAsPoint2D(); + for (int i = 0; i < vx.size(); ++i) + pts[i].set(vx[i].x(), vx[i].y()); +} + +/*! + \internal +*/ +QRectF QGeoMapItemGeometry::translateToCommonOrigin(const QList &geoms) +{ + QGeoCoordinate origin = geoms.at(0)->origin(); + + QPainterPath brects; + + // first get max offset + QPointF maxOffset = geoms.at(0)->firstPointOffset(); + foreach (QGeoMapItemGeometry *g, geoms) { + Q_ASSERT(g->origin() == origin); + QPointF o = g->firstPointOffset(); + maxOffset.setX(qMax(o.x(), maxOffset.x())); + maxOffset.setY(qMax(o.y(), maxOffset.y())); + } + + // then translate everything + foreach (QGeoMapItemGeometry *g, geoms) { + g->translate(maxOffset - g->firstPointOffset()); + brects.addRect(g->sourceBoundingBox()); + } + + return brects.boundingRect(); +} + +/*! + \internal +*/ +double QGeoMapItemGeometry::geoDistanceToScreenWidth(const QGeoMap &map, + const QGeoCoordinate &fromCoord, + const QGeoCoordinate &toCoord) +{ + // Do not wrap around half the globe + Q_ASSERT(!qFuzzyCompare(fromCoord.longitude(), toCoord.longitude())); + + QGeoCoordinate mapMid = map.itemPositionToCoordinate(QDoubleVector2D(map.width()/2.0, 0)); + double halfGeoDist = toCoord.longitude() - fromCoord.longitude(); + if (toCoord.longitude() < fromCoord.longitude()) + halfGeoDist += 360; + halfGeoDist /= 2.0; + QGeoCoordinate geoDelta = QGeoCoordinate(0, + QLocationUtils::wrapLong(mapMid.longitude() + halfGeoDist)); + QDoubleVector2D halfScreenDist = map.coordinateToItemPosition(geoDelta, false) + - QDoubleVector2D(map.width()/2.0, 0); + return halfScreenDist.x() * 2.0; +} + +QT_END_NAMESPACE diff --git a/src/imports/location/qgeomapitemgeometry_p.h b/src/imports/location/qgeomapitemgeometry_p.h new file mode 100644 index 0000000..36b670f --- /dev/null +++ b/src/imports/location/qgeomapitemgeometry_p.h @@ -0,0 +1,148 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 The Qt Company Ltd. + ** Contact: http://www.qt.io/licensing/ + ** + ** This file is part of the QtLocation module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL3$ + ** Commercial License Usage + ** Licensees holding valid commercial Qt licenses may use this file in + ** accordance with the commercial license agreement provided with the + ** Software or, alternatively, in accordance with the terms contained in + ** a written agreement between you and The Qt Company. For licensing terms + ** and conditions see http://www.qt.io/terms-conditions. For further + ** information use the contact form at http://www.qt.io/contact-us. + ** + ** GNU Lesser General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU Lesser + ** General Public License version 3 as published by the Free Software + ** Foundation and appearing in the file LICENSE.LGPLv3 included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU Lesser General Public License version 3 requirements + ** will be met: https://www.gnu.org/licenses/lgpl.html. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU + ** General Public License version 2.0 or later as published by the Free + ** Software Foundation and appearing in the file LICENSE.GPL included in + ** the packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 2.0 requirements will be + ** met: http://www.gnu.org/licenses/gpl-2.0.html. + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#ifndef QGEOMAPITEMGEOMETRY_H +#define QGEOMAPITEMGEOMETRY_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QSGGeometry; +class QGeoMap; + +class QGeoMapItemGeometry +{ +public: + QGeoMapItemGeometry(); + + inline bool isSourceDirty() const { return sourceDirty_; } + inline bool isScreenDirty() const { return screenDirty_; } + inline void markSourceDirty() { sourceDirty_ = true; screenDirty_ = true; } + inline void markScreenDirty() { screenDirty_ = true; clipToViewport_ = true; } + inline void markFullScreenDirty() { screenDirty_ = true; clipToViewport_ = false;} + inline void markClean() { screenDirty_ = (sourceDirty_ = false); clipToViewport_ = true;} + + inline void setPreserveGeometry(bool value, QGeoCoordinate geoLeftBound = QGeoCoordinate()) + { + preserveGeometry_ = value; + if (preserveGeometry_) + geoLeftBound_ = geoLeftBound; + } + inline QGeoCoordinate geoLeftBound() { return geoLeftBound_; } + + inline QRectF sourceBoundingBox() const { return sourceBounds_; } + inline QRectF screenBoundingBox() const { return screenBounds_; } + + inline QPointF firstPointOffset() const { return firstPointOffset_; } + void translate(const QPointF &offset); + + inline const QGeoCoordinate &origin() const { return srcOrigin_; } + + inline bool contains(const QPointF &screenPoint) const { + return screenOutline_.contains(screenPoint); + } + + inline QVector2D vertex(quint32 index) const { + return QVector2D(screenVertices_[index]); + } + + inline QVector vertices() const { return screenVertices_; } + inline QVector indices() const { return screenIndices_; } + + inline bool isIndexed() const { return (!screenIndices_.isEmpty()); } + + /* Size is # of triangles */ + inline quint32 size() const + { + if (isIndexed()) + return screenIndices_.size() / 3; + else + return screenVertices_.size() / 3; + } + + inline void clear() { firstPointOffset_ = QPointF(0,0); + screenVertices_.clear(); screenIndices_.clear(); } + + void allocateAndFill(QSGGeometry *geom) const; + + double geoDistanceToScreenWidth(const QGeoMap &map, + const QGeoCoordinate &fromCoord, + const QGeoCoordinate &toCoord); + + static QRectF translateToCommonOrigin(const QList &geoms); + + +protected: + bool sourceDirty_; + bool screenDirty_; + bool clipToViewport_; + bool preserveGeometry_; + QGeoCoordinate geoLeftBound_; + + QPointF firstPointOffset_; + + QPainterPath screenOutline_; + + QRectF sourceBounds_; + QRectF screenBounds_; + + QGeoCoordinate srcOrigin_; + + QVector screenVertices_; + QVector screenIndices_; +}; + +QT_END_NAMESPACE + +#endif // QGEOMAPITEMGEOMETRY_H diff --git a/src/imports/location/qmldir b/src/imports/location/qmldir new file mode 100644 index 0000000..37ecf66 --- /dev/null +++ b/src/imports/location/qmldir @@ -0,0 +1,4 @@ +module QtLocation +plugin declarative_location +classname QtLocationDeclarativeModule +typeinfo plugins.qmltypes diff --git a/src/imports/location/qquickgeomapgesturearea.cpp b/src/imports/location/qquickgeomapgesturearea.cpp new file mode 100644 index 0000000..1a3b3c1 --- /dev/null +++ b/src/imports/location/qquickgeomapgesturearea.cpp @@ -0,0 +1,1286 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickgeomapgesturearea_p.h" +#include "qdeclarativegeomap_p.h" +#include "error_messages.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "math.h" +#include "qgeomap_p.h" +#include "qdoublevector2d_p.h" + +#define QML_MAP_FLICK_DEFAULTMAXVELOCITY 2500 +#define QML_MAP_FLICK_MINIMUMDECELERATION 500 +#define QML_MAP_FLICK_DEFAULTDECELERATION 2500 +#define QML_MAP_FLICK_MAXIMUMDECELERATION 10000 + +#define QML_MAP_FLICK_VELOCITY_SAMPLE_PERIOD 50 +// FlickThreshold determines how far the "mouse" must have moved +// before we perform a flick. +static const int FlickThreshold = 20; +// Really slow flicks can be annoying. +const qreal MinimumFlickVelocity = 75.0; + +QT_BEGIN_NAMESPACE + + +/*! + \qmltype MapPinchEvent + \instantiates QGeoMapPinchEvent + \inqmlmodule QtLocation + + \brief MapPinchEvent type provides basic information about pinch event. + + MapPinchEvent type provides basic information about pinch event. They are + present in handlers of MapPinch (for example pinchStarted/pinchUpdated). Events are only + guaranteed to be valid for the duration of the handler. + + Except for the \l accepted property, all properties are read-only. + + \section2 Example Usage + + The following example enables the pinch gesture on a map and reacts to the + finished event. + + \code + Map { + id: map + gesture.enabled: true + gesture.onPinchFinished:{ + var coordinate1 = map.toCoordinate(gesture.point1) + var coordinate2 = map.toCoordinate(gesture.point2) + console.log("Pinch started at:") + console.log(" Points (" + gesture.point1.x + ", " + gesture.point1.y + ") - (" + gesture.point2.x + ", " + gesture.point2.y + ")") + console.log(" Coordinates (" + coordinate1.latitude + ", " + coordinate1.longitude + ") - (" + coordinate2.latitude + ", " + coordinate2.longitude + ")") + } + } + \endcode + + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 +*/ + +/*! + \qmlproperty QPoint QtLocation::MapPinchEvent::center + + This read-only property holds the current center point. +*/ + +/*! + \qmlproperty real QtLocation::MapPinchEvent::angle + + This read-only property holds the current angle between the two points in + the range -180 to 180. Positive values for the angles mean counter-clockwise + while negative values mean the clockwise direction. Zero degrees is at the + 3 o'clock position. +*/ + +/*! + \qmlproperty QPoint QtLocation::MapPinchEvent::point1 + \qmlproperty QPoint QtLocation::MapPinchEvent::point2 + + These read-only properties hold the actual touch points generating the pinch. + The points are not in any particular order. +*/ + +/*! + \qmlproperty int QtLocation::MapPinchEvent::pointCount + + This read-only property holds the number of points currently touched. + The MapPinch will not react until two touch points have initiated a gesture, + but will remain active until all touch points have been released. +*/ + +/*! + \qmlproperty bool QtLocation::MapPinchEvent::accepted + + Setting this property to false in the \c MapPinch::onPinchStarted handler + will result in no further pinch events being generated, and the gesture + ignored. +*/ + +/*! + \qmltype MapGestureArea + \instantiates QQuickGeoMapGestureArea + + \inqmlmodule QtLocation + + \brief The MapGestureArea type provides Map gesture interaction. + + MapGestureArea objects are used as part of a Map, to provide for panning, + flicking and pinch-to-zoom gesture used on touch displays. + + A MapGestureArea is automatically created with a new Map and available with + the \l{Map::gesture}{gesture} property. This is the only way + to create a MapGestureArea, and once created this way cannot be destroyed + without its parent Map. + + The two most commonly used properties of the MapGestureArea are the \l enabled + and \l acceptedGestures properties. Both of these must be set before a + MapGestureArea will have any effect upon interaction with the Map. + The \l flickDeceleration property controls how quickly the map pan slows after contact + is released while panning the map. + + \section2 Performance + + The MapGestureArea, when enabled, must process all incoming touch events in + order to track the shape and size of the "pinch". The overhead added on + touch events can be considered constant time. + + \section2 Example Usage + + The following example enables the pinch and pan gestures on the map, but not flicking. So the + map scrolling will halt immediately on releasing the mouse button / touch. + + \code + Map { + gesture.enabled: true + gesture.acceptedGestures: MapGestureArea.PinchGesture | MapGestureArea.PanGesture + } + \endcode + + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 +*/ + +/*! + \qmlproperty bool QtLocation::MapGestureArea::enabled + + This property holds whether the gestures are enabled. +*/ + +/*! + \qmlproperty bool QtLocation::MapGestureArea::pinchActive + + This read-only property holds whether pinch gesture is active. +*/ + +/*! + \qmlproperty bool QtLocation::MapGestureArea::panActive + + This read-only property holds whether pan gesture is active. + + \note Change notifications for this property were introduced in Qt 5.5. +*/ + +/*! + \qmlproperty real QtLocation::MapGestureArea::maximumZoomLevelChange + + This property holds the maximum zoom level change per pinch, essentially + meant to be used for setting the zoom sensitivity. + + It is an indicative measure calculated from the dimensions of the + map area, roughly corresponding how much zoom level could change with + maximum pinch zoom. Default value is 4.0, maximum value is 10.0 +*/ + +/*! + \qmlproperty real MapGestureArea::flickDeceleration + + This property holds the rate at which a flick will decelerate. + + The default value is 2500. +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::pinchStarted(PinchEvent event) + + This signal is emitted when a pinch gesture is started. + + The corresponding handler is \c onPinchStarted. + + \sa pinchUpdated, pinchFinished +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::pinchUpdated(PinchEvent event) + + This signal is emitted as the user's fingers move across the map, + after the \l pinchStarted signal is emitted. + + The corresponding handler is \c onPinchUpdated. + + \sa pinchStarted, pinchFinished +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::pinchFinished(PinchEvent event) + + This signal is emitted at the end of a pinch gesture. + + The corresponding handler is \c onPinchFinished. + + \sa pinchStarted, pinchUpdated +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::panStarted() + + This signal is emitted when the map begins to move due to user + interaction. Typically this means that the user is dragging a finger - + or a mouse with one of more mouse buttons pressed - on the map. + + The corresponding handler is \c onPanStarted. +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::panFinished() + + This signal is emitted when the map stops moving due to user + interaction. If a flick was generated, this signal is + emitted before flick starts. If a flick was not + generated, this signal is emitted when the + user stops dragging - that is a mouse or touch release. + + The corresponding handler is \c onPanFinished. + +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::flickStarted() + + This signal is emitted when the map is flicked. A flick + starts from the point where the mouse or touch was released, + while still in motion. + + The corresponding handler is \c onFlichStarted. +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::flickFinished() + + This signal is emitted when the map stops moving due to a flick. + + The corresponding handler is \c onFlickFinished. +*/ + +QQuickGeoMapGestureArea::QQuickGeoMapGestureArea(QDeclarativeGeoMap *map) + : QQuickItem(map), + m_map(0), + m_declarativeMap(map), + m_enabled(true), + m_acceptedGestures(PinchGesture | PanGesture | FlickGesture), + m_preventStealing(false), + m_panEnabled(true) +{ + m_flick.m_enabled = true, + m_flick.m_maxVelocity = QML_MAP_FLICK_DEFAULTMAXVELOCITY; + m_flick.m_deceleration = QML_MAP_FLICK_DEFAULTDECELERATION; + m_flick.m_animation = 0; + m_touchPointState = touchPoints0; + m_pinchState = pinchInactive; + m_flickState = flickInactive; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setMap(QGeoMap *map) +{ + if (m_map || !map) + return; + + m_map = map; + m_flick.m_animation = new QQuickGeoCoordinateAnimation(this); + m_flick.m_animation->setTargetObject(m_declarativeMap); + m_flick.m_animation->setProperty(QStringLiteral("center")); + m_flick.m_animation->setEasing(QEasingCurve(QEasingCurve::OutQuad)); + connect(m_flick.m_animation, &QQuickAbstractAnimation::stopped, this, &QQuickGeoMapGestureArea::handleFlickAnimationStopped); +} + +/*! + \qmlproperty bool QtQuick::MapGestureArea::preventStealing + This property holds whether the mouse events may be stolen from this + MapGestureArea. + + If a Map is placed within an item that filters child mouse + and touch events, such as Flickable, the mouse and touch events + may be stolen from the MapGestureArea if a gesture is recognized + by the parent item, e.g. a flick gesture. If preventStealing is + set to true, no item will steal the mouse and touch events. + + Note that setting preventStealing to true once an item has started + stealing events will have no effect until the next press event. + + By default this property is false. +*/ + +bool QQuickGeoMapGestureArea::preventStealing() const +{ + return m_preventStealing; +} + +void QQuickGeoMapGestureArea::setPreventStealing(bool prevent) +{ + if (prevent != m_preventStealing) { + m_preventStealing = prevent; + m_declarativeMap->setKeepMouseGrab(m_preventStealing && m_enabled); + m_declarativeMap->setKeepTouchGrab(m_preventStealing && m_enabled); + emit preventStealingChanged(); + } +} + +QQuickGeoMapGestureArea::~QQuickGeoMapGestureArea() +{ +} + +/*! + \qmlproperty enumeration QtLocation::MapGestureArea::acceptedGestures + + This property holds the gestures that will be active. By default + the zoom, pan and flick gestures are enabled. + + \list + \li MapGestureArea.NoGesture - Don't support any additional gestures (value: 0x0000). + \li MapGestureArea.PinchGesture - Support the map pinch gesture (value: 0x0001). + \li MapGestureArea.PanGesture - Support the map pan gesture (value: 0x0002). + \li MapGestureArea.FlickGesture - Support the map flick gesture (value: 0x0004). + \endlist +*/ + +QQuickGeoMapGestureArea::AcceptedGestures QQuickGeoMapGestureArea::acceptedGestures() const +{ + return m_acceptedGestures; +} + + +void QQuickGeoMapGestureArea::setAcceptedGestures(AcceptedGestures acceptedGestures) +{ + if (acceptedGestures == m_acceptedGestures) + return; + m_acceptedGestures = acceptedGestures; + + setPanEnabled(acceptedGestures & PanGesture); + setFlickEnabled(acceptedGestures & FlickGesture); + setPinchEnabled(acceptedGestures & PinchGesture); + + emit acceptedGesturesChanged(); +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::isPinchActive() const +{ + return m_pinchState == pinchActive; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::isPanActive() const +{ + return m_flickState == panActive || m_flickState == flickActive; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::enabled() const +{ + return m_enabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setEnabled(bool enabled) +{ + if (enabled == m_enabled) + return; + m_enabled = enabled; + + if (enabled) { + setPanEnabled(m_acceptedGestures & PanGesture); + setFlickEnabled(m_acceptedGestures & FlickGesture); + setPinchEnabled(m_acceptedGestures & PinchGesture); + } else { + setPanEnabled(false); + setFlickEnabled(false); + setPinchEnabled(false); + } + + emit enabledChanged(); +} + + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::pinchEnabled() const +{ + return m_pinch.m_enabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setPinchEnabled(bool enabled) +{ + if (enabled == m_pinch.m_enabled) + return; + m_pinch.m_enabled = enabled; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::panEnabled() const +{ + return m_panEnabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setPanEnabled(bool enabled) +{ + if (enabled == m_flick.m_enabled) + return; + m_panEnabled = enabled; + + // unlike the pinch, the pan existing functionality is to stop immediately + if (!enabled) + stopPan(); +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::flickEnabled() const +{ + return m_flick.m_enabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setFlickEnabled(bool enabled) +{ + if (enabled == m_flick.m_enabled) + return; + m_flick.m_enabled = enabled; + // unlike the pinch, the flick existing functionality is to stop immediately + if (!enabled) { + stopFlick(); + } +} + +/*! + \internal + Used internally to set the minimum zoom level of the gesture area. + The caller is responsible to only send values that are valid + for the map plugin. Negative values are ignored. + */ +void QQuickGeoMapGestureArea::setMinimumZoomLevel(qreal min) +{ + if (min >= 0) + m_pinch.m_zoom.m_minimum = min; +} + +/*! + \internal + */ +qreal QQuickGeoMapGestureArea::minimumZoomLevel() const +{ + return m_pinch.m_zoom.m_minimum; +} + +/*! + \internal + Used internally to set the maximum zoom level of the gesture area. + The caller is responsible to only send values that are valid + for the map plugin. Negative values are ignored. + */ +void QQuickGeoMapGestureArea::setMaximumZoomLevel(qreal max) +{ + if (max >= 0) + m_pinch.m_zoom.m_maximum = max; +} + +/*! + \internal + */ +qreal QQuickGeoMapGestureArea::maximumZoomLevel() const +{ + return m_pinch.m_zoom.m_maximum; +} + +/*! + \internal +*/ +qreal QQuickGeoMapGestureArea::maximumZoomLevelChange() const +{ + return m_pinch.m_zoom.maximumChange; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setMaximumZoomLevelChange(qreal maxChange) +{ + if (maxChange == m_pinch.m_zoom.maximumChange || maxChange < 0.1 || maxChange > 10.0) + return; + m_pinch.m_zoom.maximumChange = maxChange; + emit maximumZoomLevelChangeChanged(); +} + +/*! + \internal +*/ +qreal QQuickGeoMapGestureArea::flickDeceleration() const +{ + return m_flick.m_deceleration; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setFlickDeceleration(qreal deceleration) +{ + if (deceleration < QML_MAP_FLICK_MINIMUMDECELERATION) + deceleration = QML_MAP_FLICK_MINIMUMDECELERATION; + else if (deceleration > QML_MAP_FLICK_MAXIMUMDECELERATION) + deceleration = QML_MAP_FLICK_MAXIMUMDECELERATION; + if (deceleration == m_flick.m_deceleration) + return; + m_flick.m_deceleration = deceleration; + emit flickDecelerationChanged(); +} + +/*! + \internal +*/ +QTouchEvent::TouchPoint* createTouchPointFromMouseEvent(QMouseEvent *event, Qt::TouchPointState state) +{ + // this is only partially filled. But since it is only partially used it works + // more robust would be to store a list of QPointFs rather than TouchPoints + QTouchEvent::TouchPoint* newPoint = new QTouchEvent::TouchPoint(); + newPoint->setPos(event->localPos()); + newPoint->setScenePos(event->windowPos()); + newPoint->setScreenPos(event->screenPos()); + newPoint->setState(state); + newPoint->setId(0); + return newPoint; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleMousePressEvent(QMouseEvent *event) +{ + m_mousePoint.reset(createTouchPointFromMouseEvent(event, Qt::TouchPointPressed)); + if (m_touchPoints.isEmpty()) update(); + event->accept(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleMouseMoveEvent(QMouseEvent *event) +{ + m_mousePoint.reset(createTouchPointFromMouseEvent(event, Qt::TouchPointMoved)); + if (m_touchPoints.isEmpty()) update(); + event->accept(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleMouseReleaseEvent(QMouseEvent *event) +{ + if (!m_mousePoint.isNull()) { + //this looks super ugly , however is required in case we do not get synthesized MouseReleaseEvent + //and we reset the point already in handleTouchUngrabEvent + m_mousePoint.reset(createTouchPointFromMouseEvent(event, Qt::TouchPointReleased)); + if (m_touchPoints.isEmpty()) update(); + } + event->accept(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleMouseUngrabEvent() +{ + + if (m_touchPoints.isEmpty() && !m_mousePoint.isNull()) { + m_mousePoint.reset(); + update(); + } else { + m_mousePoint.reset(); + } +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleTouchUngrabEvent() +{ + m_touchPoints.clear(); + //this is needed since in some cases mouse release is not delivered + //(second touch point brakes mouse synthesized events) + m_mousePoint.reset(); + update(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleTouchEvent(QTouchEvent *event) +{ + m_touchPoints.clear(); + for (int i = 0; i < event->touchPoints().count(); ++i) + m_touchPoints << event->touchPoints().at(i); + if (event->touchPoints().count() >= 2) + event->accept(); + else + event->ignore(); + update(); +} + +void QQuickGeoMapGestureArea::handleWheelEvent(QWheelEvent *event) +{ + if (!m_map) + return; + + QGeoCoordinate wheelGeoPos = m_map->itemPositionToCoordinate(QDoubleVector2D(event->posF()), false); + QPointF preZoomPoint = m_map->coordinateToItemPosition(wheelGeoPos, false).toPointF(); + + double zoomLevelDelta = event->angleDelta().y() * qreal(0.001); + m_declarativeMap->setZoomLevel(m_declarativeMap->zoomLevel() + zoomLevelDelta); + QPointF postZoomPoint = m_map->coordinateToItemPosition(wheelGeoPos, false).toPointF(); + + if (preZoomPoint != postZoomPoint) + { + qreal dx = postZoomPoint.x() - preZoomPoint.x(); + qreal dy = postZoomPoint.y() - preZoomPoint.y(); + QPointF mapCenterPoint(m_map->width() / 2.0 + dx, m_map->height() / 2.0 + dy); + + QGeoCoordinate mapCenterCoordinate = m_map->itemPositionToCoordinate(QDoubleVector2D(mapCenterPoint), false); + m_declarativeMap->setCenter(mapCenterCoordinate); + } + event->accept(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::clearTouchData() +{ + m_velocityX = 0; + m_velocityY = 0; + m_sceneCenter.setX(0); + m_sceneCenter.setY(0); + m_touchCenterCoord.setLongitude(0); + m_touchCenterCoord.setLatitude(0); + m_startCoord.setLongitude(0); + m_startCoord.setLatitude(0); +} + + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updateVelocityList(const QPointF &pos) +{ + // Take velocity samples every sufficient period of time, used later to determine the flick + // duration and speed (when mouse is released). + qreal elapsed = qreal(m_lastPosTime.elapsed()); + + if (elapsed >= QML_MAP_FLICK_VELOCITY_SAMPLE_PERIOD) { + elapsed /= 1000.; + int dyFromLastPos = pos.y() - m_lastPos.y(); + int dxFromLastPos = pos.x() - m_lastPos.x(); + m_lastPos = pos; + m_lastPosTime.restart(); + qreal velX = qreal(dxFromLastPos) / elapsed; + qreal velY = qreal(dyFromLastPos) / elapsed; + m_velocityX = qBound(-m_flick.m_maxVelocity, velX, m_flick.m_maxVelocity); + m_velocityY = qBound(-m_flick.m_maxVelocity, velY, m_flick.m_maxVelocity); + } +} + +/*! + \internal +*/ + +bool QQuickGeoMapGestureArea::isActive() const +{ + return isPanActive() || isPinchActive(); +} + +/*! + \internal +*/ +// simplify the gestures by using a state-machine format (easy to move to a future state machine) +void QQuickGeoMapGestureArea::update() +{ + if (!m_map) + return; + + // First state machine is for the number of touch points + + //combine touch with mouse event + m_allPoints.clear(); + m_allPoints << m_touchPoints; + if (m_allPoints.isEmpty() && !m_mousePoint.isNull()) + m_allPoints << *m_mousePoint.data(); + + touchPointStateMachine(); + + // Parallel state machine for pinch + if (isPinchActive() || (m_enabled && m_pinch.m_enabled && (m_acceptedGestures & (PinchGesture)))) + pinchStateMachine(); + + // Parallel state machine for pan (since you can pan at the same time as pinching) + // The stopPan function ensures that pan stops immediately when disabled, + // but the line below allows pan continue its current gesture if you disable + // the whole gesture (enabled_ flag), this keeps the enabled_ consistent with the pinch + if (isPanActive() || (m_enabled && m_flick.m_enabled && (m_acceptedGestures & (PanGesture | FlickGesture)))) + panStateMachine(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::touchPointStateMachine() +{ + // Transitions: + switch (m_touchPointState) { + case touchPoints0: + if (m_allPoints.count() == 1) { + clearTouchData(); + startOneTouchPoint(); + m_touchPointState = touchPoints1; + } else if (m_allPoints.count() >= 2) { + clearTouchData(); + startTwoTouchPoints(); + m_touchPointState = touchPoints2; + } + break; + case touchPoints1: + if (m_allPoints.count() == 0) { + m_touchPointState = touchPoints0; + } else if (m_allPoints.count() == 2) { + m_touchCenterCoord = m_map->itemPositionToCoordinate(QDoubleVector2D(m_sceneCenter), false); + startTwoTouchPoints(); + m_touchPointState = touchPoints2; + } + break; + case touchPoints2: + if (m_allPoints.count() == 0) { + m_touchPointState = touchPoints0; + } else if (m_allPoints.count() == 1) { + m_touchCenterCoord = m_map->itemPositionToCoordinate(QDoubleVector2D(m_sceneCenter), false); + startOneTouchPoint(); + m_touchPointState = touchPoints1; + } + break; + }; + + // Update + switch (m_touchPointState) { + case touchPoints0: + break; // do nothing if no touch points down + case touchPoints1: + updateOneTouchPoint(); + break; + case touchPoints2: + updateTwoTouchPoints(); + break; + } +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startOneTouchPoint() +{ + m_sceneStartPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_lastPos = m_sceneStartPoint1; + m_lastPosTime.start(); + QGeoCoordinate startCoord = m_map->itemPositionToCoordinate(QDoubleVector2D(m_sceneStartPoint1), false); + // ensures a smooth transition for panning + m_startCoord.setLongitude(m_startCoord.longitude() + startCoord.longitude() - + m_touchCenterCoord.longitude()); + m_startCoord.setLatitude(m_startCoord.latitude() + startCoord.latitude() - + m_touchCenterCoord.latitude()); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updateOneTouchPoint() +{ + m_sceneCenter = mapFromScene(m_allPoints.at(0).scenePos()); + updateVelocityList(m_sceneCenter); +} + + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startTwoTouchPoints() +{ + m_sceneStartPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_sceneStartPoint2 = mapFromScene(m_allPoints.at(1).scenePos()); + QPointF startPos = (m_sceneStartPoint1 + m_sceneStartPoint2) * 0.5; + m_lastPos = startPos; + m_lastPosTime.start(); + QGeoCoordinate startCoord = m_map->itemPositionToCoordinate(QDoubleVector2D(startPos), false); + m_startCoord.setLongitude(m_startCoord.longitude() + startCoord.longitude() - + m_touchCenterCoord.longitude()); + m_startCoord.setLatitude(m_startCoord.latitude() + startCoord.latitude() - + m_touchCenterCoord.latitude()); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updateTwoTouchPoints() +{ + QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos()); + QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos()); + qreal dx = p1.x() - p2.x(); + qreal dy = p1.y() - p2.y(); + m_distanceBetweenTouchPoints = sqrt(dx * dx + dy * dy); + m_sceneCenter = (p1 + p2) / 2; + updateVelocityList(m_sceneCenter); + + m_twoTouchAngle = QLineF(p1, p2).angle(); + if (m_twoTouchAngle > 180) + m_twoTouchAngle -= 360; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::pinchStateMachine() +{ + PinchState lastState = m_pinchState; + // Transitions: + switch (m_pinchState) { + case pinchInactive: + if (m_allPoints.count() >= 2) { + if (canStartPinch()) { + m_declarativeMap->setKeepMouseGrab(true); + m_declarativeMap->setKeepTouchGrab(true); + startPinch(); + m_pinchState = pinchActive; + } else { + m_pinchState = pinchInactiveTwoPoints; + } + } + break; + case pinchInactiveTwoPoints: + if (m_allPoints.count() <= 1) { + m_pinchState = pinchInactive; + } else { + if (canStartPinch()) { + m_declarativeMap->setKeepMouseGrab(true); + m_declarativeMap->setKeepTouchGrab(true); + startPinch(); + m_pinchState = pinchActive; + } + } + break; + case pinchActive: + if (m_allPoints.count() <= 1) { + m_pinchState = pinchInactive; + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + m_declarativeMap->setKeepTouchGrab(m_preventStealing); + endPinch(); + } + break; + } + // This line implements an exclusive state machine, where the transitions and updates don't + // happen on the same frame + if (m_pinchState != lastState) { + emit pinchActiveChanged(); + return; + } + + // Update + switch (m_pinchState) { + case pinchInactive: + case pinchInactiveTwoPoints: + break; // do nothing + case pinchActive: + updatePinch(); + break; + } +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::canStartPinch() +{ + const int startDragDistance = qApp->styleHints()->startDragDistance(); + + if (m_allPoints.count() >= 2) { + QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos()); + QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos()); + if (qAbs(p1.x()-m_sceneStartPoint1.x()) > startDragDistance + || qAbs(p1.y()-m_sceneStartPoint1.y()) > startDragDistance + || qAbs(p2.x()-m_sceneStartPoint2.x()) > startDragDistance + || qAbs(p2.y()-m_sceneStartPoint2.y()) > startDragDistance) { + m_pinch.m_event.setCenter(mapFromScene(m_sceneCenter)); + m_pinch.m_event.setAngle(m_twoTouchAngle); + m_pinch.m_event.setPoint1(p1); + m_pinch.m_event.setPoint2(p2); + m_pinch.m_event.setPointCount(m_allPoints.count()); + m_pinch.m_event.setAccepted(true); + emit pinchStarted(&m_pinch.m_event); + return m_pinch.m_event.accepted(); + } + } + return false; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startPinch() +{ + m_pinch.m_startDist = m_distanceBetweenTouchPoints; + m_pinch.m_zoom.m_previous = m_declarativeMap->zoomLevel(); + m_pinch.m_lastAngle = m_twoTouchAngle; + + m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos()); + + m_pinch.m_zoom.m_start = m_declarativeMap->zoomLevel(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updatePinch() +{ + // Calculate the new zoom level if we have distance ( >= 2 touchpoints), otherwise stick with old. + qreal newZoomLevel = m_pinch.m_zoom.m_previous; + if (m_distanceBetweenTouchPoints) { + newZoomLevel = + // How much further/closer the current touchpoints are (in pixels) compared to pinch start + ((m_distanceBetweenTouchPoints - m_pinch.m_startDist) * + // How much one pixel corresponds in units of zoomlevel (and multiply by above delta) + (m_pinch.m_zoom.maximumChange / ((width() + height()) / 2))) + + // Add to starting zoom level. Sign of (dist-pinchstartdist) takes care of zoom in / out + m_pinch.m_zoom.m_start; + } + qreal da = m_pinch.m_lastAngle - m_twoTouchAngle; + if (da > 180) + da -= 360; + else if (da < -180) + da += 360; + m_pinch.m_event.setCenter(mapFromScene(m_sceneCenter)); + m_pinch.m_event.setAngle(m_twoTouchAngle); + + m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos()); + m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1); + m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2); + m_pinch.m_event.setPointCount(m_allPoints.count()); + m_pinch.m_event.setAccepted(true); + + m_pinch.m_lastAngle = m_twoTouchAngle; + emit pinchUpdated(&m_pinch.m_event); + + if (m_acceptedGestures & PinchGesture) { + // Take maximum and minimumzoomlevel into account + qreal perPinchMinimumZoomLevel = qMax(m_pinch.m_zoom.m_start - m_pinch.m_zoom.maximumChange, m_pinch.m_zoom.m_minimum); + qreal perPinchMaximumZoomLevel = qMin(m_pinch.m_zoom.m_start + m_pinch.m_zoom.maximumChange, m_pinch.m_zoom.m_maximum); + newZoomLevel = qMin(qMax(perPinchMinimumZoomLevel, newZoomLevel), perPinchMaximumZoomLevel); + m_declarativeMap->setZoomLevel(newZoomLevel); + m_pinch.m_zoom.m_previous = newZoomLevel; + } +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::endPinch() +{ + QPointF p1 = mapFromScene(m_pinch.m_lastPoint1); + QPointF p2 = mapFromScene(m_pinch.m_lastPoint2); + m_pinch.m_event.setCenter((p1 + p2) / 2); + m_pinch.m_event.setAngle(m_pinch.m_lastAngle); + m_pinch.m_event.setPoint1(p1); + m_pinch.m_event.setPoint2(p2); + m_pinch.m_event.setAccepted(true); + m_pinch.m_event.setPointCount(0); + emit pinchFinished(&m_pinch.m_event); + m_pinch.m_startDist = 0; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::panStateMachine() +{ + FlickState lastState = m_flickState; + + // Transitions + switch (m_flickState) { + case flickInactive: + if (canStartPan()) { + // Update startCoord_ to ensure smooth start for panning when going over startDragDistance + QGeoCoordinate newStartCoord = m_map->itemPositionToCoordinate(QDoubleVector2D(m_sceneCenter), false); + m_startCoord.setLongitude(newStartCoord.longitude()); + m_startCoord.setLatitude(newStartCoord.latitude()); + m_declarativeMap->setKeepMouseGrab(true); + m_flickState = panActive; + } + break; + case panActive: + if (m_allPoints.count() == 0) { + if (!tryStartFlick()) + { + m_flickState = flickInactive; + // mark as inactive for use by camera + if (m_pinchState == pinchInactive) { + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + m_map->prefetchData(); + } + emit panFinished(); + } else { + m_flickState = flickActive; + emit panFinished(); + emit flickStarted(); + } + } + break; + case flickActive: + if (m_allPoints.count() > 0) { // re touched before movement ended + stopFlick(); + m_declarativeMap->setKeepMouseGrab(true); + m_flickState = panActive; + } + break; + } + + if (m_flickState != lastState) + emit panActiveChanged(); + + // Update + switch (m_flickState) { + case flickInactive: // do nothing + break; + case panActive: + updatePan(); + // this ensures 'panStarted' occurs after the pan has actually started + if (lastState != panActive) + emit panStarted(); + break; + case flickActive: + break; + } +} +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::canStartPan() +{ + if (m_allPoints.count() == 0 || (m_acceptedGestures & PanGesture) == 0) + return false; + + // Check if thresholds for normal panning are met. + // (normal panning vs flicking: flicking will start from mouse release event). + const int startDragDistance = qApp->styleHints()->startDragDistance() * 2; + QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos()); + int dyFromPress = int(p1.y() - m_sceneStartPoint1.y()); + int dxFromPress = int(p1.x() - m_sceneStartPoint1.x()); + if ((qAbs(dyFromPress) >= startDragDistance || qAbs(dxFromPress) >= startDragDistance)) + return true; + return false; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updatePan() +{ + QPointF startPoint = m_map->coordinateToItemPosition(m_startCoord, false).toPointF(); + int dx = static_cast(m_sceneCenter.x() - startPoint.x()); + int dy = static_cast(m_sceneCenter.y() - startPoint.y()); + QPointF mapCenterPoint; + mapCenterPoint.setY(m_map->height() / 2.0 - dy); + mapCenterPoint.setX(m_map->width() / 2.0 - dx); + QGeoCoordinate animationStartCoordinate = m_map->itemPositionToCoordinate(QDoubleVector2D(mapCenterPoint), false); + m_declarativeMap->setCenter(animationStartCoordinate); +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::tryStartFlick() +{ + if ((m_acceptedGestures & FlickGesture) == 0) + return false; + // if we drag then pause before release we should not cause a flick. + qreal velocityX = 0.0; + qreal velocityY = 0.0; + if (m_lastPosTime.elapsed() < QML_MAP_FLICK_VELOCITY_SAMPLE_PERIOD) { + velocityY = m_velocityY; + velocityX = m_velocityX; + } + int flickTimeY = 0; + int flickTimeX = 0; + int flickPixelsX = 0; + int flickPixelsY = 0; + if (qAbs(velocityY) > MinimumFlickVelocity && qAbs(m_sceneCenter.y() - m_sceneStartPoint1.y()) > FlickThreshold) { + // calculate Y flick animation values + qreal acceleration = m_flick.m_deceleration; + if ((velocityY > 0.0f) == (m_flick.m_deceleration > 0.0f)) + acceleration = acceleration * -1.0f; + flickTimeY = static_cast(-1000 * velocityY / acceleration); + flickPixelsY = (flickTimeY * velocityY) / (1000.0 * 2); + } + if (qAbs(velocityX) > MinimumFlickVelocity && qAbs(m_sceneCenter.x() - m_sceneStartPoint1.x()) > FlickThreshold) { + // calculate X flick animation values + qreal acceleration = m_flick.m_deceleration; + if ((velocityX > 0.0f) == (m_flick.m_deceleration > 0.0f)) + acceleration = acceleration * -1.0f; + flickTimeX = static_cast(-1000 * velocityX / acceleration); + flickPixelsX = (flickTimeX * velocityX) / (1000.0 * 2); + } + int flickTime = qMax(flickTimeY, flickTimeX); + if (flickTime > 0) { + startFlick(flickPixelsX, flickPixelsY, flickTime); + return true; + } + return false; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startFlick(int dx, int dy, int timeMs) +{ + if (!m_flick.m_animation) + return; + if (timeMs < 0) + return; + + QGeoCoordinate animationStartCoordinate = m_declarativeMap->center(); + + if (m_flick.m_animation->isRunning()) + m_flick.m_animation->stop(); + QGeoCoordinate animationEndCoordinate = m_declarativeMap->center(); + m_flick.m_animation->setDuration(timeMs); + + double zoom = pow(2.0, m_declarativeMap->zoomLevel()); + double longitude = animationStartCoordinate.longitude() - (dx / zoom); + double latitude = animationStartCoordinate.latitude() + (dy / zoom); + + if (dx > 0) + m_flick.m_animation->setDirection(QQuickGeoCoordinateAnimation::East); + else + m_flick.m_animation->setDirection(QQuickGeoCoordinateAnimation::West); + + //keep animation in correct bounds + if (latitude > 85.05113) + latitude = 85.05113; + else if (latitude < -85.05113) + latitude = -85.05113; + + if (longitude > 180) + longitude = longitude - 360; + else if (longitude < -180) + longitude = longitude + 360; + + animationEndCoordinate.setLongitude(longitude); + animationEndCoordinate.setLatitude(latitude); + + m_flick.m_animation->setFrom(animationStartCoordinate); + m_flick.m_animation->setTo(animationEndCoordinate); + m_flick.m_animation->start(); +} + +void QQuickGeoMapGestureArea::stopPan() +{ + if (m_flickState == flickActive) { + stopFlick(); + } else if (m_flickState == panActive) { + m_velocityX = 0; + m_velocityY = 0; + m_flickState = flickInactive; + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + emit panFinished(); + emit panActiveChanged(); + m_map->prefetchData(); + } +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::stopFlick() +{ + if (!m_flick.m_animation) + return; + m_velocityX = 0; + m_velocityY = 0; + if (m_flick.m_animation->isRunning()) + m_flick.m_animation->stop(); + else + handleFlickAnimationStopped(); +} + +void QQuickGeoMapGestureArea::handleFlickAnimationStopped() +{ + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + if (m_flickState == flickActive) { + m_flickState = flickInactive; + emit flickFinished(); + m_map->prefetchData(); + } +} + +#include "moc_qquickgeomapgesturearea_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/location/qquickgeomapgesturearea_p.h b/src/imports/location/qquickgeomapgesturearea_p.h new file mode 100644 index 0000000..51c5cc1 --- /dev/null +++ b/src/imports/location/qquickgeomapgesturearea_p.h @@ -0,0 +1,317 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKGEOMAPGESTUREAREA_P_H +#define QQUICKGEOMAPGESTUREAREA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include "qgeocoordinate.h" +#include "qgeomap_p.h" +#include "qquickgeocoordinateanimation_p.h" + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneMouseEvent; +class QDeclarativeGeoMap; +class QTouchEvent; +class QWheelEvent; +class QGeoMap; + +class QGeoMapPinchEvent : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPointF center READ center) + Q_PROPERTY(qreal angle READ angle) + Q_PROPERTY(QPointF point1 READ point1) + Q_PROPERTY(QPointF point2 READ point2) + Q_PROPERTY(int pointCount READ pointCount) + Q_PROPERTY(bool accepted READ accepted WRITE setAccepted) + +public: + QGeoMapPinchEvent(const QPointF ¢er, qreal angle, + const QPointF &point1, const QPointF &point2, + int pointCount = 0, bool accepted = true) + : QObject(), m_center(center), m_angle(angle), + m_point1(point1), m_point2(point2), + m_pointCount(pointCount), m_accepted(accepted) {} + QGeoMapPinchEvent() + : QObject(), + m_angle(0.0), + m_pointCount(0), + m_accepted(true) {} + + QPointF center() const { return m_center; } + void setCenter(const QPointF ¢er) { m_center = center; } + qreal angle() const { return m_angle; } + void setAngle(qreal angle) { m_angle = angle; } + QPointF point1() const { return m_point1; } + void setPoint1(const QPointF &p) { m_point1 = p; } + QPointF point2() const { return m_point2; } + void setPoint2(const QPointF &p) { m_point2 = p; } + int pointCount() const { return m_pointCount; } + void setPointCount(int count) { m_pointCount = count; } + bool accepted() const { return m_accepted; } + void setAccepted(bool a) { m_accepted = a; } + +private: + QPointF m_center; + qreal m_angle; + QPointF m_point1; + QPointF m_point2; + int m_pointCount; + bool m_accepted; +}; + +class QQuickGeoMapGestureArea: public QQuickItem +{ + Q_OBJECT + Q_ENUMS(GeoMapGesture) + Q_FLAGS(AcceptedGestures) + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool pinchActive READ isPinchActive NOTIFY pinchActiveChanged) + Q_PROPERTY(bool panActive READ isPanActive NOTIFY panActiveChanged) + Q_PROPERTY(AcceptedGestures acceptedGestures READ acceptedGestures WRITE setAcceptedGestures NOTIFY acceptedGesturesChanged) + Q_PROPERTY(qreal maximumZoomLevelChange READ maximumZoomLevelChange WRITE setMaximumZoomLevelChange NOTIFY maximumZoomLevelChangeChanged) + Q_PROPERTY(qreal flickDeceleration READ flickDeceleration WRITE setFlickDeceleration NOTIFY flickDecelerationChanged) + Q_PROPERTY(bool preventStealing READ preventStealing WRITE setPreventStealing NOTIFY preventStealingChanged REVISION 1) + +public: + QQuickGeoMapGestureArea(QDeclarativeGeoMap *map); + ~QQuickGeoMapGestureArea(); + + enum GeoMapGesture { + NoGesture = 0x0000, + PinchGesture = 0x0001, + PanGesture = 0x0002, + FlickGesture = 0x004 + }; + + Q_DECLARE_FLAGS(AcceptedGestures, GeoMapGesture) + + AcceptedGestures acceptedGestures() const; + void setAcceptedGestures(AcceptedGestures acceptedGestures); + + bool isPinchActive() const; + bool isPanActive() const; + bool isActive() const; + + bool enabled() const; + void setEnabled(bool enabled); + + qreal maximumZoomLevelChange() const; + void setMaximumZoomLevelChange(qreal maxChange); + + qreal flickDeceleration() const; + void setFlickDeceleration(qreal deceleration); + + void handleTouchEvent(QTouchEvent *event); + void handleWheelEvent(QWheelEvent *event); + void handleMousePressEvent(QMouseEvent *event); + void handleMouseMoveEvent(QMouseEvent *event); + void handleMouseReleaseEvent(QMouseEvent *event); + void handleMouseUngrabEvent(); + void handleTouchUngrabEvent(); + + void setMinimumZoomLevel(qreal min); + qreal minimumZoomLevel() const; + + void setMaximumZoomLevel(qreal max); + qreal maximumZoomLevel() const; + + void setMap(QGeoMap *map); + + bool preventStealing() const; + void setPreventStealing(bool prevent); + +Q_SIGNALS: + void panActiveChanged(); + void pinchActiveChanged(); + void enabledChanged(); + void maximumZoomLevelChangeChanged(); + void acceptedGesturesChanged(); + void flickDecelerationChanged(); + void pinchStarted(QGeoMapPinchEvent *pinch); + void pinchUpdated(QGeoMapPinchEvent *pinch); + void pinchFinished(QGeoMapPinchEvent *pinch); + void panStarted(); + void panFinished(); + void flickStarted(); + void flickFinished(); + void preventStealingChanged(); +private: + void update(); + + // Create general data relating to the touch points + void touchPointStateMachine(); + void startOneTouchPoint(); + void updateOneTouchPoint(); + void startTwoTouchPoints(); + void updateTwoTouchPoints(); + + // All pinch related code, which encompasses zoom + void pinchStateMachine(); + bool canStartPinch(); + void startPinch(); + void updatePinch(); + void endPinch(); + + // Pan related code (regardles of number of touch points), + // includes the flick based panning after letting go + void panStateMachine(); + bool canStartPan(); + void updatePan(); + bool tryStartFlick(); + void startFlick(int dx, int dy, int timeMs = 0); + void stopFlick(); + + bool pinchEnabled() const; + void setPinchEnabled(bool enabled); + bool panEnabled() const; + void setPanEnabled(bool enabled); + bool flickEnabled() const; + void setFlickEnabled(bool enabled); + +private Q_SLOTS: + void handleFlickAnimationStopped(); + + +private: + void stopPan(); + void clearTouchData(); + void updateVelocityList(const QPointF &pos); + +private: + QGeoMap *m_map; + QDeclarativeGeoMap *m_declarativeMap; + bool m_enabled; + + struct Pinch + { + Pinch() : m_enabled(true), m_startDist(0), m_lastAngle(0.0) {} + + QGeoMapPinchEvent m_event; + bool m_enabled; + struct Zoom + { + Zoom() : m_minimum(-1.0), m_maximum(20.0), m_start(0.0), m_previous(0.0), + maximumChange(4.0) {} + qreal m_minimum; + qreal m_maximum; + qreal m_start; + qreal m_previous; + qreal maximumChange; + } m_zoom; + + QPointF m_lastPoint1; + QPointF m_lastPoint2; + qreal m_startDist; + qreal m_lastAngle; + } m_pinch; + + AcceptedGestures m_acceptedGestures; + + struct Pan + { + qreal m_maxVelocity; + qreal m_deceleration; + QQuickGeoCoordinateAnimation *m_animation; + bool m_enabled; + } m_flick; + + + // these are calculated regardless of gesture or number of touch points + qreal m_velocityX; + qreal m_velocityY; + QElapsedTimer m_lastPosTime; + QPointF m_lastPos; + QList m_allPoints; + QList m_touchPoints; + QScopedPointer m_mousePoint; + QPointF m_sceneStartPoint1; + + // only set when two points in contact + QPointF m_sceneStartPoint2; + QGeoCoordinate m_startCoord; + QGeoCoordinate m_touchCenterCoord; + qreal m_twoTouchAngle; + qreal m_distanceBetweenTouchPoints; + QPointF m_sceneCenter; + bool m_preventStealing; + bool m_panEnabled; + + // prototype state machine... + enum TouchPointState + { + touchPoints0, + touchPoints1, + touchPoints2 + } m_touchPointState; + + enum PinchState + { + pinchInactive, + pinchInactiveTwoPoints, + pinchActive + } m_pinchState; + + enum FlickState + { + flickInactive, + panActive, + flickActive + } m_flickState; +}; + +QT_END_NAMESPACE +QML_DECLARE_TYPE(QQuickGeoMapGestureArea) + +#endif // QQUICKGEOMAPGESTUREAREA_P_H diff --git a/src/imports/positioning/locationsingleton.cpp b/src/imports/positioning/locationsingleton.cpp new file mode 100644 index 0000000..f12486b --- /dev/null +++ b/src/imports/positioning/locationsingleton.cpp @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "locationsingleton.h" + +/*! + \qmltype QtPositioning + \instantiates LocationSingleton + \inqmlmodule QtPositioning + \since 5.2 + + \brief The QtPositioning global object provides useful functions for working with location-based + types in QML. + + \qml + import QtPositioning 5.2 + + Item { + property variant coordinate: QtPositioning.coordinate(-27.5, 153.1) + } + \endqml +*/ + +LocationSingleton::LocationSingleton(QObject *parent) +: QObject(parent) +{ +} + +/*! + \qmlmethod coordinate ::QtPositioning::coordinate() + + Constructs an invalid coordinate. + +*/ +QGeoCoordinate LocationSingleton::coordinate() const +{ + return QGeoCoordinate(); +} + +/*! + \qmlmethod coordinate QtPositioning::coordinate(real latitude, real longitue, real altitude) const + + Constructs a coordinate with the specified \a latitude, \a longitude and optional \a altitude. + Both \a latitude and \a longitude must be valid, otherwise an invalid coordinate is returned. + + \sa {coordinate} +*/ +QGeoCoordinate LocationSingleton::coordinate(double latitude, double longitude, double altitude) const +{ + return QGeoCoordinate(latitude, longitude, altitude); +} + +/*! + \qmlmethod geoshape QtPositioning::shape() const + + Constructs an invalid geoshape. + + \sa {geoshape} +*/ +QGeoShape LocationSingleton::shape() const +{ + return QGeoShape(); +} + +/*! + \qmlmethod georectangle QtPositioning::rectangle() const + + Constructs an invalid georectangle. + + \sa {georectangle} +*/ +QGeoRectangle LocationSingleton::rectangle() const +{ + return QGeoRectangle(); +} + +/*! + \qmlmethod georectangle QtPositioning::rectangle(coordinate center, real width, real height) const + + Constructs a georectangle centered at \a center with a width of \a width degrees and a hight of + \a height degrees. + + \sa {georectangle} +*/ +QGeoRectangle LocationSingleton::rectangle(const QGeoCoordinate ¢er, + double width, double height) const +{ + return QGeoRectangle(center, width, height); +} + +/*! + \qmlmethod georectangle QtPositioning::rectangle(coordinate topLeft, coordinate bottomRight) const + + Constructs a georectangle with its top left corner positioned at \a topLeft and its bottom + right corner positioned at \a {bottomLeft}. + + \sa {georectangle} +*/ +QGeoRectangle LocationSingleton::rectangle(const QGeoCoordinate &topLeft, + const QGeoCoordinate &bottomRight) const +{ + return QGeoRectangle(topLeft, bottomRight); +} + +/*! + \qmlmethod georectangle QtLocation5::QtLocation::rectangle(list coordinates) const + + Constructs a georectangle from the list of coordinates, the returned list is the smallest possible + containing all the coordinates. + + \sa {georectangle} +*/ +QGeoRectangle LocationSingleton::rectangle(const QVariantList &coordinates) const +{ + QList internalCoordinates; + for (int i = 0; i < coordinates.size(); i++) { + if (coordinates.at(i).canConvert()) + internalCoordinates << coordinates.at(i).value(); + } + return QGeoRectangle(internalCoordinates); +} + +/*! + \qmlmethod geocircle QtPositioning::circle() const + + Constructs an invalid geocircle. + + \sa {geocircle} +*/ +QGeoCircle LocationSingleton::circle() const +{ + return QGeoCircle(); +} + +/*! + \qmlmethod geocircle QtPositioning::circle(coordinate center, real radius) const + + Constructs a geocircle centered at \a center with a radius of \a radius meters. +*/ +QGeoCircle LocationSingleton::circle(const QGeoCoordinate ¢er, qreal radius) const +{ + return QGeoCircle(center, radius); +} + +/*! + \qmlmethod geocircle QtPositioning::shapeToCircle(geoshape shape) const + + Converts \a shape to a geocircle. + + \sa {geocircle} + \since 5.5 +*/ +QGeoCircle LocationSingleton::shapeToCircle(const QGeoShape &shape) const +{ + return QGeoCircle(shape); +} + +/*! + \qmlmethod georectangle QtPositioning::shapeToRectangle(geoshape shape) const + + Converts \a shape to a georectangle. + + \sa {georectangle} + \since 5.5 +*/ +QGeoRectangle LocationSingleton::shapeToRectangle(const QGeoShape &shape) const +{ + return QGeoRectangle(shape); +} + diff --git a/src/imports/positioning/locationsingleton.h b/src/imports/positioning/locationsingleton.h new file mode 100644 index 0000000..cc4ea5c --- /dev/null +++ b/src/imports/positioning/locationsingleton.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef LOCATIONSINGLETON_H +#define LOCATIONSINGLETON_H + +#include +#include +#include +#include +#include +#include +#include + +class LocationSingleton : public QObject +{ + Q_OBJECT + +public: + explicit LocationSingleton(QObject *parent = 0); + + Q_INVOKABLE QGeoCoordinate coordinate() const; + Q_INVOKABLE QGeoCoordinate coordinate(double latitude, double longitude, + double altitude = qQNaN()) const; + + Q_INVOKABLE QGeoShape shape() const; + + Q_INVOKABLE QGeoRectangle rectangle() const; + Q_INVOKABLE QGeoRectangle rectangle(const QGeoCoordinate ¢er, + double width, double height) const; + Q_INVOKABLE QGeoRectangle rectangle(const QGeoCoordinate &topLeft, + const QGeoCoordinate &bottomRight) const; + Q_INVOKABLE QGeoRectangle rectangle(const QVariantList &coordinates) const; + + Q_INVOKABLE QGeoCircle circle() const; + Q_INVOKABLE QGeoCircle circle(const QGeoCoordinate ¢er, qreal radius = -1.0) const; + + Q_INVOKABLE QGeoCircle shapeToCircle(const QGeoShape &shape) const; + Q_INVOKABLE QGeoRectangle shapeToRectangle(const QGeoShape &shape) const; +}; + +#endif // LOCATIONSINGLETON_H diff --git a/src/imports/positioning/plugin.json b/src/imports/positioning/plugin.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/src/imports/positioning/plugin.json @@ -0,0 +1,2 @@ +{ +} diff --git a/src/imports/positioning/plugins.qmltypes b/src/imports/positioning/plugins.qmltypes new file mode 100644 index 0000000..55fcbba --- /dev/null +++ b/src/imports/positioning/plugins.qmltypes @@ -0,0 +1,224 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -nonrelocatable QtPositioning 5.7' + +Module { + dependencies: ["QtQuick 2.0"] + Component { + name: "LocationSingleton" + prototype: "QObject" + exports: ["QtPositioning/QtPositioning 5.0"] + isCreatable: false + isSingleton: true + exportMetaObjectRevisions: [0] + Method { name: "coordinate"; type: "QGeoCoordinate" } + Method { + name: "coordinate" + type: "QGeoCoordinate" + Parameter { name: "latitude"; type: "double" } + Parameter { name: "longitude"; type: "double" } + Parameter { name: "altitude"; type: "double" } + } + Method { + name: "coordinate" + type: "QGeoCoordinate" + Parameter { name: "latitude"; type: "double" } + Parameter { name: "longitude"; type: "double" } + } + Method { name: "shape"; type: "QGeoShape" } + Method { name: "rectangle"; type: "QGeoRectangle" } + Method { + name: "rectangle" + type: "QGeoRectangle" + Parameter { name: "center"; type: "QGeoCoordinate" } + Parameter { name: "width"; type: "double" } + Parameter { name: "height"; type: "double" } + } + Method { + name: "rectangle" + type: "QGeoRectangle" + Parameter { name: "topLeft"; type: "QGeoCoordinate" } + Parameter { name: "bottomRight"; type: "QGeoCoordinate" } + } + Method { + name: "rectangle" + type: "QGeoRectangle" + Parameter { name: "coordinates"; type: "QVariantList" } + } + Method { name: "circle"; type: "QGeoCircle" } + Method { + name: "circle" + type: "QGeoCircle" + Parameter { name: "center"; type: "QGeoCoordinate" } + Parameter { name: "radius"; type: "double" } + } + Method { + name: "circle" + type: "QGeoCircle" + Parameter { name: "center"; type: "QGeoCoordinate" } + } + Method { + name: "shapeToCircle" + type: "QGeoCircle" + Parameter { name: "shape"; type: "QGeoShape" } + } + Method { + name: "shapeToRectangle" + type: "QGeoRectangle" + Parameter { name: "shape"; type: "QGeoShape" } + } + } + Component { + name: "QDeclarativeGeoAddress" + prototype: "QObject" + exports: ["QtPositioning/Address 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "address"; type: "QGeoAddress" } + Property { name: "text"; type: "string" } + Property { name: "country"; type: "string" } + Property { name: "countryCode"; type: "string" } + Property { name: "state"; type: "string" } + Property { name: "county"; type: "string" } + Property { name: "city"; type: "string" } + Property { name: "district"; type: "string" } + Property { name: "street"; type: "string" } + Property { name: "postalCode"; type: "string" } + Property { name: "isTextGenerated"; type: "bool"; isReadonly: true } + } + Component { + name: "QDeclarativeGeoLocation" + prototype: "QObject" + exports: ["QtPositioning/Location 5.0"] + exportMetaObjectRevisions: [0] + Property { name: "location"; type: "QGeoLocation" } + Property { name: "address"; type: "QDeclarativeGeoAddress"; isPointer: true } + Property { name: "coordinate"; type: "QGeoCoordinate" } + Property { name: "boundingBox"; type: "QGeoRectangle" } + } + Component { + name: "QDeclarativePosition" + prototype: "QObject" + exports: [ + "QtPositioning/Position 5.0", + "QtPositioning/Position 5.3", + "QtPositioning/Position 5.4", + "QtPositioning/Position 5.7" + ] + exportMetaObjectRevisions: [0, 1, 2, 2] + Property { name: "latitudeValid"; type: "bool"; isReadonly: true } + Property { name: "longitudeValid"; type: "bool"; isReadonly: true } + Property { name: "altitudeValid"; type: "bool"; isReadonly: true } + Property { name: "coordinate"; type: "QGeoCoordinate"; isReadonly: true } + Property { name: "timestamp"; type: "QDateTime"; isReadonly: true } + Property { name: "speed"; type: "double"; isReadonly: true } + Property { name: "speedValid"; type: "bool"; isReadonly: true } + Property { name: "horizontalAccuracy"; type: "double" } + Property { name: "verticalAccuracy"; type: "double" } + Property { name: "horizontalAccuracyValid"; type: "bool"; isReadonly: true } + Property { name: "verticalAccuracyValid"; type: "bool"; isReadonly: true } + Property { name: "directionValid"; revision: 1; type: "bool"; isReadonly: true } + Property { name: "direction"; revision: 1; type: "double"; isReadonly: true } + Property { name: "verticalSpeedValid"; revision: 1; type: "bool"; isReadonly: true } + Property { name: "verticalSpeed"; revision: 1; type: "double"; isReadonly: true } + Property { name: "magneticVariation"; revision: 2; type: "double"; isReadonly: true } + Property { name: "magneticVariationValid"; revision: 2; type: "bool"; isReadonly: true } + Signal { name: "directionValidChanged"; revision: 1 } + Signal { name: "directionChanged"; revision: 1 } + Signal { name: "verticalSpeedValidChanged"; revision: 1 } + Signal { name: "verticalSpeedChanged"; revision: 1 } + Signal { name: "magneticVariationChanged"; revision: 2 } + Signal { name: "magneticVariationValidChanged"; revision: 2 } + } + Component { + name: "QDeclarativePositionSource" + prototype: "QObject" + exports: ["QtPositioning/PositionSource 5.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "PositioningMethod" + values: { + "NoPositioningMethods": 0, + "SatellitePositioningMethods": 255, + "NonSatellitePositioningMethods": -256, + "AllPositioningMethods": -1 + } + } + Enum { + name: "PositioningMethods" + values: { + "NoPositioningMethods": 0, + "SatellitePositioningMethods": 255, + "NonSatellitePositioningMethods": -256, + "AllPositioningMethods": -1 + } + } + Enum { + name: "SourceError" + values: { + "AccessError": 0, + "ClosedError": 1, + "UnknownSourceError": 2, + "NoError": 3, + "SocketError": 100 + } + } + Property { name: "position"; type: "QDeclarativePosition"; isReadonly: true; isPointer: true } + Property { name: "active"; type: "bool" } + Property { name: "valid"; type: "bool"; isReadonly: true } + Property { name: "nmeaSource"; type: "QUrl" } + Property { name: "updateInterval"; type: "int" } + Property { name: "supportedPositioningMethods"; type: "PositioningMethods"; isReadonly: true } + Property { name: "preferredPositioningMethods"; type: "PositioningMethods" } + Property { name: "sourceError"; type: "SourceError"; isReadonly: true } + Property { name: "name"; type: "string" } + Signal { name: "validityChanged" } + Signal { name: "updateTimeout" } + Method { name: "update" } + Method { name: "start" } + Method { name: "stop" } + } + Component { + name: "QGeoShape" + exports: ["QtPositioning/GeoShape 5.0"] + isCreatable: false + exportMetaObjectRevisions: [0] + Enum { + name: "ShapeType" + values: { + "UnknownType": 0, + "RectangleType": 1, + "CircleType": 2 + } + } + Property { name: "type"; type: "ShapeType"; isReadonly: true } + Property { name: "isValid"; type: "bool"; isReadonly: true } + Property { name: "isEmpty"; type: "bool"; isReadonly: true } + Method { + name: "contains" + type: "bool" + Parameter { name: "coordinate"; type: "QGeoCoordinate" } + } + Method { name: "toString"; type: "string" } + } + Component { + name: "QQuickGeoCoordinateAnimation" + prototype: "QQuickPropertyAnimation" + exports: ["QtPositioning/CoordinateAnimation 5.3"] + exportMetaObjectRevisions: [0] + Enum { + name: "Direction" + values: { + "Shortest": 0, + "West": 1, + "East": 2 + } + } + Property { name: "from"; type: "QGeoCoordinate" } + Property { name: "to"; type: "QGeoCoordinate" } + Property { name: "direction"; type: "Direction" } + } +} diff --git a/src/imports/positioning/positioning.cpp b/src/imports/positioning/positioning.cpp new file mode 100644 index 0000000..5137da2 --- /dev/null +++ b/src/imports/positioning/positioning.cpp @@ -0,0 +1,566 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include +#include +#include + +#include "qdeclarativepositionsource_p.h" +#include "qdeclarativeposition_p.h" + +#include "qquickgeocoordinateanimation_p.h" +#include "locationsingleton.h" + +#include + +#include +#include + +#include +#include +#include + +#include + +static void initResources() +{ +#ifdef QT_STATIC + Q_INIT_RESOURCE(qmake_QtPositioning); +#endif +} + +QT_BEGIN_NAMESPACE + +/*! + \qmlbasictype coordinate + \inqmlmodule QtPositioning + \ingroup qml-QtPositioning5-basictypes + \since 5.2 + + \brief The coordinate type represents and stores a geographic position. + + This type is a QML representation of \l QGeoCoordinate and represents a geographic + position in the form of \l {latitude}, \l longitude and \l altitude attributes. + The \l latitude attribute specifies the number of + decimal degrees above and below the equator. A positive latitude indicates the Northern + Hemisphere and a negative latitude indicates the Southern Hemisphere. The \l longitude + attribute specifies the number of decimal degrees east and west. A positive longitude + indicates the Eastern Hemisphere and a negative longitude indicates the Western Hemisphere. + The \l altitude attribute specifies the number of meters above sea level. Together, these + attributes specify a 3-dimensional position anywhere on or near the Earth's surface. + + The \l isValid attribute can be used to test if a coordinate is valid. A coordinate is + considered valid if it has a valid latitude and longitude. A valid altitude is not required. + The latitude must be between -90 and 90 inclusive and the longitude must be between -180 and + 180 inclusive. + + The \c coordinate type is used by many other types in the Qt Location module, for specifying + the position of an object on a Map, the current position of a device and many other tasks. + They also feature a number of important utility methods that make otherwise complex + calculations simple to use, such as \l {atDistanceAndAzimuth}(). + + \section1 Accuracy + + The latitude, longitude and altitude attributes stored in the coordinate type are represented + as doubles, giving them approximately 16 decimal digits of precision -- enough to specify + micrometers. The calculations performed in coordinate's methods such as \l {azimuthTo}() and + \l {distanceTo}() also use doubles for all intermediate values, but the inherent inaccuracies in + their spherical Earth model dominate the amount of error in their output. + + \section1 Example Usage + + Use properties of type \l variant to store a \c {coordinate}. To create a \c coordinate use + one of the methods described below. In all cases, specifying the \l altitude attribute is + optional. + + To create a \c coordinate value, use the \l{QtPositioning::coordinate}{QtPositioning.coordinate()} + function: + + \qml + import QtPositioning 5.2 + + Location { coordinate: QtPositioning.coordinate(-27.5, 153.1) } + \endqml + + or as separate \l latitude, \l longitude and \l altitude components: + + \qml + Location { + coordinate { + latitude: -27.5 + longitude: 153.1 + } + } + \endqml + + When integrating with C++, note that any QGeoCoordinate value passed into QML from C++ is + automatically converted into a \c coordinate value, and vice-versa. + + \section1 Properties + + \section2 latitude + + \code + real latitude + \endcode + + This property holds the latitude value of the geographical position + (decimal degrees). A positive latitude indicates the Northern Hemisphere, + and a negative latitude indicates the Southern Hemisphere. + If the property has not been set, its default value is NaN. + + For more details see the \l {QGeoCoordinate::latitude} property + + \section2 longitude + + \code + real longitude + \endcode + + This property holds the longitude value of the geographical position + (decimal degrees). A positive longitude indicates the Eastern Hemisphere, + and a negative longitude indicates the Western Hemisphere + If the property has not been set, its default value is NaN. + + For more details see the \l {QGeoCoordinate::longitude} property + + \section2 altitude + + \code + real altitude + \endcode + + This property holds the altitude value (meters above sea level). + If the property has not been set, its default value is NaN. + + For more details see the \l {QGeoCoordinate::altitude} property + + \section2 isValid + + \code + bool isValid + \endcode + + This property holds the current validity of the coordinate. Coordinates + are considered valid if they have been set with a valid latitude and + longitude (altitude is not required). + + The latitude must be between -90 to 90 inclusive to be considered valid, + and the longitude must be between -180 to 180 inclusive to be considered + valid. + + This is a read-only property. + + \section1 Methods + + \section2 distanceTo() + + \code + real distanceTo(coordinate other) + \endcode + + Returns the distance (in meters) from this coordinate to the coordinate specified by \a other. + Altitude is not used in the calculation. + + This calculation returns the great-circle distance between the two coordinates, with an + assumption that the Earth is spherical for the purpose of this calculation. + + \section2 azimuthTo() + + \code + real azimuth(coordinate other) + \endcode + + Returns the azimuth (or bearing) in degrees from this coordinate to the coordinate specified by + \a other. Altitude is not used in the calculation. + + There is an assumption that the Earth is spherical for the purpose of this calculation. + + \section2 atDistanceAndAzimuth() + + \code + coordinate atDistanceAndAzimuth(real distance, real azimuth) + \endcode + + Returns the coordinate that is reached by traveling \a distance metres from this coordinate at + \a azimuth degrees along a great-circle. + + There is an assumption that the Earth is spherical for the purpose of this calculation. +*/ + +/*! + \qmlbasictype geoshape + \inqmlmodule QtPositioning + \ingroup qml-QtPositioning5-basictypes + \since 5.2 + + \brief A geoshape type represents an abstract geographic area. + + This type is a QML representation of \l QGeoShape which is an abstract geographic area. + It includes attributes and methods common to all geographic areas. To create objects + that represent a valid geographic area use \l {georectangle} or \l {geocircle}. + + The \l isValid attribute can be used to test if the geoshape represents a valid geographic + area. + + The \l isEmpty attribute can be used to test if the geoshape represents a region with a + geometrical area of 0. + + The \l {contains}{contains()} method can be used to test if a \l {coordinate} is + within the geoshape. + + \section1 Example Usage + + Use properties of type \l variant to store a \c {geoshape}. To create a \c geoshape use one + of the methods described below. + + To create a \c geoshape value, specify it as a "shape()" string: + + \qml + import QtPositioning + + Item { + property variant region: "shape()" + } + \endqml + + or with the \l {QtPositioning::shape}{QtPositioning.shape()} function: + + \qml + import QtPositioning 5.2 + + Item { + property variant region: QtPositioning.shape() + } + \endqml + + When integrating with C++, note that any QGeoShape value passed into QML from C++ is + automatically converted into a \c geoshape value, and vice-versa. + + \section1 Properties + + \section2 isEmpty + + \code + bool isEmpty + \endcode + + Returns whether this geoshape is empty. An empty geoshape is a region which has + a geometrical area of 0. + + \section2 isValid + + \code + bool isValid + \endcode + + Returns whether this geoshape is valid. + + A geoshape is considered to be invalid if some of the data that is required to + unambiguously describe the geoshape has not been set or has been set to an + unsuitable value. + + \section2 type + + \code + ShapeType type + \endcode + + Returns the current type of the shape. + + \list + \li GeoShape.UnknownType - The shape's type is not known. + \li GeoShape.RectangleType - The shape is a \l georectangle. + \li GeoShape.CircleType - The shape is a \l geocircle. + \endlist + + This QML property was introduced by Qt 5.5. + + \section1 Methods + + \section2 contains() + + \code + bool contains(coordinate coord) + \endcode + + Returns true if the \l {QtPositioning::coordinate}{coordinate} specified by \a coord is within + this geoshape; Otherwise returns false. +*/ + +/*! + \qmlbasictype georectangle + \inqmlmodule QtPositioning + \ingroup qml-QtPositioning5-basictypes + \since 5.2 + + \brief The georectangle type represents a rectangular geographic area. + + The \c georectangle type is a \l {geoshape} that represents a + rectangular geographic area. The type is direct representation of a \l QGeoRectangle. + It is defined by a pair of \l {coordinate}{coordinates} which represent the top-left + and bottom-right corners of the \c {georectangle}. The coordinates are accessible + from the \l topLeft and \l bottomRight attributes. + + A \c georectangle is considered invalid if the top-left or bottom-right coordinates are invalid + or if the top-left coordinate is south of the bottom-right coordinate. + + The coordinates of the four corners of the \c georectangle can be accessed with the + \l {topLeft}, \l {topRight}, \l {bottomLeft} and \l {bottomRight} attributes. The \l center + attribute can be used to get the coordinate of the center of the \c georectangle. The \l width + and \l height attributes can be used to get the width and height of the \c georectangle in + degrees. Setting one of these attributes will cause the other attributes to be adjusted + accordingly. + + \section1 Limitations + + A \c georectangle can never cross the poles. + + If the height or center of a \c georectangle is adjusted such that it would cross one of the + poles the height is modified such that the \c georectangle touches but does not cross the pole + and that the center coordinate is still in the center of the \c georectangle. + + \section1 Example Usage + + Use properties of type \l variant to store a \c {georectangle}. To create a \c georectangle + value, use the \l {QtPositioning::rectangle}{QtPositioning.rectangle()} function: + + \qml + import QtPositioning 5.2 + + Item { + property variant region: QtPositioning.rectangle(QtPositioning.coordinate(-27.5, 153.1), QtPositioning.coordinate(-27.6, 153.2)) + } + \endqml + + When integrating with C++, note that any QGeoRectangle value passed into QML from C++ is + automatically converted into a \c georectangle value, and vice-versa. + + \section1 Properties + + \section2 bottomLeft + + \code + coordinate bottomLeft + \endcode + + This property holds the bottom left coordinate of this georectangle. + + \section2 bottomRight + + \code + coordinate bottomRight + \endcode + + This property holds the bottom right coordinate of this georectangle. + + \section2 center + + \code + coordinate center + \endcode + + This property holds the center coordinate of this georectangle. For more details + see \l {QGeoRectangle::setCenter()}. + + \section2 height + + \code + double height + \endcode + + This property holds the height of this georectangle (in degrees). For more details + see \l {QGeoRectangle::setHeight()}. + + \note If the georectangle is invalid, it is not possible to set the height. QtPositioning + releases prior to Qt 5.5 permitted the setting of the height even on invalid georectangles. + + \section2 topLeft + + \code + coordinate topLeft + \endcode + + This property holds the top left coordinate of this georectangle. + + \section2 topRight + + \code + coordinate topRight + \endcode + + This property holds the top right coordinate of this georectangle. + + \section2 width + + \code + double width + \endcode + + This property holds the width of this georectangle (in degrees). For more details + see \l {QGeoRectangle::setWidth()}. + + \note If the georectangle is invalid, it is not possible to set the width. QtPositioning + releases prior to Qt 5.5 permitted the setting of the width even on invalid georectangles. +*/ + +/*! + \qmlbasictype geocircle + \inqmlmodule QtPositioning + \ingroup qml-QtPositioning5-basictypes + \since 5.2 + + \brief The geocircle type represents a circular geographic area. + + The \c geocircle type is a \l {geoshape} that represents a circular + geographic area. It is a direct representation of a \l QGeoCircle and is defined + in terms of a \l {coordinate} which specifies the \l center of the circle and + a qreal which specifies the \l radius of the circle in meters. + + The circle is considered invalid if the \l center coordinate is invalid or if + the \l radius is less than zero. + + \section1 Example Usage + + Use properties of type \l variant to store a \c {geocircle}. To create a \c geocircle value, + use the \l {QtPositioning::circle}{QtPositioning.circle()} function: + + \qml + import QtPositioning 5.2 + + Item { + property variant region: QtPositioning.circle(QtPositioning.coordinate(-27.5, 153.1), 1000) + } + \endqml + + When integrating with C++, note that any QGeoCircle value passed into QML from C++ is + automatically converted into a \c geocircle value, and vise-versa. + + \section1 Properties + + \section2 center + + \code + coordinate radius + \endcode + + This property holds the coordinate of the center of the geocircle. + + \section2 radius + + \code + real radius + \endcode + + This property holds the radius of the geocircle in meters. + + The default value for the radius is -1 indicating an invalid geocircle area. +*/ + +static QObject *singleton_type_factory(QQmlEngine *engine, QJSEngine *jsEngine) +{ + Q_UNUSED(engine) + Q_UNUSED(jsEngine) + + return new LocationSingleton; +} + +class QtPositioningDeclarativeModule: public QQmlExtensionPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0" + FILE "plugin.json") + +public: + QtPositioningDeclarativeModule(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); } + virtual void registerTypes(const char *uri) + { + if (QLatin1String(uri) == QStringLiteral("QtPositioning")) { + + // @uri QtPositioning 5.0 + + int major = 5; + int minor = 0; + + qRegisterMetaType(); + QMetaType::registerEqualsComparator(); + qRegisterMetaType(); + qRegisterMetaType(); + QMetaType::registerEqualsComparator(); + qRegisterMetaType(); + QMetaType::registerEqualsComparator(); + qRegisterMetaType(); + qRegisterMetaType(); + QMetaType::registerEqualsComparator(); + + qRegisterAnimationInterpolator(q_coordinateInterpolator); + + // Register the 5.0 types + // 5.0 is silent and not advertised + qmlRegisterSingletonType(uri, major, minor, "QtPositioning", singleton_type_factory); + qmlRegisterValueTypeEnums(uri, major, minor, "GeoShape"); + qmlRegisterType(uri, major, minor, "Position"); + qmlRegisterType(uri, major, minor, "PositionSource"); + qmlRegisterType(uri, major, minor, "Address"); + qmlRegisterType(uri, major, minor, "Location"); + + // Register the 5.3 types + // Introduction of 5.3 version; existing 5.0 exports become automatically available under 5.3 + minor = 3; + qmlRegisterType(uri, major, minor, "CoordinateAnimation"); + qmlRegisterType(uri, major, minor, "Position"); + + // Register the 5.4 types + // Introduction of 5.4 version; existing 5.3 exports become automatically available under 5.4 + minor = 4; + qmlRegisterType(uri, major, minor, "Position"); + + minor = 7; + qmlRegisterType(uri, major, minor, "Position"); + } else { + qDebug() << "Unsupported URI given to load positioning QML plugin: " << QLatin1String(uri); + } + } +}; + +#include "positioning.moc" + +QT_END_NAMESPACE diff --git a/src/imports/positioning/positioning.pro b/src/imports/positioning/positioning.pro new file mode 100644 index 0000000..119c3de --- /dev/null +++ b/src/imports/positioning/positioning.pro @@ -0,0 +1,21 @@ +QT += quick-private positioning-private qml-private core-private + +INCLUDEPATH *= $$PWD + +HEADERS += qdeclarativeposition_p.h \ + qdeclarativepositionsource_p.h \ + locationsingleton.h \ + qquickgeocoordinateanimation_p.h \ + qquickgeocoordinateanimation_p_p.h + +SOURCES += qdeclarativeposition.cpp \ + positioning.cpp \ + qdeclarativepositionsource.cpp \ + locationsingleton.cpp \ + qquickgeocoordinateanimation.cpp + +load(qml_plugin) + +OTHER_FILES += \ + plugin.json \ + qmldir diff --git a/src/imports/positioning/qdeclarativeposition.cpp b/src/imports/positioning/qdeclarativeposition.cpp new file mode 100644 index 0000000..50313de --- /dev/null +++ b/src/imports/positioning/qdeclarativeposition.cpp @@ -0,0 +1,471 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qdeclarativeposition_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Position + \instantiates QDeclarativePosition + \inqmlmodule QtPositioning + \since 5.2 + + \brief The Position type holds positional data at a particular point in time, + such as coordinate (longitude, latitude, altitude) and speed. + + The Position type holds values related to geographic location such as + a \l coordinate (longitude, latitude, and altitude), the \l timestamp when + the Position was obtained, the \l speed at that time, and the accuracy of + the data. + + Primarily, it is used in the \l{PositionSource::position}{position} property + of a \l{PositionSource}, as the basic unit of data available from the system + location data source. + + Not all properties of a Position object are necessarily valid or available + (for example latitude and longitude may be valid, but speed update has not been + received or set manually). As a result, corresponding "valid" properties + are available (for example \l{coordinate} and \l{longitudeValid}, \l{latitudeValid} + etc) to discern whether the data is available and valid in this position + update. + + Position objects are read-only and can only be produced by a PositionSource. + + \section2 Example Usage + + See the example given for the \l{PositionSource} type, or the + \l{geoflickr}{GeoFlickr} example application. + + \sa PositionSource, coordinate +*/ + +namespace +{ + +bool equalOrNaN(qreal a, qreal b) +{ + return a == b || (qIsNaN(a) && qIsNaN(b)); +} + +bool exclusiveNaN(qreal a, qreal b) +{ + return qIsNaN(a) != qIsNaN(b); +} + +} + +QDeclarativePosition::QDeclarativePosition(QObject *parent) +: QObject(parent) +{ +} + +QDeclarativePosition::~QDeclarativePosition() +{ +} + +void QDeclarativePosition::setPosition(const QGeoPositionInfo &info) +{ + // timestamp + const QDateTime pTimestamp = m_info.timestamp(); + const QDateTime timestamp = info.timestamp(); + bool emitTimestampChanged = pTimestamp != timestamp; + + // coordinate + const QGeoCoordinate pCoordinate = m_info.coordinate(); + const QGeoCoordinate coordinate = info.coordinate(); + bool emitCoordinateChanged = pCoordinate != coordinate; + bool emitLatitudeValidChanged = exclusiveNaN(pCoordinate.latitude(), coordinate.latitude()); + bool emitLongitudeValidChanged = exclusiveNaN(pCoordinate.longitude(), coordinate.longitude()); + bool emitAltitudeValidChanged = exclusiveNaN(pCoordinate.altitude(), coordinate.altitude()); + + // direction + const qreal pDirection = m_info.attribute(QGeoPositionInfo::Direction); + const qreal direction = info.attribute(QGeoPositionInfo::Direction); + bool emitDirectionChanged = !equalOrNaN(pDirection, direction); + bool emitDirectionValidChanged = exclusiveNaN(pDirection, direction); + + // ground speed + const qreal pSpeed = m_info.attribute(QGeoPositionInfo::GroundSpeed); + const qreal speed = info.attribute(QGeoPositionInfo::GroundSpeed); + bool emitSpeedChanged = !equalOrNaN(pSpeed, speed); + bool emitSpeedValidChanged = exclusiveNaN(pSpeed, speed); + + // vertical speed + const qreal pVerticalSpeed = m_info.attribute(QGeoPositionInfo::VerticalSpeed); + const qreal verticalSpeed = info.attribute(QGeoPositionInfo::VerticalSpeed); + bool emitVerticalSpeedChanged = !equalOrNaN(pVerticalSpeed, verticalSpeed); + bool emitVerticalSpeedValidChanged = exclusiveNaN(pVerticalSpeed, verticalSpeed); + + // magnetic variation + const qreal pMagneticVariation = m_info.attribute(QGeoPositionInfo::MagneticVariation); + const qreal magneticVariation = info.attribute(QGeoPositionInfo::MagneticVariation); + bool emitMagneticVariationChanged = !equalOrNaN(pMagneticVariation, magneticVariation); + bool emitMagneticVariationValidChanged = exclusiveNaN(pMagneticVariation, magneticVariation); + + // horizontal accuracy + const qreal pHorizontalAccuracy = m_info.attribute(QGeoPositionInfo::HorizontalAccuracy); + const qreal horizontalAccuracy = info.attribute(QGeoPositionInfo::HorizontalAccuracy); + bool emitHorizontalAccuracyChanged = !equalOrNaN(pHorizontalAccuracy, horizontalAccuracy); + bool emitHorizontalAccuracyValidChanged = exclusiveNaN(pHorizontalAccuracy, horizontalAccuracy); + + // vertical accuracy + const qreal pVerticalAccuracy = m_info.attribute(QGeoPositionInfo::VerticalAccuracy); + const qreal verticalAccuracy = info.attribute(QGeoPositionInfo::VerticalAccuracy); + bool emitVerticalAccuracyChanged = !equalOrNaN(pVerticalAccuracy, verticalAccuracy); + bool emitVerticalAccuracyValidChanged = exclusiveNaN(pVerticalAccuracy, verticalAccuracy); + + m_info = info; + + if (emitTimestampChanged) + emit timestampChanged(); + if (emitCoordinateChanged) + emit coordinateChanged(); + if (emitLatitudeValidChanged) + emit latitudeValidChanged(); + if (emitLongitudeValidChanged) + emit longitudeValidChanged(); + if (emitAltitudeValidChanged) + emit altitudeValidChanged(); + if (emitDirectionChanged) + emit directionChanged(); + if (emitDirectionValidChanged) + emit directionValidChanged(); + if (emitSpeedChanged) + emit speedChanged(); + if (emitSpeedValidChanged) + emit speedValidChanged(); + if (emitVerticalSpeedChanged) + emit verticalSpeedChanged(); + if (emitVerticalSpeedValidChanged) + emit verticalSpeedValidChanged(); + if (emitHorizontalAccuracyChanged) + emit horizontalAccuracyChanged(); + if (emitHorizontalAccuracyValidChanged) + emit horizontalAccuracyValidChanged(); + if (emitVerticalAccuracyChanged) + emit verticalAccuracyChanged(); + if (emitVerticalAccuracyValidChanged) + emit verticalAccuracyValidChanged(); + if (emitMagneticVariationChanged) + emit magneticVariationChanged(); + if (emitMagneticVariationValidChanged) + emit magneticVariationValidChanged(); +} + +/*! + \qmlproperty coordinate Position::coordinate + + This property holds the latitude, longitude, and altitude value of the Position. + + It is a read-only property. + + \sa longitudeValid, latitudeValid, altitudeValid +*/ +QGeoCoordinate QDeclarativePosition::coordinate() +{ + return m_info.coordinate(); +} + +/*! + \qmlproperty bool Position::latitudeValid + + This property is true if coordinate's latitude has been set + (to indicate whether that data has been received or not, as every update + does not necessarily contain all data). + + \sa coordinate +*/ +bool QDeclarativePosition::isLatitudeValid() const +{ + return !qIsNaN(m_info.coordinate().latitude()); +} + + +/*! + \qmlproperty bool Position::longitudeValid + + This property is true if coordinate's longitude has been set + (to indicate whether that data has been received or not, as every update + does not necessarily contain all data). + + \sa coordinate +*/ +bool QDeclarativePosition::isLongitudeValid() const +{ + return !qIsNaN(m_info.coordinate().longitude()); +} + + +/*! + \qmlproperty bool Position::speedValid + + This property is true if \l speed has been set + (to indicate whether that data has been received or not, as every update + does not necessarily contain all data). + + \sa speed +*/ +bool QDeclarativePosition::isSpeedValid() const +{ + return !qIsNaN(m_info.attribute(QGeoPositionInfo::GroundSpeed)); +} + +/*! + \qmlproperty bool Position::altitudeValid + + This property is true if coordinate's altitude has been set + (to indicate whether that data has been received or not, as every update + does not necessarily contain all data). + + \sa coordinate +*/ +bool QDeclarativePosition::isAltitudeValid() const +{ + return !qIsNaN(m_info.coordinate().altitude()); +} + +/*! + \qmlproperty double Position::speed + + This property holds the value of speed (groundspeed, meters / second). + + It is a read-only property. + + \sa speedValid, coordinate +*/ +double QDeclarativePosition::speed() const +{ + return m_info.attribute(QGeoPositionInfo::GroundSpeed); +} + +/*! + \qmlproperty real Position::horizontalAccuracy + + This property holds the horizontal accuracy of the coordinate (in meters). + + \sa horizontalAccuracyValid, coordinate +*/ +void QDeclarativePosition::setHorizontalAccuracy(qreal horizontalAccuracy) +{ + const qreal pHorizontalAccuracy = m_info.attribute(QGeoPositionInfo::HorizontalAccuracy); + + if (equalOrNaN(pHorizontalAccuracy, horizontalAccuracy)) + return; + + bool validChanged = exclusiveNaN(pHorizontalAccuracy, horizontalAccuracy); + + m_info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, horizontalAccuracy); + emit horizontalAccuracyChanged(); + if (validChanged) + emit horizontalAccuracyValidChanged(); +} + +qreal QDeclarativePosition::horizontalAccuracy() const +{ + return m_info.attribute(QGeoPositionInfo::HorizontalAccuracy); +} + +/*! + \qmlproperty bool Position::horizontalAccuracyValid + + This property is true if \l horizontalAccuracy has been set + (to indicate whether that data has been received or not, as every update + does not necessarily contain all data). + + \sa horizontalAccuracy +*/ +bool QDeclarativePosition::isHorizontalAccuracyValid() const +{ + return !qIsNaN(m_info.attribute(QGeoPositionInfo::HorizontalAccuracy)); +} + +/*! + \qmlproperty real Position::verticalAccuracy + + This property holds the vertical accuracy of the coordinate (in meters). + + \sa verticalAccuracyValid, coordinate +*/ +void QDeclarativePosition::setVerticalAccuracy(qreal verticalAccuracy) +{ + const qreal pVerticalAccuracy = m_info.attribute(QGeoPositionInfo::VerticalAccuracy); + + if (equalOrNaN(pVerticalAccuracy, verticalAccuracy)) + return; + + bool validChanged = exclusiveNaN(pVerticalAccuracy, verticalAccuracy); + + m_info.setAttribute(QGeoPositionInfo::VerticalAccuracy, verticalAccuracy); + emit verticalAccuracyChanged(); + if (validChanged) + emit verticalAccuracyValidChanged(); +} + +qreal QDeclarativePosition::verticalAccuracy() const +{ + return m_info.attribute(QGeoPositionInfo::VerticalAccuracy); +} + +/*! + \qmlproperty bool Position::verticalAccuracyValid + + This property is true if \l verticalAccuracy has been set + (to indicate whether that data has been received or not, as every update + does not necessarily contain all data). + + \sa verticalAccuracy +*/ +bool QDeclarativePosition::isVerticalAccuracyValid() const +{ + return !qIsNaN(m_info.attribute(QGeoPositionInfo::VerticalAccuracy)); +} + +/*! + \qmlproperty date Position::timestamp + + This property holds the timestamp when this position + was received. If the property has not been set, it is invalid. + + It is a read-only property. +*/ +QDateTime QDeclarativePosition::timestamp() const +{ + return m_info.timestamp(); +} + +/*! + \qmlproperty bool Position::directionValid + \since Qt Positioning 5.3 + + This property is true if \l direction has been set (to indicate whether that data has been + received or not, as every update does not necessarily contain all data). + + \sa direction +*/ +bool QDeclarativePosition::isDirectionValid() const +{ + return !qIsNaN(m_info.attribute(QGeoPositionInfo::Direction)); +} + +/*! + \qmlproperty double Position::direction + \since Qt Positioning 5.3 + + This property holds the value of the direction of travel in degrees from true north. + + It is a read-only property. + + \sa directionValid +*/ +double QDeclarativePosition::direction() const +{ + return m_info.attribute(QGeoPositionInfo::Direction); +} + +/*! + \qmlproperty bool Position::verticalSpeedValid + \since Qt Positioning 5.3 + + This property is true if \l verticalSpeed has been set (to indicate whether that data has been + received or not, as every update does not necessarily contain all data). + + \sa verticalSpeed +*/ +bool QDeclarativePosition::isVerticalSpeedValid() const +{ + return !qIsNaN(m_info.attribute(QGeoPositionInfo::VerticalSpeed)); +} + +/*! + \qmlproperty double Position::verticalSpeed + \since Qt Positioning 5.3 + + This property holds the value of the vertical speed in meters per second. + + It is a read-only property. + + \sa verticalSpeedValid +*/ +double QDeclarativePosition::verticalSpeed() const +{ + return m_info.attribute(QGeoPositionInfo::VerticalSpeed); +} + +/*! + \qmlproperty bool Position::magneticVariationValid + \since Qt Positioning 5.4 + + This property is true if \l magneticVariation has been set (to indicate whether that data has been + received or not, as every update does not necessarily contain all data). + + \sa magneticVariation +*/ +bool QDeclarativePosition::isMagneticVariationValid() const +{ + return !qIsNaN(m_info.attribute(QGeoPositionInfo::MagneticVariation)); +} + +/*! + \qmlproperty double Position::magneticVariation + \since Qt Positioning 5.4 + + This property holds the angle between the horizontal component of the + magnetic field and true north, in degrees. Also known as magnetic + declination. A positive value indicates a clockwise direction from + true north and a negative value indicates a counter-clockwise direction. + + It is a read-only property. + + \sa magneticVariationValid +*/ +double QDeclarativePosition::magneticVariation() const +{ + return m_info.attribute(QGeoPositionInfo::MagneticVariation); +} + +#include "moc_qdeclarativeposition_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/positioning/qdeclarativeposition_p.h b/src/imports/positioning/qdeclarativeposition_p.h new file mode 100644 index 0000000..208d81c --- /dev/null +++ b/src/imports/positioning/qdeclarativeposition_p.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +***************************************************************************/ + +#ifndef QDECLARATIVEPOSITION_H +#define QDECLARATIVEPOSITION_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDeclarativePosition : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool latitudeValid READ isLatitudeValid NOTIFY latitudeValidChanged) + Q_PROPERTY(bool longitudeValid READ isLongitudeValid NOTIFY longitudeValidChanged) + Q_PROPERTY(bool altitudeValid READ isAltitudeValid NOTIFY altitudeValidChanged) + Q_PROPERTY(QGeoCoordinate coordinate READ coordinate NOTIFY coordinateChanged) + Q_PROPERTY(QDateTime timestamp READ timestamp NOTIFY timestampChanged) + Q_PROPERTY(double speed READ speed NOTIFY speedChanged) + Q_PROPERTY(bool speedValid READ isSpeedValid NOTIFY speedValidChanged) + Q_PROPERTY(qreal horizontalAccuracy READ horizontalAccuracy WRITE setHorizontalAccuracy NOTIFY horizontalAccuracyChanged) + Q_PROPERTY(qreal verticalAccuracy READ verticalAccuracy WRITE setVerticalAccuracy NOTIFY verticalAccuracyChanged) + Q_PROPERTY(bool horizontalAccuracyValid READ isHorizontalAccuracyValid NOTIFY horizontalAccuracyValidChanged) + Q_PROPERTY(bool verticalAccuracyValid READ isVerticalAccuracyValid NOTIFY verticalAccuracyValidChanged) + + Q_PROPERTY(bool directionValid READ isDirectionValid NOTIFY directionValidChanged REVISION 1) + Q_PROPERTY(double direction READ direction NOTIFY directionChanged REVISION 1) + Q_PROPERTY(bool verticalSpeedValid READ isVerticalSpeedValid NOTIFY verticalSpeedValidChanged REVISION 1) + Q_PROPERTY(double verticalSpeed READ verticalSpeed NOTIFY verticalSpeedChanged REVISION 1) + + Q_PROPERTY(double magneticVariation READ magneticVariation NOTIFY magneticVariationChanged REVISION 2) + Q_PROPERTY(bool magneticVariationValid READ isMagneticVariationValid NOTIFY magneticVariationChanged REVISION 2) + +public: + explicit QDeclarativePosition(QObject *parent = 0); + ~QDeclarativePosition(); + + bool isLatitudeValid() const; + bool isLongitudeValid() const; + bool isAltitudeValid() const; + QDateTime timestamp() const; + double speed() const; + bool isSpeedValid() const; + QGeoCoordinate coordinate(); + bool isHorizontalAccuracyValid() const; + qreal horizontalAccuracy() const; + void setHorizontalAccuracy(qreal horizontalAccuracy); + bool isVerticalAccuracyValid() const; + qreal verticalAccuracy() const; + void setVerticalAccuracy(qreal verticalAccuracy); + + bool isDirectionValid() const; + double direction() const; + void setDirection(double direction); + + bool isVerticalSpeedValid() const; + double verticalSpeed() const; + void setVerticalSpeed(double speed); + + bool isMagneticVariationValid() const; + double magneticVariation() const; + + void setPosition(const QGeoPositionInfo &info); + +Q_SIGNALS: + void latitudeValidChanged(); + void longitudeValidChanged(); + void altitudeValidChanged(); + void timestampChanged(); + void speedChanged(); + void speedValidChanged(); + void coordinateChanged(); + void horizontalAccuracyChanged(); + void horizontalAccuracyValidChanged(); + void verticalAccuracyChanged(); + void verticalAccuracyValidChanged(); + + Q_REVISION(1) void directionValidChanged(); + Q_REVISION(1) void directionChanged(); + Q_REVISION(1) void verticalSpeedValidChanged(); + Q_REVISION(1) void verticalSpeedChanged(); + + Q_REVISION(2) void magneticVariationChanged(); + Q_REVISION(2) void magneticVariationValidChanged(); + +private: + QGeoPositionInfo m_info; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativePosition) + +#endif diff --git a/src/imports/positioning/qdeclarativepositionsource.cpp b/src/imports/positioning/qdeclarativepositionsource.cpp new file mode 100644 index 0000000..35ff33b --- /dev/null +++ b/src/imports/positioning/qdeclarativepositionsource.cpp @@ -0,0 +1,765 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativepositionsource_p.h" +#include "qdeclarativeposition_p.h" + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype PositionSource + \instantiates QDeclarativePositionSource + \inqmlmodule QtPositioning + \since 5.2 + + \brief The PositionSource type provides the device's current position. + + The PositionSource type provides information about the user device's + current position. The position is available as a \l{Position} type, which + contains all the standard parameters typically available from GPS and other + similar systems, including longitude, latitude, speed and accuracy details. + + As different position sources are available on different platforms and + devices, these are categorized by their basic type (Satellite, NonSatellite, + and AllPositioningMethods). The available methods for the current platform + can be enumerated in the \l{supportedPositioningMethods} property. + + To indicate which methods are suitable for your application, set the + \l{preferredPositioningMethods} property. If the preferred methods are not + available, the default source of location data for the platform will be + chosen instead. If no default source is available (because none are installed + for the runtime platform, or because it is disabled), the \l{valid} property + will be set to false. + + The \l updateInterval property can then be used to indicate how often your + application wishes to receive position updates. The \l{start}(), + \l{stop}() and \l{update}() methods can be used to control the operation + of the PositionSource, as well as the \l{active} property, which when set + is equivalent to calling \l{start}() or \l{stop}(). + + When the PositionSource is active, position updates can be retrieved + either by simply using the \l{position} property in a binding (as the + value of another item's property), or by providing an implementation of + the \c {onPositionChanged} signal-handler. + + \section2 Example Usage + + The following example shows a simple PositionSource used to receive + updates every second and print the longitude and latitude out to + the console. + + \code + PositionSource { + id: src + updateInterval: 1000 + active: true + + onPositionChanged: { + var coord = src.position.coordinate; + console.log("Coordinate:", coord.longitude, coord.latitude); + } + } + \endcode + + The \l{geoflickr}{GeoFlickr} example application shows how to use + a PositionSource in your application to retrieve local data for users + from a REST web service. + + \sa {QtPositioning::Position}, {QGeoPositionInfoSource} + +*/ + +/*! + \qmlsignal PositionSource::updateTimeout() + + If \l update() was called, this signal is emitted if the current position could not be + retrieved within a certain amount of time. + + If \l start() was called, this signal is emitted if the position engine determines that + it is not able to provide further regular updates. + + \since Qt Positioning 5.5 + + \sa QGeoPositionInfoSource::updateTimeout() +*/ + + +QDeclarativePositionSource::QDeclarativePositionSource() +: m_positionSource(0), m_preferredPositioningMethods(NoPositioningMethods), m_nmeaFile(0), + m_nmeaSocket(0), m_active(false), m_singleUpdate(false), m_updateInterval(0), + m_sourceError(NoError) +{ +} + +QDeclarativePositionSource::~QDeclarativePositionSource() +{ + delete m_nmeaFile; + delete m_nmeaSocket; + delete m_positionSource; +} + + +/*! + \qmlproperty string PositionSource::name + + This property holds the unique internal name for the plugin currently + providing position information. + + Setting the property causes the PositionSource to use a particular positioning provider. If + the PositionSource is active at the time that the name property is changed, it will become + inactive. If the specified positioning provider cannot be loaded the position source will + become invalid. + + Changing the name property may cause the \l {updateInterval}, \l {supportedPositioningMethods} + and \l {preferredPositioningMethods} properties to change as well. +*/ + + +QString QDeclarativePositionSource::name() const +{ + if (m_positionSource) + return m_positionSource->sourceName(); + else + return QString(); +} + +void QDeclarativePositionSource::setName(const QString &newName) +{ + if (m_positionSource && m_positionSource->sourceName() == newName) + return; + + const QString previousName = name(); + int previousUpdateInterval = updateInterval(); + PositioningMethods previousPositioningMethods = supportedPositioningMethods(); + PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods(); + + delete m_positionSource; + if (newName.isEmpty()) + m_positionSource = QGeoPositionInfoSource::createDefaultSource(this); + else + m_positionSource = QGeoPositionInfoSource::createSource(newName, this); + + if (m_positionSource) { + connect(m_positionSource, SIGNAL(positionUpdated(QGeoPositionInfo)), + this, SLOT(positionUpdateReceived(QGeoPositionInfo))); + connect(m_positionSource, SIGNAL(error(QGeoPositionInfoSource::Error)), + this, SLOT(sourceErrorReceived(QGeoPositionInfoSource::Error))); + connect(m_positionSource, SIGNAL(updateTimeout()), + this, SLOT(updateTimeoutReceived())); + + m_positionSource->setUpdateInterval(m_updateInterval); + m_positionSource->setPreferredPositioningMethods( + static_cast(int(m_preferredPositioningMethods))); + + setPosition(m_positionSource->lastKnownPosition()); + } + + if (previousUpdateInterval != updateInterval()) + emit updateIntervalChanged(); + + if (previousPreferredPositioningMethods != preferredPositioningMethods()) + emit preferredPositioningMethodsChanged(); + + if (previousPositioningMethods != supportedPositioningMethods()) + emit supportedPositioningMethodsChanged(); + + emit validityChanged(); + + if (m_active) { + m_active = false; + emit activeChanged(); + } + + if (previousName != name()) + emit nameChanged(); +} + +/*! + \qmlproperty bool PositionSource::valid + + This property is true if the PositionSource object has acquired a valid + backend plugin to provide data. If false, other methods on the PositionSource + will have no effect. + + Applications should check this property to determine whether positioning is + available and enabled on the runtime platform, and react accordingly. +*/ +bool QDeclarativePositionSource::isValid() const +{ + return (m_positionSource != 0); +} + +/*! + \internal +*/ +void QDeclarativePositionSource::setNmeaSource(const QUrl &nmeaSource) +{ + if (nmeaSource.scheme() == QLatin1String("socket")) { + if (m_nmeaSocket + && nmeaSource.host() == m_nmeaSocket->peerName() + && nmeaSource.port() == m_nmeaSocket->peerPort()) { + return; + } + + delete m_nmeaSocket; + m_nmeaSocket = new QTcpSocket(); + + connect(m_nmeaSocket, static_cast (&QAbstractSocket::error), + this, &QDeclarativePositionSource::socketError); + connect(m_nmeaSocket, &QTcpSocket::connected, + this, &QDeclarativePositionSource::socketConnected); + + m_nmeaSocket->connectToHost(nmeaSource.host(), nmeaSource.port(), QTcpSocket::ReadOnly); + } else { + // Strip the filename. This is clumsy but the file may be prefixed in several + // ways: "file:///", "qrc:///", "/", "" in platform dependent manner. + QString localFileName = nmeaSource.toString(); + if (!QFile::exists(localFileName)) { + if (localFileName.startsWith("qrc:///")) { + localFileName.remove(0, 7); + } else if (localFileName.startsWith("file:///")) { + localFileName.remove(0, 7); + } else if (localFileName.startsWith("qrc:/")) { + localFileName.remove(0, 5); + } + if (!QFile::exists(localFileName) && localFileName.startsWith('/')) { + localFileName.remove(0,1); + } + } + if (m_nmeaFileName == localFileName) + return; + m_nmeaFileName = localFileName; + + PositioningMethods previousPositioningMethods = supportedPositioningMethods(); + + // The current position source needs to be deleted + // because QNmeaPositionInfoSource can be bound only to a one file. + delete m_nmeaSocket; + m_nmeaSocket = 0; + delete m_positionSource; + m_positionSource = 0; + setPosition(QGeoPositionInfo()); + // Create the NMEA source based on the given data. QML has automatically set QUrl + // type to point to correct path. If the file is not found, check if the file actually + // was an embedded resource file. + delete m_nmeaFile; + m_nmeaFile = new QFile(localFileName); + if (!m_nmeaFile->exists()) { + localFileName.prepend(":"); + m_nmeaFile->setFileName(localFileName); + } + if (m_nmeaFile->exists()) { +#ifdef QDECLARATIVE_POSITION_DEBUG + qDebug() << "QDeclarativePositionSource NMEA File was found: " << localFileName; +#endif + m_positionSource = new QNmeaPositionInfoSource(QNmeaPositionInfoSource::SimulationMode); + (qobject_cast(m_positionSource))->setDevice(m_nmeaFile); + connect(m_positionSource, SIGNAL(positionUpdated(QGeoPositionInfo)), + this, SLOT(positionUpdateReceived(QGeoPositionInfo))); + connect(m_positionSource, SIGNAL(error(QGeoPositionInfoSource::Error)), + this, SLOT(sourceErrorReceived(QGeoPositionInfoSource::Error))); + connect(m_positionSource, SIGNAL(updateTimeout()), + this, SLOT(updateTimeoutReceived())); + + setPosition(m_positionSource->lastKnownPosition()); + if (m_active && !m_singleUpdate) { + // Keep on updating even though source changed + QTimer::singleShot(0, this, SLOT(start())); + } + } else { + qmlInfo(this) << QStringLiteral("Nmea file not found") << localFileName; +#ifdef QDECLARATIVE_POSITION_DEBUG + qDebug() << "QDeclarativePositionSource NMEA File was not found: " << localFileName; +#endif + if (m_active) { + m_active = false; + m_singleUpdate = false; + emit activeChanged(); + } + } + + if (previousPositioningMethods != supportedPositioningMethods()) + emit supportedPositioningMethodsChanged(); + } + + m_nmeaSource = nmeaSource; + emit nmeaSourceChanged(); +} + +/*! + \internal +*/ +void QDeclarativePositionSource::socketConnected() +{ +#ifdef QDECLARATIVE_POSITION_DEBUG + qDebug() << "Socket connected: " << m_nmeaSocket->peerName(); +#endif + PositioningMethods previousPositioningMethods = supportedPositioningMethods(); + + // The current position source needs to be deleted + // because QNmeaPositionInfoSource can be bound only to a one file. + delete m_nmeaFile; + m_nmeaFile = 0; + delete m_positionSource; + + m_positionSource = new QNmeaPositionInfoSource(QNmeaPositionInfoSource::RealTimeMode); + (qobject_cast(m_positionSource))->setDevice(m_nmeaSocket); + + connect(m_positionSource, &QNmeaPositionInfoSource::positionUpdated, + this, &QDeclarativePositionSource::positionUpdateReceived); + connect(m_positionSource, SIGNAL(error(QGeoPositionInfoSource::Error)), + this, SLOT(sourceErrorReceived(QGeoPositionInfoSource::Error))); + connect(m_positionSource, SIGNAL(updateTimeout()), + this, SLOT(updateTimeoutReceived())); + + setPosition(m_positionSource->lastKnownPosition()); + + if (m_active && !m_singleUpdate) { + // Keep on updating even though source changed + QTimer::singleShot(0, this, SLOT(start())); + } + + if (previousPositioningMethods != supportedPositioningMethods()) + emit supportedPositioningMethodsChanged(); +} + +/*! + \internal +*/ +void QDeclarativePositionSource::socketError(QAbstractSocket::SocketError error) +{ + delete m_nmeaSocket; + m_nmeaSocket = 0; + + switch (error) { + case QAbstractSocket::UnknownSocketError: + m_sourceError = QDeclarativePositionSource::UnknownSourceError; + break; + case QAbstractSocket::SocketAccessError: + m_sourceError = QDeclarativePositionSource::AccessError; + break; + case QAbstractSocket::RemoteHostClosedError: + m_sourceError = QDeclarativePositionSource::ClosedError; + break; + default: + qWarning() << "Connection failed! QAbstractSocket::SocketError" << error; + m_sourceError = QDeclarativePositionSource::SocketError; + break; + } + + emit sourceErrorChanged(); +} + + +void QDeclarativePositionSource::updateTimeoutReceived() +{ + if (!m_active) + return; + + if (m_singleUpdate) { + m_singleUpdate = false; + + // only singleUpdate based timeouts change activity + // continuous updates may resume again (see QGeoPositionInfoSource::startUpdates()) + m_active = false; + emit activeChanged(); + } + + emit updateTimeout(); +} + +void QDeclarativePositionSource::setPosition(const QGeoPositionInfo &pi) +{ + m_position.setPosition(pi); + emit positionChanged(); +} + +/*! + \internal +*/ +void QDeclarativePositionSource::setUpdateInterval(int updateInterval) +{ + if (m_positionSource) { + int previousUpdateInterval = m_positionSource->updateInterval(); + + m_updateInterval = updateInterval; + + if (previousUpdateInterval != updateInterval) { + m_positionSource->setUpdateInterval(updateInterval); + if (previousUpdateInterval != m_positionSource->updateInterval()) + emit updateIntervalChanged(); + } + } else { + if (m_updateInterval != updateInterval) { + m_updateInterval = updateInterval; + emit updateIntervalChanged(); + } + } +} + +/*! + \qmlproperty url PositionSource::nmeaSource + + This property holds the source for NMEA (National Marine Electronics Association) + position-specification data (file). One purpose of this property is to be of + development convenience. + + Setting this property will override any other position source. Currently only + files local to the .qml -file are supported. The NMEA source is created in simulation mode, + meaning that the data and time information in the NMEA source data is used to provide + positional updates at the rate at which the data was originally recorded. + + If nmeaSource has been set for a PositionSource object, there is no way to revert + back to non-file sources. +*/ + +QUrl QDeclarativePositionSource::nmeaSource() const +{ + return m_nmeaSource; +} + +/*! + \qmlproperty int PositionSource::updateInterval + + This property holds the desired interval between updates (milliseconds). + + \sa {QGeoPositionInfoSource::updateInterval()} +*/ + +int QDeclarativePositionSource::updateInterval() const +{ + if (!m_positionSource) + return m_updateInterval; + + return m_positionSource->updateInterval(); +} + +/*! + \qmlproperty enumeration PositionSource::supportedPositioningMethods + + This property holds the supported positioning methods of the + current source. + + \list + \li PositionSource.NoPositioningMethods - No positioning methods supported (no source). + \li PositionSource.SatellitePositioningMethods - Satellite-based positioning methods such as GPS are supported. + \li PositionSource.NonSatellitePositioningMethods - Non-satellite-based methods are supported. + \li PositionSource.AllPositioningMethods - Both satellite-based and non-satellite positioning methods are supported. + \endlist + +*/ + +QDeclarativePositionSource::PositioningMethods QDeclarativePositionSource::supportedPositioningMethods() const +{ + if (m_positionSource) { + return static_cast( + int(m_positionSource->supportedPositioningMethods())); + } + return QDeclarativePositionSource::NoPositioningMethods; +} + +/*! + \qmlproperty enumeration PositionSource::preferredPositioningMethods + + This property holds the preferred positioning methods of the + current source. + + \list + \li PositionSource.NoPositioningMethods - No positioning method is preferred. + \li PositionSource.SatellitePositioningMethods - Satellite-based positioning methods such as GPS should be preferred. + \li PositionSource.NonSatellitePositioningMethods - Non-satellite-based methods should be preferred. + \li PositionSource.AllPositioningMethods - Any positioning methods are acceptable. + \endlist + +*/ + +void QDeclarativePositionSource::setPreferredPositioningMethods(PositioningMethods methods) +{ + if (m_positionSource) { + PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods(); + + m_preferredPositioningMethods = methods; + + if (previousPreferredPositioningMethods != methods) { + m_positionSource->setPreferredPositioningMethods( + static_cast(int(methods))); + if (previousPreferredPositioningMethods != m_positionSource->preferredPositioningMethods()) + emit preferredPositioningMethodsChanged(); + } + } else { + if (m_preferredPositioningMethods != methods) { + m_preferredPositioningMethods = methods; + emit preferredPositioningMethodsChanged(); + } + } +} + +QDeclarativePositionSource::PositioningMethods QDeclarativePositionSource::preferredPositioningMethods() const +{ + if (m_positionSource) { + return static_cast( + int(m_positionSource->preferredPositioningMethods())); + } + return m_preferredPositioningMethods; +} + +/*! + \qmlmethod PositionSource::start() + + Requests updates from the location source. + Uses \l updateInterval if set, default interval otherwise. + If there is no source available, this method has no effect. + + \sa stop, update, active +*/ + +void QDeclarativePositionSource::start() +{ + if (!m_positionSource) + return; + + m_positionSource->startUpdates(); + if (!m_active) { + m_active = true; + emit activeChanged(); + } +} + +/*! + \qmlmethod PositionSource::update() + + A convenience method to request single update from the location source. + If there is no source available, this method has no effect. + + If the position source is not active, it will be activated for as + long as it takes to receive an update, or until the request times + out. The request timeout period is source-specific. + + \sa start, stop, active +*/ + +void QDeclarativePositionSource::update() +{ + if (m_positionSource) { + if (!m_active) { + m_active = true; + m_singleUpdate = true; + emit activeChanged(); + } + // Use default timeout value. Set active before calling the + // update request because on some platforms there may + // be results immediately. + m_positionSource->requestUpdate(); + } +} + +/*! + \qmlmethod PositionSource::stop() + + Stops updates from the location source. + If there is no source available or it is not active, + this method has no effect. + + \sa start, update, active +*/ + +void QDeclarativePositionSource::stop() +{ + if (m_positionSource) { + m_positionSource->stopUpdates(); + if (m_active) { + m_active = false; + emit activeChanged(); + } + } +} + +/*! + \qmlproperty bool PositionSource::active + + This property indicates whether the position source is active. + Setting this property to false equals calling \l stop, and + setting this property true equals calling \l start. + + \sa start, stop, update +*/ +void QDeclarativePositionSource::setActive(bool active) +{ + if (active == m_active) + return; + + if (active) + QTimer::singleShot(0, this, SLOT(start())); // delay ensures all properties have been set + else + stop(); +} + +bool QDeclarativePositionSource::isActive() const +{ + return m_active; +} + +/*! + \qmlproperty Position PositionSource::position + + This property holds the last known positional data. + It is a read-only property. + + The Position type has different positional member variables, + whose validity can be checked with appropriate validity functions + (for example sometimes an update does not have speed or altitude data). + + However, whenever a \c {positionChanged} signal has been received, at least + position::coordinate::latitude, position::coordinate::longitude, and position::timestamp can + be assumed to be valid. + + \sa start, stop, update +*/ + +QDeclarativePosition *QDeclarativePositionSource::position() +{ + return &m_position; +} + +void QDeclarativePositionSource::positionUpdateReceived(const QGeoPositionInfo &update) +{ + setPosition(update); + + if (m_singleUpdate && m_active) { + m_active = false; + m_singleUpdate = false; + emit activeChanged(); + } +} + + +/*! + \qmlproperty enumeration PositionSource::sourceError + + This property holds the error which last occurred with the PositionSource. + + \list + \li PositionSource.AccessError - The connection setup to the remote positioning backend failed because the + application lacked the required privileges. + \li PositionSource.ClosedError - The positioning backend closed the connection, which happens for example in case + the user is switching location services to off. As soon as the location service is re-enabled + regular updates will resume. + \li PositionSource.NoError - No error has occurred. + \li PositionSource.UnknownSourceError - An unidentified error occurred. + \li PositionSource.SocketError - An error occurred while connecting to an nmea source using a socket. + \endlist + +*/ + +QDeclarativePositionSource::SourceError QDeclarativePositionSource::sourceError() const +{ + return m_sourceError; +} + +void QDeclarativePositionSource::componentComplete() +{ + if (!m_positionSource) { + int previousUpdateInterval = updateInterval(); + PositioningMethods previousPositioningMethods = supportedPositioningMethods(); + PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods(); + + m_positionSource = QGeoPositionInfoSource::createDefaultSource(this); + if (m_positionSource) { + connect(m_positionSource, SIGNAL(positionUpdated(QGeoPositionInfo)), + this, SLOT(positionUpdateReceived(QGeoPositionInfo))); + connect(m_positionSource, SIGNAL(error(QGeoPositionInfoSource::Error)), + this, SLOT(sourceErrorReceived(QGeoPositionInfoSource::Error))); + connect(m_positionSource, SIGNAL(updateTimeout()), + this, SLOT(updateTimeoutReceived())); + + m_positionSource->setUpdateInterval(m_updateInterval); + m_positionSource->setPreferredPositioningMethods( + static_cast(int(m_preferredPositioningMethods))); + + setPosition(m_positionSource->lastKnownPosition()); + } + + if (previousUpdateInterval != updateInterval()) + emit updateIntervalChanged(); + + if (previousPreferredPositioningMethods != preferredPositioningMethods()) + emit preferredPositioningMethodsChanged(); + + if (previousPositioningMethods != supportedPositioningMethods()) + emit supportedPositioningMethodsChanged(); + + emit validityChanged(); + + if (m_active) { + m_active = false; + emit activeChanged(); + } + + emit nameChanged(); + } +} + +/*! + \internal +*/ +void QDeclarativePositionSource::sourceErrorReceived(const QGeoPositionInfoSource::Error error) +{ + if (error == QGeoPositionInfoSource::AccessError) + m_sourceError = QDeclarativePositionSource::AccessError; + else if (error == QGeoPositionInfoSource::ClosedError) + m_sourceError = QDeclarativePositionSource::ClosedError; + else if (error == QGeoPositionInfoSource::NoError) + return; //nothing to do + else + m_sourceError = QDeclarativePositionSource::UnknownSourceError; + + emit sourceErrorChanged(); +} + +#include "moc_qdeclarativepositionsource_p.cpp" + +QT_END_NAMESPACE diff --git a/src/imports/positioning/qdeclarativepositionsource_p.h b/src/imports/positioning/qdeclarativepositionsource_p.h new file mode 100644 index 0000000..f785234 --- /dev/null +++ b/src/imports/positioning/qdeclarativepositionsource_p.h @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +***************************************************************************/ + +#ifndef QDECLARATIVEPOSITIONSOURCE_H +#define QDECLARATIVEPOSITIONSOURCE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdeclarativeposition_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QFile; +class QTcpSocket; + +class QDeclarativePositionSource : public QObject, public QQmlParserStatus +{ + Q_OBJECT + + Q_PROPERTY(QDeclarativePosition *position READ position NOTIFY positionChanged) + Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) + Q_PROPERTY(bool valid READ isValid NOTIFY validityChanged) + Q_PROPERTY(QUrl nmeaSource READ nmeaSource WRITE setNmeaSource NOTIFY nmeaSourceChanged) + Q_PROPERTY(int updateInterval READ updateInterval WRITE setUpdateInterval NOTIFY updateIntervalChanged) + Q_PROPERTY(PositioningMethods supportedPositioningMethods READ supportedPositioningMethods NOTIFY supportedPositioningMethodsChanged) + Q_PROPERTY(PositioningMethods preferredPositioningMethods READ preferredPositioningMethods WRITE setPreferredPositioningMethods NOTIFY preferredPositioningMethodsChanged) + Q_PROPERTY(SourceError sourceError READ sourceError NOTIFY sourceErrorChanged) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_ENUMS(PositioningMethod) + + Q_INTERFACES(QQmlParserStatus) + +public: + enum PositioningMethod { + NoPositioningMethods = QGeoPositionInfoSource::NoPositioningMethods, + SatellitePositioningMethods = QGeoPositionInfoSource::SatellitePositioningMethods, + NonSatellitePositioningMethods = QGeoPositionInfoSource::NonSatellitePositioningMethods, + AllPositioningMethods = QGeoPositionInfoSource::AllPositioningMethods + }; + + Q_DECLARE_FLAGS(PositioningMethods, PositioningMethod) + Q_FLAGS(PositioningMethods) + + enum SourceError { + AccessError = QGeoPositionInfoSource::AccessError, + ClosedError = QGeoPositionInfoSource::ClosedError, + UnknownSourceError = QGeoPositionInfoSource::UnknownSourceError, + NoError = QGeoPositionInfoSource::NoError, + + //Leave a gap for future error enum values in QGeoPositionInfoSource::Error + SocketError = 100 + }; + Q_ENUMS(SourceError) + + QDeclarativePositionSource(); + ~QDeclarativePositionSource(); + void setNmeaSource(const QUrl &nmeaSource); + void setUpdateInterval(int updateInterval); + void setActive(bool active); + void setPreferredPositioningMethods(PositioningMethods methods); + + QString name() const; + void setName(const QString &name); + + QUrl nmeaSource() const; + int updateInterval() const; + bool isActive() const; + bool isValid() const; + QDeclarativePosition *position(); + PositioningMethods supportedPositioningMethods() const; + PositioningMethods preferredPositioningMethods() const; + SourceError sourceError() const; + + // Virtuals from QQmlParserStatus + void classBegin() { } + void componentComplete(); + +public Q_SLOTS: + void update(); // TODO Qt 6 change to void update(int) + void start(); + void stop(); + +Q_SIGNALS: + void positionChanged(); + void activeChanged(); + void nmeaSourceChanged(); + void updateIntervalChanged(); + void supportedPositioningMethodsChanged(); + void preferredPositioningMethodsChanged(); + void sourceErrorChanged(); + void nameChanged(); + void validityChanged(); + void updateTimeout(); + +private Q_SLOTS: + void positionUpdateReceived(const QGeoPositionInfo &update); + void sourceErrorReceived(const QGeoPositionInfoSource::Error error); + void socketConnected(); + void socketError(QAbstractSocket::SocketError error); + void updateTimeoutReceived(); + +private: + void setPosition(const QGeoPositionInfo &pi); + + QGeoPositionInfoSource *m_positionSource; + QDeclarativePosition m_position; + PositioningMethods m_preferredPositioningMethods; + QFile *m_nmeaFile; + QTcpSocket *m_nmeaSocket; + QString m_nmeaFileName; + QUrl m_nmeaSource; + bool m_active; + bool m_singleUpdate; + int m_updateInterval; + SourceError m_sourceError; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativePositionSource) + +#endif diff --git a/src/imports/positioning/qmldir b/src/imports/positioning/qmldir new file mode 100644 index 0000000..fc4ebf8 --- /dev/null +++ b/src/imports/positioning/qmldir @@ -0,0 +1,4 @@ +module QtPositioning +plugin declarative_positioning +classname QtPositioningDeclarativeModule +typeinfo plugins.qmltypes diff --git a/src/imports/positioning/qquickgeocoordinateanimation.cpp b/src/imports/positioning/qquickgeocoordinateanimation.cpp new file mode 100644 index 0000000..c386074 --- /dev/null +++ b/src/imports/positioning/qquickgeocoordinateanimation.cpp @@ -0,0 +1,297 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickgeocoordinateanimation_p.h" +#include "qquickgeocoordinateanimation_p_p.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype CoordinateAnimation + \instantiates QQuickGeoCoordinateAnimation + \inherits PropertyAnimation + \inqmlmodule QtPositioning + \since 5.3 + + \brief A PropertyAnimation for geo coordinate properties. + + A specialized \l{PropertyAnimation} that defines an animation + between two \l{coordinate}{coordinates}. + + By default, a \l{latitude} of the \l{coordinate} is animated in the direction of shortest + (geodesic) distance between those coordinates. Since CoordinateAnimation uses Mercator + map projection, the \l{latitude} animation is always between -90 and 90 degrees. + The \l{longitude} animation path is not limited and can go over 180 degrees + in both west and east directions. + + The \l{direction} property can be set to specify the direction in which the \l{longitude} + animation should occur. + + \sa {Animation and Transitions in Qt Quick} +*/ + +QVariant q_coordinateInterpolator(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress) +{ + if (from == to) { + if (progress < 0.5) { + return QVariant::fromValue(from); + } else { + return QVariant::fromValue(to); + } + } + + QGeoCoordinate result = QGeoProjection::coordinateInterpolation(from, to, progress); + + return QVariant::fromValue(result); +} + +QVariant q_coordinateShortestInterpolator(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress) +{ + const QGeoMercatorCoordinatePrivate* fromMercator = + static_cast(QGeoCoordinatePrivate::get(&from)); + const QGeoMercatorCoordinatePrivate* toMercator = + static_cast(QGeoCoordinatePrivate::get(&to)); + + double toX = toMercator->m_mercatorX; + double toY = toMercator->m_mercatorY; + double fromX = fromMercator->m_mercatorX; + double fromY = fromMercator->m_mercatorY; + double x; + if (0.5 < qAbs(toX - fromX)) { + // handle dateline crossing + double ex = toX; + double sx = fromX; + if (ex < sx) + sx -= 1.0; + else if (sx < ex) + ex -= 1.0; + + x = fromX + (toX - fromX) * progress; + + if (x < 0.0) + x += 1.0; + + } else { + x = fromX + (toX - fromX) * progress; + } + + double y = fromY + (toY - fromY) * progress; + + QGeoCoordinate result = QGeoProjection::mercatorToCoord(QDoubleVector2D(x, y)); + result.setAltitude(from.altitude() + (to.altitude() - from.altitude()) * progress); + return QVariant::fromValue(result); +} + +QVariant q_coordinateWestInterpolator(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress) +{ + const QGeoMercatorCoordinatePrivate* fromMercator = + static_cast(QGeoCoordinatePrivate::get(&from)); + const QGeoMercatorCoordinatePrivate* toMercator = + static_cast(QGeoCoordinatePrivate::get(&to)); + + double toX = toMercator->m_mercatorX; + double toY = toMercator->m_mercatorY; + double fromX = fromMercator->m_mercatorX; + double fromY = fromMercator->m_mercatorY; + double diff = toX - fromX; + + while (diff < 0.0) { + toX += 1.0; + diff += 1.0; + } + + double x = fromX + (toX - fromX) * progress; + double y = fromY + (toY - fromY) * progress; + + while (x > 1.0) + x -= 1.0; + + QGeoCoordinate result = QGeoProjection::mercatorToCoord(QDoubleVector2D(x, y)); + result.setAltitude(from.altitude() + (to.altitude() - from.altitude()) * progress); + + return QVariant::fromValue(result); +} + +QVariant q_coordinateEastInterpolator(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress) +{ + const QGeoMercatorCoordinatePrivate* fromMercator = + static_cast(QGeoCoordinatePrivate::get(&from)); + const QGeoMercatorCoordinatePrivate* toMercator = + static_cast(QGeoCoordinatePrivate::get(&to)); + + double toX = toMercator->m_mercatorX; + double toY = toMercator->m_mercatorY; + double fromX = fromMercator->m_mercatorX; + double fromY = fromMercator->m_mercatorY; + double diff = toX - fromX; + + while (diff > 0.0) { + toX -= 1.0; + diff -= 1.0; + } + + double x = fromX + (toX - fromX) * progress; + double y = fromY + (toY - fromY) * progress; + + while (x < 0.0) + x += 1.0; + + QGeoCoordinate result = QGeoProjection::mercatorToCoord(QDoubleVector2D(x, y)); + result.setAltitude(from.altitude() + (to.altitude() - from.altitude()) * progress); + + return QVariant::fromValue(result); +} + +QQuickGeoCoordinateAnimation::QQuickGeoCoordinateAnimation(QObject *parent) + : QQuickPropertyAnimation(*(new QQuickGeoCoordinateAnimationPrivate), parent) + +{ + Q_D(QQuickGeoCoordinateAnimation); + d->interpolatorType = qMetaTypeId(); + d->defaultToInterpolatorType = true; + d->interpolator = QVariantAnimationPrivate::getInterpolator(d->interpolatorType); +} + +QQuickGeoCoordinateAnimation::~QQuickGeoCoordinateAnimation() +{ +} + +/*! + \qmlproperty coordinate CoordinateAnimation::from + This property holds the coordinate where the animation should begin. +*/ +QGeoCoordinate QQuickGeoCoordinateAnimation::from() const +{ + Q_D(const QQuickGeoCoordinateAnimation); + return d->from.value(); +} + +void QQuickGeoCoordinateAnimation::setFrom(const QGeoCoordinate &f) +{ + QGeoMercatorCoordinatePrivate *mercator = new QGeoMercatorCoordinatePrivate(); + QDoubleVector2D fromVector = QGeoProjection::coordToMercator(f); + mercator->lat = f.latitude(); + mercator->lng = f.longitude(); + mercator->alt = f.altitude(); + mercator->m_mercatorX = fromVector.x(); + mercator->m_mercatorY = fromVector.y(); + QGeoCoordinate from(*mercator); + QQuickPropertyAnimation::setFrom(QVariant::fromValue(from)); +} + +/*! + \qmlproperty coordinate CoordinateAnimation::to + This property holds the coordinate where the animation should end. +*/ +QGeoCoordinate QQuickGeoCoordinateAnimation::to() const +{ + Q_D(const QQuickGeoCoordinateAnimation); + return d->to.value(); +} + +void QQuickGeoCoordinateAnimation::setTo(const QGeoCoordinate &t) +{ + QGeoMercatorCoordinatePrivate *mercator = new QGeoMercatorCoordinatePrivate(); + QDoubleVector2D toVector = QGeoProjection::coordToMercator(t); + mercator->lat = t.latitude(); + mercator->lng = t.longitude(); + mercator->alt = t.altitude(); + mercator->m_mercatorX = toVector.x(); + mercator->m_mercatorY = toVector.y(); + QGeoCoordinate to(*mercator); + QQuickPropertyAnimation::setTo(QVariant::fromValue(to)); +} + +/*! + \qmlproperty enumeration CoordinateAnimation::direction + This property holds the direction of the \l{longitude} animation of the \l{coordinate}. + + Possible values are: + + \list + \li CoordinateAnimation.Shortest (default) - the longitude animation goes in the direction + that produces the shortest animation path. + \li CoordinateAnimation.West - the longitude animation always goes into western direction + and may cross the date line. + \li CoordinateAnimation.East - the longitude animation always goes into eastern direction + and may cross the date line. + \endlist + \since 5.5 +*/ + + +QQuickGeoCoordinateAnimation::Direction QQuickGeoCoordinateAnimation::direction() const +{ + Q_D(const QQuickGeoCoordinateAnimation); + return d->m_direction; +} + +void QQuickGeoCoordinateAnimation::setDirection(QQuickGeoCoordinateAnimation::Direction direction) +{ + Q_D( QQuickGeoCoordinateAnimation); + if (d->m_direction == direction) + return; + + d->m_direction = direction; + switch (direction) { + case West: + d->interpolator = reinterpret_cast(&q_coordinateWestInterpolator); + break; + case East: + d->interpolator = reinterpret_cast(&q_coordinateEastInterpolator); + break; + case Shortest: + default: + d->interpolator = reinterpret_cast(&q_coordinateShortestInterpolator); + break; + } + emit directionChanged(); + +} + +QQuickGeoCoordinateAnimationPrivate::QQuickGeoCoordinateAnimationPrivate(): + m_direction(QQuickGeoCoordinateAnimation::Shortest) +{ +} + +QT_END_NAMESPACE diff --git a/src/imports/positioning/qquickgeocoordinateanimation_p.h b/src/imports/positioning/qquickgeocoordinateanimation_p.h new file mode 100644 index 0000000..95f8dc0 --- /dev/null +++ b/src/imports/positioning/qquickgeocoordinateanimation_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKGEOCOORDINATEANIMATION_P_H +#define QQUICKGEOCOORDINATEANIMATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQuickGeoCoordinateAnimationPrivate; + +class QQuickGeoCoordinateAnimation : public QQuickPropertyAnimation +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickGeoCoordinateAnimation) + Q_PROPERTY(QGeoCoordinate from READ from WRITE setFrom) + Q_PROPERTY(QGeoCoordinate to READ to WRITE setTo) + Q_PROPERTY(Direction direction READ direction WRITE setDirection NOTIFY directionChanged) + +public: + enum Direction { + Shortest, + West, + East + }; + Q_ENUM(Direction) + + QQuickGeoCoordinateAnimation(QObject *parent=0); + ~QQuickGeoCoordinateAnimation(); + + QGeoCoordinate from() const; + void setFrom(const QGeoCoordinate &); + + QGeoCoordinate to() const; + void setTo(const QGeoCoordinate &); + + Direction direction() const; + void setDirection(Direction direction); + +Q_SIGNALS: + void directionChanged(); +}; + +QVariant q_coordinateInterpolator(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress); + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickGeoCoordinateAnimation) + +#endif // QQUICKCOORDINATEANIMATION_P_H diff --git a/src/imports/positioning/qquickgeocoordinateanimation_p_p.h b/src/imports/positioning/qquickgeocoordinateanimation_p_p.h new file mode 100644 index 0000000..946a38c --- /dev/null +++ b/src/imports/positioning/qquickgeocoordinateanimation_p_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKGEOCOORDINATEANIMATION_P_P_H +#define QQUICKGEOCOORDINATEANIMATION_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickgeocoordinateanimation_p.h" +#include + +QT_BEGIN_NAMESPACE + +class QQuickGeoCoordinateAnimationPrivate : public QQuickPropertyAnimationPrivate +{ + Q_DECLARE_PUBLIC(QQuickGeoCoordinateAnimation) +public: + QQuickGeoCoordinateAnimationPrivate(); + QQuickGeoCoordinateAnimation::Direction m_direction; +}; + +QT_END_NAMESPACE + +#endif // QQUICKGEOCOORDINATEANIMATION_P_P_H diff --git a/src/location/doc/images/api-mapcircle.png b/src/location/doc/images/api-mapcircle.png new file mode 100644 index 0000000000000000000000000000000000000000..ab31eefc8e972e16a48c50c1c23e05229716d46f GIT binary patch literal 11336 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 O0000-w1^_tXw|fo*|Mup$v8%NYRB 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
    gN~+4!i9}wrL$Vl+l|1qM-k>id*e6A~8gE`I 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

    D1?NBIm>hvj5l=}MzL!r>A7f$-TzL}Y6 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 literal 0 HcmV?d00001 diff --git a/src/location/doc/images/api-mapquickitem-anchor.png b/src/location/doc/images/api-mapquickitem-anchor.png new file mode 100644 index 0000000000000000000000000000000000000000..40581db50c353e114b21c74af49e4e97e4e32d87 GIT binary patch literal 61695 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 literal 0 HcmV?d00001 diff --git a/src/location/doc/images/api-mapquickitem.png b/src/location/doc/images/api-mapquickitem.png new file mode 100644 index 0000000000000000000000000000000000000000..3abde6b62cacacfeaa6f0a117272c86b00d6b159 GIT binary patch literal 23147 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 Wk%2|+Otne?0000?F1dkr=YVrW z6rKR^knc`V<_e(5=>lsPc!Qo>BHBWvtpceYunK?x0FM6HmkFrQY+I8hy+^d(0a^k8 z1ZO&mB_h=nIU{fVnU~&qU~EBNxn}(hw}->wEnBwOj(zR=wPP7~cVDjL`30hv+jxX7 zyZ^r=T>PUnwu62npwN}MKaxS7i=tlvd#@-wgh~N$djv@$Ws&fIl1epzH9!&oo;iaU zy}_FKs%{*7^8iWBqmpmtBhTeRfZ<}&@P|e?TP2=0dVl)he{5&8p|PcP-MaNVy7NRRE<%jN)He}{fPM>*@adn1 zw76dhPfnxXTMMno`_k3ke@9B>Fg2BZVRF@G)h zkR(ZYJ8infvP6npm!`!CRXw+WB^eP(!1T<5Fu}ShYX~6)VGIZW%aR(@eSv~uM`KZz z)&wE|m6_FHU(~M4 zL7A!;5zu^6%^Glv+?@(9xi;!+E{}_rTgn<^9=PcE^cVNnmOM*RR6vL`4^avTS7hia zabaR)z6fj_?A?HMp{_(EaLV_gP z`=WN$ukPQwf5(oU2%x|3XebnPZ1>5ZJ>A#WCrk3mmCF|{Zl99c_wGDo+lNZUB4g~W zxAyh)9Ge)QxbFIEhlYn<+4fp`VzhN$>$U6Gmx}U%y+cKFV4(k)u4^}LxKR&>N=}vu z4*&#bxpX!ePsy_Ex(pC5u!1K}(y=OBK`!jE27e9We+TMHVZ90CzYpVoB61Idbr^(u z{0KBKaI9D;wzM>Qv{Z64;}hfe-}hi$UH!^cD>ps+{P<{kba>*YKiODUm+D;8Nh#%= zV@g)8TKTyze6D3y^Ut1ma&lsfnRwGPFYMp9x4Nc!>9VD&rY&2sY-Ptv!?fhVZJUsux*0HgP>u=hyv?0j&bdc<#w+!KHs{cx z(UMW(z$vBqd~tMa#4=6GGUv{n+t}0?kH-linyk6l?e9C9&EztfT*)k{h&ztc(b=(h z$s*gb3C6)-P*K&cyd&jah!z<+M1i2V)1y%FgfGp-e@SQ z`hAilBZL6JvQ0{8k%yS5Xx3MY3sr9XcXOGLIAHAXAky zRz9F;6dCgfvJ0l_@yd*LwYVhn38Dx0_8!>NYh@=Is>G5F4I1_}wj@uOVu4{G>ej|_%wk~ z#Z-88xfC%vxc5*g!|(a@Z-&F67hZVgz`n`$rOEoL*j;zt^yE{U4|MITt*ig|$M5XC zX3bO2y!`UkU4Zy!KhZg_zPf#W%R>)591Qwnk$_Lv>Khun4;*;tfd}L9xT45}U;#>5 zU1Nd;1EFM9^2NqKUIKPm(A?kSP^4)eQ!s z%qwzU(Tc7v=wh8A0PysqFWq&=rxm6LzZPI#3L99s_U{|FiJKMO$`D^Dw+T}YT8&;)hS~rsK&U5O~cZ3 zO?LYL6b_mz5O`TtRY0jRZLX>cLf|+qLdbEQygSB(ixJ_>1B4@TP?P2IaP|BiX{vao z2c*gfcbrQZjX2|yB-@U~7?WjLa1PGK!CyQc`6L~V6Yiz&>4TNXpCo? zz5tpF2*3p)000mIE=1Wx$pr(900bduX3DE@Rk;+cv!SK><*hHJ(`f+MvSmv+8B_!H z0{gfCkGn2+827kfTrl^t0Y54ug)eBU`Pe-`q3NSv;n+x1{}=d-lr%Mg*N;BPG~Fm67SHanWKVyc{h(v#yp) zpSg=DjUIA6WO42Rz)3#T^MMeS12LzN#)=PN3IIY`h`b>X2Ee;wRN9%2g`1qofQxdD zR3+p{B0DyB(S>73l zNA*1mkt{DF9-Ob8Nc2aC$h35F7+l#am=a9iHrU9E)>yX%DEvg>Zf` zI=J`HfjzyJ=`_TSEa_NMqtD&__Q8BE|A|k2qM@N)qIAnIw$1oZSFT>M{nZ^OdRt3{ z!rnu>^7%sl(SBXmZn|{?CDNYmH}m;?|Iz+n&_Azj?*6^|i$&vx>y|boKPHD34GfIF z`pS-UI^ERNaAU`+DaU*L?LBrWTe8KC8*ZIUk8j)haz0;JvSRJ*)_EA)@AUebYw|M_ zTzRgH2dY$AADQg4tkQ)~wG}Q6^1FZZ-_G%I+0yv2+wQvY`kRKw#PFn4o0`Kp?ArO( z#Mne_eQi9Nlw`VV*PE+4R;^sMJQxZ^qp_tcmYRm~!gHJFFPLu{#!r6ysH!NPYdT)r zzQZUK{r=$N8=p{QxpPg&OD}FY)P4B+>#v=hOz+uusI7gy?ePEj?)O{g&0f8F_2%cF zH9YPQg&%+X$wJARH@__q@_+xozTduZVRK8<^UpprcW&cIzG@EiV#^38BGYQ1!`*A}g|TtgpZ9Z8a^EbM83yf(30Yvzp@Zc%fMI zT-R}&1q<3{&zThp1)G|i>KYnSwKau8(PMDv&=JEZwYIj-n%$DBYwGRoHw@Epoom*u zS+RUsUvFQrSe*9w77F8|Xe z-kMmleDT}`ZI3+k$mY$zh$j;W03jq}-+}#yAAfA)jJH(?ArJxpN-4n@V~h~-I1@rh zicAQ>2r05e2*v~p?s-n$vaPZQ7i?FNXqGvgdmx0OswFpHEENP7Galb)ynfUs0BmpX z^uo<9riJGMzz`a%jy1}XiZMZe5MaM*S)Rr!4%({#tqL?#LJ`7cby%SqMmTFum~Kf3 zUbZw|ckK=3@TR7w_QmaG-`TOf7UN=JbH=B- z+%^UCgy4!l7Wc;#s?3;<)IdCxF)|Qfa7E{}3IG5=xx=@;{;FeUJ66sO1bvS_`fS_W z=3td8ln5Yc!P2(9uRMMKefvD2)ilHxubl7q`?kOO`tG;(n5H?Ep1SFl4b6>BRaMCs zH@`SJIoaIY>^k<0x0Ou{N_5H`a@n-0$<9x3 z<|=`NEbCFlPw`1Mg~9Q|dxn|{mQP%%bz4QnJ`U|Zc=(!iYu|W%=ipf0EKa3rs$#*g z&r3XhloE<2OavchFR8S`42TB6ZN@8JW3!y|L&)$1yf>>v#^X#P4f@n$|f z=>}amo((2I2*J2Gi#1Q2<@H(Kw2Nu(PLCyw6$pS30)V->Q6ZRM9%oE2UUu06On-c~ zyO%9SPBE=XI)HY{r;?;60_pKmtRhBN92~9kNh)e?syotq^kDbV#>Q5+uyg3(_c9~f z1b0G-6#*qoho1s_@@m{(qH5WHqWEK&Fo5Zyg$)-ppcvy)Cg=GSpcfQIO)v$7gy2Rg z&Dg2#HlGrp1XmoiR|C9ub%)>QTeNhkY4Q5{hFG$`6kKY}|MMIQ4);CnI{9$bYS|va zJ^u;T*cHFM>zd_-6_%6}M=Cx(<@f89QXvHP`G8!I7|BG%fC0duV;U%`9Z z#WeRE0B}l~vU(&y0fYg784d^l1jTF?d@`aJWb8ozlB^+w&p4qXgb>0>Ur8brF}lLg ztT~<;ExomCcXM;&s#Pl)DosxZL=|~TciRk0N%im*hm zT%QWpRI&P;{7#>dGnf!S39OhM2>WFOuy3F=GG%+LGV5J5)L~X#eX60p5ffq>V}-&# z=JWy^15pxI7t?x66Pn+duj(nnO2`=zTGe!#(Pe`b9XWZu4-Z8}msiskc2Edt1-(?7ib zN6BQ&@AvQB({;_dH8C5E7gtgaHu%j1UGO02mMviS@?B zwlFh&k+yvLC!UiQN)j$6JnbCJ#6U{Wg&>dI5s(_hc-kKbP)aAphfzZ{(#|uZ0RR`m zGOcCHRxDUJk8?h7bYSq<@Z`j}Wjm#kK`0qKHrO(&S<`gJ`OY_XZu!Nwa5%hv!$*^` z*qb}H_4M>4Qq67id_6v3}0%`G$=%MRvu^mA|}TkXwL+0f!Kc00{y1 z1J(hlfTVIAAVgHAw`3>xGPe||?o^Kb6q&)+}7O;MlQa>B%(nTo6K$l^brlePCd4 z`_`AhHCkHRM@A>i!bCQce&R>>r&5heJFeOM+*8@937<*{u1s_12Lp+m|EPvL4Lj#L zxvl~F=vL*S@BM81i?15RE0<`&3c?!!WjS&ON`nK4Qbe$4*#cpp=TVGKWcS;9e(~bV zxokcd&@lqXwmUjImMvS6tV+PN^^G!P&JKX725Q7SqZ zVTl$Bg~`z#=Gy1HVTEbv1u2zMnALsqLe*OHX zpAW=F0PCR62dT#0|GHmG5lJehv$#5nm5cDCUbD7y;i3gEZr-we`;MD#@&kY@$;`{m zQoDT!CUEpBah9sw7F7`8<@U5`gB+p1XG4^*7)8(MY1!a0TNP&FK8l zy2#w}E7x-}#;D7zGe<5X>PmHz60{w=9Kxm`haUbGDmI9k>%lksvaA3sEl3<884%?* zmlOpN?Bq*&W9qyXqZ4`Tv5ikY`IPH<*I&Or8a?x5U6ZPLZTpVx+qW8qd;2GDM@osHsVa8f()_(0i)UB0D6%q> zYj#R{kbh0|)y)swf8RdOHfptl>zAhj`VvB$bbp;$IGXAIsng;YzB!j-3t)MW763>_ zZ+{>Zpo(mpj^xuZA@gfHu55>YIrz@A$KLNIoCGc93yP{S#)J?KGoSzY=A(T_Pe~6l zO{-Weu3kBJ(SqT*%QgV1#-$@XyM6e`PyC_gP;#Xnnhk)m{SVpAza#Xzi*5xk3Zx@M z1Y@|IdM|`fC{z~P9}?)~RHnLmTK^zIUb$lB+VyL9y}t8jPdr(jN^!=Yd3sYa8C$V( zxu4`k>^Bh705E9tAv)V(c6RWIQqL1rjkoBbSyu6wlineun=jh$RxUPj%9>GE0UsaO z{_?Ur`b)!KzTXWv6IdKyiC~O$&8MpBsp&zs)6v0-)2!6C`VjH19z(HwpsV}ezx|!T!J(P-pt3+Hs+!Gj zvaJIrl%72fQK9KKn6KHkvzW+%$Xq(|3|JGF)*5CQ<>>v>SLqc#$ef@KBj`DD7!WL8 zx}>VA3ZEP)##m9nY4c7QqO#`CPNnxb#+X04P%`^b<}L6p$;Mrgo)0G{t*YAQ&vzZ? zp$8t^^Ugcv^q`rKkSLVBwr9KwQmR>eZ8md|dqyO+UY&Xk?D5N-+OV!)V9;ZQ_Z_-+ z-5QMXU;NddYnloG|Lc$c*b^tE2mQtW`{$~vOpH9sz<2gdTac)_(Vp66mPUQSW=)N{ zefN2Z>qK+~lKht##8a`#e0ZQ^#oqF(tP5~_RyjRAh&jVR>&_(*SaxeakvaBbMT>{4 zRx72UV)hMlXd{cR;#KRBS_|jcdrGPXH0?W+bRFSJe)NY7%Fb5!=j0hl2#!ELw+{}5 z=CRSp)X4UD?X{Y}LDf@2u>9mZ#qMu#gt^|r8$Ja}{0t|uEU+vHh722VTp5QxbX@T7 z-WvntKaZ~i5kBY37H7kU9?Xnvb)6|pm1Hgmh zYeN9QAynBr@590ciILz8cm|}{%+zycoIWx6RaQ6KS@=J5zIo##JH~n+&riMsf>Svd zsahS>B2xF)QFaILlj3*;mH}Jpd2 zQ+Vlgq{O%$KmZPdiuLis!v%NPi@^ehxMB5fT+|M1;dN<;w5BDzdO zmvI1e^nu~NC*su|zR;YSrrWLJppoBcjXvesQ(S96!WD8D=ukYPN@~Jl7yu#9t(cL{ z$_MPpT!0v{DNq)GHvx7RkObhsyC0P{L*X${mO$d~fhGW{XS;@|{}fYd(xXo^w-ksj z)Pl{D))?R%f$7?rv|VyN2N?s1Cu1S4AbAE7awrJ2q8xoVxPb48gu&?++I7g-3`%=h zjD*vNa{nQuc_{X0Kx2T^^B9MS3RTy7u|>I_?-zPEGQC0Ic>+`k1yx^2*M&}noLm46 z*c{e{C0PLt3FwDQjYjL^UO-Oz zRYSr~xl+oM5fOkwYMx@Rd}PiF4j1ZeSGua&)Oq9p6&?m}5cKPi?*_mj!3&_S1Emd4 z+qL+@Nk!(EB^O~*JL})_P39UBB^g(ktys%-s=Gd4Ds z%jJs2LVdkwmCXL(p^{-lqw(6>TG#U?Mkkr)WwKdCQ5x!+6S_27sJ^OIoz5jj!s$ca zAO!9PsR57-fFN);0J-=<=p-0toUwrJlVtggvHypfDckuy3P&d={>gP0IZmTYl@bUh z2z)Rm{tqCO(nKPWs;>1sx2tP^GMSuJQ`6nujWPKB{{H^{Xe83u(0HWh$k1?qV-rb} zt6LoGTmpg2Z9<-pXceI2=^5xE@-)syCX}nSWJG*wq zO|bVPW!_0E{L99iGG!uZs*EuL0EHr1(lFM5#zKkDNoopUdJLNI1>WpWUcp=cl7SHN zgi;O>#TX-m5Jp1qa>O6T7!WBJz|5IJk6&HW$=SuodIOZjKw?+0R*T@w?~8|nQA$qg z73T#2im?_{<#NuvBnJ|vS*4e*)UAIh38@92PCd^vO^b70DjAxlk>f#Hjz_sb0N}B! zZWlPa7-^m0><4f3%H+iY0D#0ou~3yPo$8q5tSESHsP8KyL;qps#{?4yA(l65!o@3f z>t8ZdRqgBRJ9Oxv&*!VFt2=V!$j+U+9LK4vuTzv$6_5A8;7!9?^2B5`&b@bcvI>6N zEWTC3BJ7{unF`Lbx0U0WJ|#r(v=Zlf4n{zTbmER!+(o(`@cROUoGQDCy8mt3Lw;?I zTIVYiho(C&0OJpq$^V1H;w4MD;}Mx~q%}6Fjg1ZE_ZjFCao4QrB!sve&!0aZBc8QX zxXL9hC}N6b7<&%wvP`R3Dz>*T7#$rqiiO_3qnfJSe#@eo>ek+)L)*4)OQ)x<>1bQL zXhqI&_8i(($QSzi`*mI4u;Ip3s=BM|!1nDs07PR`b1WKeY--rH?bW`%UP)3`tz5ov zaeKik?BBi9w(Mf5#2AZ2qSvfh6AT6)d+gCUbLX_w|DW{W?@{ejT;LCwzvzR=!O6u4 zgaFDo0s(}0N&iILmsT(z&DTtpVjg=}Z4iL2_D(6-b6vxcPf>_7;o9~i>dU)(f+<&Z@xt^RkZ44^;a3&&e&LGyXx~|yQ<}Cwkvsz zdYsy>>X%CceTRpKj&Wu&PCQO${un1$P{BPT)N)FUGskf{J60`PvS?&<%(m^h^IB)k zZfR_48XBH>>+LQH8j*e z8}Ne=nxtZZ2l|iYvW09WXIiF2CC72ru3Ni&`SN5kwtf3+LnFhvT)w(Gsq1>lv~=Gh zqTk@TUFFm{Y}rsrRXYnyYu^EYoE6O5!K8m&Ar8STlg;>a-KV|}4~@+c_&)7t6}>Z9Ymwq+)htyu0?fx7@)l`GM- zXJhZ4U1bY(LnxcgrjHCB!x$Tep=;XvGM1k-W%8L6T7)=fV`HPSShS(30RWnt8rNNW z&24wwnyRhFgaSfmL|Ac5Eu0W{e%}>&FN$U$L~hrUsw9ymy(6 z5New@x4XONKv#G3tR@di#E%Xi8Ti2i&%Utt(91oC0C@ysGgH}c_{=mmG!~1+q2d0i zsqupc4}?OY>gsCF`TLb>d}gcE#>bv`^il59eb?WxzM-!ESl__YPd&4B>#G3p*?WF{ zdW=93;2sjT;BxEi)?07A`SsU#c6T4PEL)bz>^ZFm4jlZ^gAc~ziBnSEFs4HAh3)g- zd1rTBeO)vXwYk}~_n>3*&wu`lkznwJ&Cl;1I#5$6%}uqu7ti2B$c%+LpUVj$B9U+) z5Y%-IzfVauG!ltOapf=m>d#eGG29$NCIH-g^Mj$ScI@@n-|)Z>di=#nl~!D{XW_Vodd6mr@mX@3-xFxM zGz5UkS!tRS@cZ=k_64Rz*OUDiO5jCl-t5^SSPW2dE?wjLG|yu71*2#RSV*BLE0M@DozT&WQjV&DlA` z2Ff~<5Fi9UTjf6haG8@fh5-N};6lWGH51v<1q)k_c0Ks;gI%`48-u})#@h7Q_~mu& zml=$wEz}IO*12sU7)It-9(?j0pGtzNV#cF|-Xj;+m|q#VpqfN%Pwme%*q-8(jet_{$t8!$ zmZxIG5IU9<2f8n_d%vt$ zX7R|uuDbdL#v2iWxH{_D=s&p-FGYp-3Wgv)~uLh>xm4J6!)3Tg||gkUHf49c?X z6%S91eb+X28pWLjsm3WSpy7z3X}SJOEVuPb5A*CyCX-AiK?nrGwDLy|?$tFNW0cPq zTIaUb$3$aurw-g>eK<*LC0U@KB*ppoCP{)FflB>sS+`6U_5m&kY7c<o-0c6LgXmM!a4$SYT@Ec@y=KKl6h z*aQGzj53+*{Xh6oU48w^)vJI0%=Tl$!vs-uG3wE1q2JPLkh+SA7zt^b+)Ic97~`Oi zdoFMhN`^0|Z@+3xrPDn}dJMye$K!qtB#H}#0%Oc^9KksNFve<9HOXY6>{J-&?=O`~ z4Gj%0ikEEP<>b3zzu%Y77v6Y%mk{V^U%zRZt#jwjn?E<6h!a97k#ya!s_J0>P%c}@ zX0o{78fI@)ba-Y<&J0xw+Py}4k3h>7w=7#Fe+N!1`c5L}&A zQz(`^*VV!mF}etB$5B*u(V|6FRmprV_x3w)PfcYBnYMSA!5EJfQ?{o707BSt9LAVT zY=kaLf1nE%&cElL&%E&bi{JXjx8^TcAjz^MNrVtpRWU}I>_6Dm{n(?ApYE%-EQ@n7 zHZtlO^4fLl8fq8&WZg2K;-$?X&lXB2Qsx4_0Hqt_vRs1P3ijJT6Uv;%S2yn*>+k1M zrS{`RP*qixNW?Q%mBp;|)G-98x;jxRn&{*}iJ4KSwyt(`boA)aqx0seAuZ=J>5?P= zDF7^5yf_+*{@b^{D@l@3GKz(QWm}VzlL3DK0qW`LMF=flv2=8F{6t@UA`ugux6GQg zaBf)kr4Ui4N?a&k6z(LlccbECkedW`83aCYL0h%~fVK|O{{Y&m?gNu(_S}0m+!~EW z|Kgv%b7>v>6$E2^EZ3B?VhxdjXe>HDKGM~7K+|+W2txRDKMBUTxw(1o-n~OZ!!+D* zIk%?UUVi1^!9&9%!@*E!&DsvrG#>iVPeP$!EEd&$IsmlIpTF~sH)nkHfT*N@=AO^c00^WaxpwcdwxaRr_WJ!kdJs+gx$xbDd>a7!&eIkW0=XIN z{XGZyT_63;+PZqjbre#P}B7|U!IdUmhtdip$ z_tpFLK*=b{isT7fFzh;xYfQ+BO0~$#``!AgiN29weX`g)9JCy?sAa70nAaTqQ9vFR zflq_fbPf~c-riVq^mV`Y<0m)Oe)=>2J06cS#)5$mzz1}PgZGEO1OWK2P1A*6nIIs5 z;A!_I7nlo-KoHCbfwIq^^OHs*E-*%?E`iG@UUH-8c&QHoMu1F*XB+|L3QP_hcaVt_ zvO54F$T$c9z!Bw-2*S*L&ajK%U2nN9dwOEApmk(0WI9v`7@Z0mOd0k5yX@a77dL|c zPLNv8CP|)$M~h1u=;ICb_~n{WZS@ z@>zq?MyCSRwd)rw>^^!F|K!IHrza~`><7edNlrNfq;{ROINp-H!-GUdPptLh#tdTN27~o&GrXA4uzjW@Hc>-R03;nZ>(rZ?Ja4a z>VdVy&$}YYR=jrV@|#!TJ>YUjql0@6l``U<-}>i2{*%9p`cqwRA3kwo^|El3UwaJi z=}8pDa!^-8;R*1@PP~riVIkl^eJQ}bhib3lDsRiR{K_2L8erTo z%%00^UWNCD%N3(jVyHO>Apj%nlYD%0NAo+OGtU+ z*qG*%TXY;A>Kn_99;wz#5ua3E#Cmwm|;c(Cw zPsHsyYtEeZw)((~1*~^W5e&_$p^AhI3v+!1#N3*uf&e5-vZ_dw%2l<0hEYJ)8!xwi z72XprSBxknZA<31E}SI<5Q3@wOjr-^c)h!j&)@#hyK8IfU5}Mq{NnSEjtun^LRNLG zpEGx1u~gjtO1Euhj~qS{3I-Q0n!jiF9@8|}-msvpse!81&%d;G~y7cN{F4(WtqfN4J+p(LOSN)e@0*L1&6K_G00ty;NSmCQ|>wtV9o-@Jm& zt8n$WqAohOZzA97*TajONg|l5iCMFnA{g=B-adlRiWN(1QZ?j+h$UT_#E?B9RSy6` z$VW9*UbblQ=RbFY;O}|fUWH5IikgYt!&COU*{Sh8Z|&Ll&SA^2rn1G4eB>kHNa)d@ zKK^cX5o}73$KV200U^#j4~n+g=Q%|X0wJ{NxlISUx`s!F0>Q+UY+i+{2I?Bxaqc6W zVw!3}OfF#2H1U7-41aN9U3YcuCzy!2o=1tKswzUrD2%#xN%dDVx1h`15^?* z1c9oQub|74E^Ev(q#)+Xu2oWC*N&Zm4 zpX9(D&oZ5Y$6X;@f(+1>a^&OOJ@s<(JVt5&>qEtKZ757BEf({DImNO>2}TGL2_Y0t zel{4qad6Iio)G;AplNJ7cqBXJ2>`~Jsw#USG>a&t_J6v)XrXWDU-=P{LoL6RSCb}w|CF}b=R)HVZ(K)rhj3~7R&@721iA^ zfG~;sYtB-dT_C`gTe3aVC&w^Ij_VvcbSM&yw6`zz`C|36zUGvA0RH*PcDTaT;H+Yl z3N;TKMT<>OE3!X^=(HLn5yj4eWm(IXEp1=iE&%uS_AzJuvB}KVZLdzH({tOZJ3Bf9 zYDibYgZ)FVzOo~oo|-#v-uktjmSerKYghhErP+lLJ-t1P{EMct>F1w+ZlJ#(<4W@N zhl6+RMyXJ9ZD>{yCH*2%rMIZmrvze(PemBX{uq`Ngz(H?dBV!)^8@|;!$U*OElv4C z?g#fhkVwRCyZs}(-Z;GF{M0=64Z2oaWO&%7ywDTD`rEmtci-7RRF-`jii z(VuRVR4Eei1p}nMzP{&3*AtIERF$kaLp~fJ$3D*vineEGCi+Xnp2@V7h!WFNo-!h6D#{|Y-eD34KOo7nOrOR9X$leJ=|Uu^ zQZXsGAVLnom_u9hve|gJ$77f>7|^ zQLYW*GH1LvaMDpO%;aJNAj9Rt5nS*L0M6Y9Y)sI4pf!2Y2ppF;(G~!Edt)Vwwzmuq z9P^g7*7<#k>&O#FznrN5+i-0D(4qevo%n*B5sWYZ#8ZDgG4LgjR#0gU zRYO;%mlduuSV#;Lj4%o+p-3QNlnjr(JEBmxPqi^2Nx*)j%z@aS0trL*-$dy-;Ad8< zgwqH1KzV~K;pG5H2%l?bG}rbdiOy`jqiPL-$VVr~zAsCYgjTApeK?>PPk+I~`J)J- zfR-#2izFB<7c2FEAm&XUi!dNR`0o&QJ2Eyw;c-wF3H??iHvpo7Gi1I3#%6>A;0*yw z14$r~1YS6I+4Z2DP$eB7AO8@-;lyv3`v1rr-|SP9SF!6At{m_B)XM6Pfim2r0l$v| z1pr@gj*+^H<^Dg;Zv$;HAjv5esoIFbzXLoE=4&YTZG;1YRv~jI$aBE=F;M0JBEU+5 zjUu-XkOKLQ5V{9w)#nr9HoEKAzKQKe|#hl5k68;K~2s=A&tv)v@d zv^w4#2!DYXFM&6L6&>O7$bkUKfc>B>LjIdUsspS~%h2?v1dt3^1Fb{E{~mZAWxfec z@66T_f-}$Y0=l1)Q^iP;R%nueDq{2@;neR>CaNUqSu=CuwhgMT8_r5p_>tuEqtk=3 z(fRTMkc4OiLZ1_M7i9hwl%;?G07nDMbN0g1w})^%#-IUskrfVHN}npc=bR=+R8E(M zJcb>YQ7X!&NQ6)%95==~`Osz_?+2w0a0E%wvH)R(5g>lfJ=6dIkP6BoNc;^L+XIS^ z5T!W$Hlta98i04pXFH~qH;NwPK&ndC&MV*J3ReMI7im3Suhc6!`HA9iF*_7cn;h5H z6d4G?M4HN^9ovdF-E;83L)!QO867Se1szISZ7kC8$1aN_f;f0NbHY141p7n~1r!KH zJ}#lq)KorDdrR@qzm5&QrG?uS#fK0s8AT+7q{vl?WF8h4vd_FnkDklFO8N2ZkXLppGXicuL4WT;Q!UCayKd2IpB?V&| zOMKDHZF-L$LxszOe*NCBuUXSU2z_(cE&=Ep-@JFpvN;8j*Q6Mg6hbKgy5WW!nwy*B zi7H+7`}7b(7$FdPydl+*KU9*)?|MJ|i+7`U_+Ro07HM{$FAL>3F42NY&nmv1#s;Y#_>8XjAU*0x& ztY6ptH*UBwk(jm+zVpu3mo`5i@Q1FuX?-}Hkg>13`_Qv{Uz(UmYnrlZ)vEUPMULx) zL(%6pJ=d{%<>1KhuAOgUgs;2)+Jt|B-vWAE3dv<$mciQuxei0N}qDUE|&`L zD-%KrB5Qzja#y+1)CG_v#{_am6esu5Ny*^#N^R7Fedg|#+CfC`~i7@=P-~R6d z{YO`Kbb5@Ba-uTYHhaY(0{<-u1$Ht96|C_)0hxUc@ zOGfFx{_}f(``3T8dgX%m`}tJ3AZRXMeEhK|Hf`GU_{N_tS+e-HJ8ln#LZ)f%*sV8$VHn*92OfCv!4)f3 z{L9zA_WkdDe{a`;SR(l!-~C=qD)lFS`qe}-arQeLP&5y?;7)z9ra@ganS+i7CedwWw z5yoHo(*IRoSMMBA2_e5kwExmAfq61(^AIe6fuzQg-V#q8AN zxRKr+4$cSQb@g@i^>yKJxVE;I5K<}@_Uzp^I6ORW-n=6{ho`b>S(b+e2TO&s7GnPu zhmvVW=iFpB9N)M4eH1Ai4Eq1z4}b4pzjg0F{_{T!4GsP92S1#cm;eAm2*%_Tzx=G0 zSzrJ9y~~!i-}n9R-+k9zyWV`gEJnKSE60|E|9|%0J3g-Ky7yhXoI1Ut7m_Fh!Cu88 z7FDWQvTVy$mgJgZCpXuz?|r^6&(6zByK!RYT9&QYvDIzKlB~uiQDPBEkraCeNc0ZO z45pqrb?^5FNU(txEdd1R;QN`+hs4a8bM~BBv)fwhw|ss|X+EzF#QVSUm9IVcg-5o( zdoZm@j8o3J=_J8U0(S@@>A_7}dG#%DrH;t$>YbKdHUI(KmLot!lGHT=g7(* zPy_kDCKNlA*h7yID~DdIW}KTF)z#Mtg3#L9rt4R%h(rikdE?533l{$5v7bKnlgHMt z+f-Ck3IL|54>?bg$)s)DfnaF!TU)ljyX&EcANtZGkHiP#>(_6TB*7h63}O_J001t} zjSS+p-p82*0Fg*g6ouJy=KRgy{Ef>SwH<~rk=bvq)qS|`Y3Lu%(G3GYCY|o->3QkJ z-^F6FX;sq{<%-)s@kA`I>u$HFva*5-l%31YK6L2l@#DvnspQ6u>$F_7-F zL)RrqdT-b6WGaOaAS|RcWIF%=tplhzHQ&UAfJj#Bt*Uet7KC>0*m?M9gDBa4HAS5^ z&a^AFWReSs<-)opbGTwq*H}Y4I=en`{{t9fQIu}I{noF1<*VW76_?qsz4peBe)yww zI+e*}XU&>*@4X)j1Ofo?@FNfX;0OQu&2Rn%=j^}#^Fo_*$- zrOTJ!eDlpgzZ(EN=OT+7#vLvoA|ZraN>p`M{^S4t{y+W0zy0-JeS;EgTIM4Uf9}Cg zyRX%KxV{<4hD}HO;N!*&)Yf^nz#BqXl0*j^ZrPhkWMxVA`TPhRgoR8dV_BBZ=d&y; zn@J^-2~m{&zJS;3#TX}(NkJ4W(@G>`Zq?)U`DIB0fMSp)dH?_*07*naRBSfe-P)xiU5G>BtHB{0RKP$zup4IxkM!nOi?7@4hOz*Uwl!u z8uZBkOcY2k81M)D2mm1j++mL6^|$~)kwlMM84_@0js*a!A|a*Zc(=ARAbJ!E0G!zz zVCZ-N2Aqik5q%y27;SGV;o@k0r;%mCtUC(2jG`KSs4{92iX_B{R19D25LH{p&iSg z**1>1=DNVJ#ZfQS_#vRlti<_6N=Bw>y1l-k7<<#^7iv>)JeXcwr3O7$D?Catx_zEk zxOCs(ikObs{?d!t&Q4p`UDNg#RnG^Qj1?IHN`<2E3{g-GTTIzjSKP9lQCRI)7pvt_ zMHcZ;d0NA?4>V`fntf%Vt8?wSVMg!K=Fo*tWitIfj~74&K)28HZa9$MbTEC#eDB^f zTJNCshbo>+^lELW<-pe1+q)Ek3#XLj)9G;4YBe}H#l8Xv<#PH{zkPZ3+{KpbibYrd zPznRm?k%soJ-+hF%AN0SybymR;2nonBZjZ03e!6PR@-+rlM$pWjck$WgdS}6r|cRRT9n) zZEgK|f>A+0x_l5YOuMz;C=H2_qOGmHsB}tQozl_KwtLsR*-U1^ z!o}0;XQ$GcWBcFNa@m&VGd`cUy0-r0@gt^@U$T77wDM`0RR25Mwk%t*G7>4-yK}Q5 ztIL-!X=pgPXZKEopsS-L81Qn=9LM3D*Oj@tI@;cR@7;7VF}138!3{TBw%j(5x9p30 z6nuQoyZPMNUB01m)wbhMaVUiq9L70^we!56yD?B05M*&Ut!kUjEeuTkr!l$M_0N_qJIYE+`g^;w>Ez&G;)P#7Ra9IuXWqiMHmw_s^;o91W%I^( zJa*%%HK!XJHm!ec_S^+jl(%f&;23FL*H4}}n$2brf~Mxyj`rr}mbRB)d_G!OvS8s- zOei~-?@9$B5s$s_+!LX2&chOLR*rUy%WL7%T6iQ{0 zhy{%B?en}r4_!OYJEjISgx1j^$ug!?OXXa3Z9>5C7e4&XsmXN5k;$Y6CB+R00q3V1 zPUW>6=bTWI$)x*xJ1kQ-EwiSku5wzf*Xu8@m{L?yUQ%3~%Vu=b;+#8ozCaAs_N+#Q>qJ#%bJ@S3_W+bA>dW3R>yj?dCeGF zH(Hi+xQ%5E004=iJLY=}1HzaZ5CE8tX{6EuB^YBpm0=;%TXdfwm93dInZrLaoO6ev zp#_v_;9?P=rB7Znr<035@Nr30nR z4S=P|!q->8bYS?~3`L3&LQ|$y)zr-#(#z}V>Ad*xi@p?55)s0dY3YWU&177v9Eud{ z#(TM3I-fU^$wVmV9X>cxSY()Haxfl^77h#ygu+otk}hAm(XCsz`U2AI>6YKEc6WF8 z=JJkhamSicRwPRTb1X~mPQ`we&F*1l7tpAzR0%?vB!sohHc_dfxNzu1mRZtLdxm-t&o>;kR<(+HSdc9sF+W;USs$e2;#xRi)qRfnQ zrct>Zkzq|p)ZEhi9({zDgDo{}*vLK{$ zc~O!EvrG{fw@uE0P@*$>tW8VitV^tvE?u*L3KSv488gl)rUSw)&CQyob#%7v+_7yi znVmj;#+ucuyUb1A-7ryX?>guW&UU;dmxBrr}$K!p!{KYS$1(AF2 zyJzSO-93Hl)@^9%JhgPilJ@qlE!%d{l43zs+7F!w&0ioy$^elbz0lxZYc*d$4`c+lL>f#Vm*G=J2bIX^&t- zaJdx3!GlNMd3#$jnR3alrZdf&rY)K~tG249yu7@%r6sRvoO9DKm)@{s{=9i!$#ziR zsWUqV2AT#FF~ig^$-h2vvca^h8Fe$Nrd1D}A(zXUhBQ;t%{ZI`LYAic zXUx3&#^T~2)h#4RmCT8WWKs%ZWLjEUySh4Ud!kf$y3~wTu3YiZBM&ZExMa_+Z3BJX zq9Eo?G?;Zb;Cxuf*`NFCwNkgGZLr_Zd12(fb08~b$t;e&cyLHRvhLr;5 z+IVm>h<0Q!p`1;iN*I^!%v_|HzF>ATo_zlK7k($cLMdIdW=(lJ(2`8*&&20LFEK1j`=hXsiiKFcijQ0|A~-J+Jwq9HYr)QBJjJ)&jmvMdvVOw-Jz zS{R~|9L?$k5K#ojr7DDwWJeoSsWq7H*tjk@dv3vkMUEg3MG^G3|4A_T@hvARK5_FR zfXM~_1$-X>;Cp}j_n~mOYTm7VIq`CT`?cp>1)~cT(@n!Z)kLR;Hx~sw`xHl;%V)PZ)|nix%WAp(Emmcj?owHI8V)ykwXC*wQCZNd05IkNCB!yf z5`=PDymQ-0E1t3MSm5&@#u@8)?d3>GSaPdoEaojMr?N~D0zg(atsR#8&%7PXPi{Ys zOfs49`~BnHit|u#bRaSSA686vm~A++lRvaSe$%`e{-B#^`F!5U=kwZ(LO9Z9H+3fq zl~XyVI%^v?Qm0SJD6+{pI2QT2ZXHCo33@Odt?c)Cu$(qoDVLtL#Fz%gK&lQ%h?@ z8@AZ-OrLw|w8Chl#O3mO6sLW|+tH>@?9zOzh!YGD$wW9t3A=Ecx~|BlUf>MNX5ST* zIzq!64`vb>Tcqf6N3Y;g8-M+@t?4em+glWkRumFK5W&CSk@8=eFMjDy2_YQ`yDxXC zUt(OKLccV8jKw|({RLZR^Hx8L5od;fI%JvE&L$M%N(<+bHn zVlcfaCN57))j@n&jO=@-QUQ-E=si0DG`06-c`hNoHM2Y$4x}$UQVj*nye_%aXmurX zm>|+&>oE;0pGXXxo>&D|CJx(9Y8-$u?u~G^)y`Wjc-{ilT!=#phGK?x9YCb4L&bSI|Fo z;mkx=Z{pdF8dU;!FLM=z5Jrk5=sC3EK>Ut{1%kQmOxxb->PG~rw7JhtX-ptzN3^yr zn`7_q4VD$i9yb6Wg!1WZ&+*f?ZPVh=lm{NL$ZvD{VRyyvQvsZ0;y_~^Z+b#A4adzv z!IS_%Wz@fQ%jVU$to8f-TeofvVgniZSZgxb*~@T_85B4!nN#Nu`a@+U!IHw%Ks@&9 zJA*_{mCa8V1a0gA(@YP(IT+vH6kl~}5U*WqpX}5%<9sQSOhZFqWwGk>B8;RWO;qjP zcTX$Jg|hn3$lh`yDd|*;-t!KED~eJ~GNGXEdH;yHp>SptLif7usnac9-RQ`+JbCg6 z7b)#c)=NUF(3HvsJRgr7AABHdglgITo`VP6eeUSAazT~?g;8H5WEghWsRO~2J2>b4 zwbL7jlw!A6Re4)F@*DSdR+dCEdHjA;e))7)?2Q*hY)dZ1vh2=7C#K#~D0$>?Rc~5t z0`UeSPeqt`19znR9|f3LUH2qcg1)|SZ);17K&3^C7R!5omRFX)#Iz(j-LYO-`cT(|zA0+z!9=OG5gfmAGi^u!^OmbCFS+F|V z+EU3(axj(G^VCZKoz*xy+8i=lV@z1`@Yg@@_ZM#3y!m9?F=J|xTM+>O2^0uo%y9<- zR1newvBB;EUo_;6gcMbh6$t?h&8Ue$r%o#?G0k+9DY+Cu5)lSd&ojqyn2U1)035j6 zvj!wflBc`;w_+0LZhnXg!EwAblUzOY(Z`;hIj2$-T^OTF!tZSxH?{b`@Yx4SN~Rd5 zdDH5Z?N2_#O&@^BnS->A`S(xyg0-Sc0f2aOCt6q+>*(*@wZC{qwL9q7v$<@1(99dY zk^M?7%8BU5 z_yX?-5}A`7p-qzjz&Y6lfuPgfTKi>btk)Q7n%1+=JQt3HCCP=I6lZ6XOSsuJ%lrT0 z%G}ni#j9_+e`xXVbDx$2E5yLP0|Qjc*~|fOteUDKbC)SsR}!}h2#KN~eBx$c3@}2N z0EW^VZvj1RI%5Ck&Dpj#rpR2&BZo!i&dhqKWW!*)A{B)dbdg}FEKd={a)4*Ys4?zHM_;5+%;LjFbJH-v-YYK&V9eyA*5nwJj$P{o=V_9My4Q zVOexe^$9oWO~;o#{nRei<%>omilUT7m()D?W$be!N&z5|>k(a&D*7-wSNGUXJ@ebQ zrq|D%^|6nePBvo>D;?0}3G&G)~MEK;# zZ#i-D*ug_5np=7k@z~tOGqECZM%*{eP5t(rplNcCC+mgc`Lp!cVEW9No;?S+<9KRo z{Bygdf|~KYI}?_dp8NTbY=hV5n+Tz_R5b@LdP10|`DZd4hKUIo5? zeD2DGCt6riURHj(v2lWg($epK=U*@U`;Oa|EL=7pd!vpt7o~m$fo}jU2Sk7b^)nrM zqgFO!X3iG`L=uShtC>27vn7 ziup@!@OgYEPMv!3rPpTGPggtwCdkwV>Z=RC`1!RP-+b@ifBf68edgzkh`FD(EkZQ0x3-Ww__LfO=~-kgccux!Hw38kf9`{M1@Q;S@xe4%&% z*LDN;p>#L&pU&+z-Lxb)t5#HG%e4ENPdW8-5kkND&2QJQ-zdw<@*9^MrupnM&xXSh zNs=fPw`|>i>QqB_cemH)EsXjCYNjCK+w%67{=VL2YZjB_X2l-#L>C}T<85!~<(~#u zCBhO6MJ*|k`@WSb%9;BWu=mtVZQ z!cPy{Rz^|^Rd2QJL~ybcb0s|x>u+rJ+J2eGR~5 zX2=D69{}L^ncgAUhM`c})7>NJFa_34~9L?JqNl+0&hNWR&FR@5+fXP6-$C@+18-e3&Lb?G&1|z zYye=)oFJjJv}$@(kVr}!u-Oy|Bme}6U@Bv90Dw8hK--#v(=nLI@!Wz^!~wfGU`%7)!P>09a7PqLT8wKj2qH)vXi}EC7Ij zNlU!_qyW+(YT=%fGqb&QZpJttb`^%POU^j`q$SJKl@O6U$aC^>{pv zXCKR+`s?W zth(^&j=nwbAIfQ|vZx`? zy5+g&UKqbSGszzS{h#0cGeI~%&{Bf!>_5f_vZxD5t|Hl0D!WSsDddmL^Mz-dM#45V zLH1)JA%w7?APbwORaISCRUucDMLN3&y}_bXI+(#gsbgEV?#}4G=F`Wf zO`F!=KbY68rZY`<+;RJnBS#ryjSXjVT5k5-*`9!GSZT&=0I098FDow(g+f)Axw-x8^)c&4bnDoKe-3IaI-oO8}?yNB}#07#OF z?}A z2jY&<)AH&-+d3nkv+W$`4ggT4&ZZIv4zvWY+Sk_~3I$b{I(yEXPk;7P%yIgA`vCEZ zii&;v4#pIlJjs}RW9mWh<5rn^9F&)#4?K ze)sY#Z@;~D*|H_Ua|0Pg!sLSgdf}|T(M6=5;z&iVTY+3AlFyk-aJ!|5ZS$cG>$Z-L zpFH~0d+)ucuD({06nE1F!AJqvB#4|OfIY%X2y1pe z?{p6JVV3Tb6>-+A`jXOOCK!X}AU7ERoZZ4U88;XQKoCa=zycW7;2VyyHi2^vfC$0> zD5nho000lu@?;_r2~JM2uMb8rBaXbtX$3x#83AzM{G1Y!W~O}J!cTnaJ|H}!$A%}! z0M5m4`QSid#xHt{KnZYe2u>?H%!HHL8~qa9^<(it)`*+iF?j=lQZCsVObf$(u((2ih6h-U#w?2R(~S`BJf0)YWo zg^M*tCYi8YI&yL5Ag%)~80)W4CJLnzcbvSFyL=CmJ0=2w8^_1A4c9NVGiD=%zWv?5 z6-D{|Gg*Lfu3vbm86kZq&>}?qFqR=>lt>B*_V@IM3&H}nP3O|GkcjmHc4K|t*tOxh zX5)dg0v;T6+h*gwb9C7}I=>6#8v&1PN-qLPB;rp!`Rsik|5!n#7XT=c!tNr9&vt?N zRGUL@Q+;ayW{qh}u2UwQO8-TF=?4&SE|vox^GOL~Owa3%?EnXiaoAPV-QK%%$L@GM zQ9FJ54U1|5{w2p6y2iR0lZ*=Ir`X)=g7j~=)qd^e-(T9Q^CEaZ1JZP$WnlLK)30$m4AT}x zQ4j=-F%cxkVMA53C%G=bpdY}wx4?5hh*ba>q)?O*WR|Ys=7Xt9Q?^o9=it3nB*fu!TT8?q2akU5-R{N2t2}y zOj!oz8DN=FznDh~W@2VO$vk3I6}^P3iN0($rTxcGe{uNG;jYfkC!ct_tD`3t>wo&0 z*E+j0%Wk-N)Y~x0STK@n774)eoSCD(fg}m?-reuN@cc`;Ox~x4j~za>`OPhwmM<foav1>B zaH=634j(*tsH3AZo5?t~<2VlIoO2lZ&-nX_Cb>FB3h6iYgIELDGwK)1=*g<83P}<> zJG#@!OnX~L8xwASuI^yxc$<4;=m&;tUEDu&YxNZYH`f<>CGad1l7Zw(@x3wvf zO#m0V^<`d$z!0FVBN1^^Cs5Q3`e>Z3=G z8M;+gR_an!%_I&(hJ_hBdqu;-jDf?|)}|Aq-iArWg3AWA{5~#>$%rEh;qC!p>07C!>01f>E=-hvxrlxv~nlZ`faU`ZMi=mXfQhbeWOs3)2B9&{ zoacS%wzJyFCUDOBGwldn5KKHQWYWDyiftXsmP|5gTx>>&BBvL)0WgLIP1rekq#^jl zf1bq}z}>I-BD2S?8Iz167tf7I0kEr5HX2*30o?d#$UjAPm5y^uCb>E;Ua8Sy;2g$A zvgm5D25{q}5r382U*}kp1=Oz(E*jKOZao+45%!K(YhYkE?*3gQJX7^nId&%2v2om5 za$SKj3V8&VOb~)t%FOD{u+WIlMSU(xFdEF-X>F`>kuK=J#xurfMw#klngzY1s(>ouSh>0{ zIB#wPmlgtvT+3koOJNqFD#A40`Lde` z;Xl9d^nYekgJbu-o*UGR2E;9p>Hv>t(MiW!r{%jPrBIY3dCM3hVaY!N7FD@}UTL^q zanGP3Vol5C3_~|9$E%VB)n1oG5CRN5ujO<-pVe(=%rv#mnUQ{g$%_yPjb~Q`z?3PL z*2KBx3fAR2-qvB#H9Im)k3IU!XAbOrk5as9%`NwR^0UIZ+H)LWW`(D_t?~an^`9qC z9G@~}YEO4pSy?HEF|3_&&W!9%pammGv0Xz@pw8%efjNdGMle*X=LkQ7zy&@_pA~B7;>QZF^;dA$1l4Ojru4yGf0dw;j ztW?gK5fV6((Us>$oH;oCB-h^umIiJD5QIcP5<(&n4*&tH3$OsnN^su;aN4S>E2vaf*q9v~mK z_>iSh!HqBl&Jmk*hK$Ttzy6J1{^DnU`Db4?Oucsc%uhV<1=$^P7yv-daLP+V4?Xgg zC;s#CuYdUqg+;|PX3p_>#4kL2VrDBRo{g(Q#K=R+ckMkV7hG6(EYO*UK7Rz)2b0iX&Qb z;kd?`gVI0eLOD`bT`53Y5#1Z!*yQv1YGzgl46w?T8<4W}YLZMst|De3 zK@`$P%mBnWNJ0qY~{z8=?IIdR2x;wRaXh(CbtH_^R1kNB_SYC*|II08Iqu4M06`BQW+zH zF+OvqIU0%7%$Qmjs6>bWR6}%0YG4n>aaATjy+jP_wwX1Oelo4pJuy#3a;Qzy4kO76XT zQE|oWj`rRi+uu!RGE0^&EiNi*X=%%A`CKmRIL`h9N6$31#|Qf7%&NmZuZ!XmZ)h_5 zab(smI6L=f>(4j$=BE{@B_WC6^HYda&UPzg_!;IfQ6Rti-MY56)~Qw1%j?RHwq<$} zhCNEd9Gxcg<$xoJZp5wESC{%3#j?Jb+vzx>kknwD#BI{n2*zUr-9GIGV34+ZrH0u7C) zv$-romf(wz9G! z90?Ob8ynB$v>fA{QkuLT3$>t=}d2Le=eI_ z`mwua&z)@;2Eo|v^%4biC+n0yz)!Z==HO_3&Ob6P^rqc=|L3saU32qo_kH|+0C?=j zKYZ-bA8FadZ(sQJO*h|u^KEN^^FSbY`^XCSq2ObWKHk?C3kH0gu_@IvX3m^SkQ0x^C;{LJVs+TG3NQzNpsUd0TG~#X z=q9i3n#`IVnYIDDMkW40prgIju`QWcPygqCY}@jV*Y6+b@6YA3^X4zmbnCU>y?FT0 zmmm7lS8u)JV*(L}7IT9d8?$h*;2>@Sw_zSgl)A#Io z|Ji4r35O$+BmzQ(MFo}9Dqec|75ApMd|scbsWI#BTGkSB_6O9Hv zZ@;;ztE274RkIpSH7A@qKK-S8|Ht1wH;SM3kA@4qX#tP8ch}Z8UVruTU;NVQn{HeG z+KbOT^@JeMN51kWYi_yi(I5WHFMs~n%-Qp6>t+l^xlug&aU68{(s>}TJMX#umfP1j zj0sebWib*7edjyhwz(;Z66ahL1qS@#hab{SL!<&B1R*R*;%7elKwj4bDiA_2#+2f_ zK6c0Oc~n)6L_-S}&$k_07G*@4$+SD~S$pg4Hw_)&artJ?o+C;^JR1f%`t@(rYdKAk zIMcVao!WBuC;qRJ(uz_4z<(TMQ^{d(n$PFUWwVB^d)#i9%iY=8sq4CK*pejIPOqO) zKdZUMDc z_DoE4nmP{iJb1=0+qL&==fW}3a}ic zeg&S711%Y~Yvr~hV;`7}5d13;FAGMICGE{gVYJy{u{ryT=i6D zVh1!c%L;A>F$91Bi5v$}`WXazn+SFt}*6OQfZrO42R8wy>T8I$ZxarL;+jf@J-{c7tpzEjf&N%l*JljUw zpFw64ArJ(&>YL*BRWUo8&vw|AU*byXa3_Jpsa#o#vKzp2AJ9=y@j|!=gi7Re1J?mm zZnhxfAm~THYQeOasdplI1|VVNW}96o^%%!NB-JCq2OJ@aQ0z(5^+!n?888;ArhEyVhJz|O`9oTgUBO<8ECknC);TTqO~d&7Y#^%-B7_vtgU>B= zId?i@-6v0+nmc#SjMC|t5Z%f#?#O}*BQkWnDtdsC($do5LPYcD&r@CM(7Q?kp^&eL z;IoVBM6O5EI%k$=ItChEc;bgyBVRbBYR-~5F3$%iBq0!C)R)1;4bmKL_aj;gc;s=7 z8%Gg#fixAYCNPd1js5wQ^5D_tFaXS&8h0xe03hF|A@wBW-Ujz=3_P(|%vD*II(=GF z-y=W1Y8qVF*?wgaKFU5?_Zu1su{ST6^VWk6U0c0DcA0D$IFMk<}y zzHQst0O~n&Q<>C}hJE>5wz;KMQPex`x(xsv$9aG6zMVUEx!vwt*WMfshqiCoVO!1( z%NBdQ-q&AyBa_MwCI>4j%Opv@q!5wM73pm6916i+xM<<5*|TiPu(7>(R%CuXiS@Tw zl{Ztb=jyge&=|-KAn<(-^?#R>(3O&+Po2F}AJ-}6MLA}q!;vh6^cEf9K-7nLaf z=zJ~nV#X3|%M@h?0I96QKoRGI=4ulM>@f4mC!Q%SD_gX9(T4SJcK7t>a{7k#8@sxD zSKPRwwY7E2<}F$_j{pf2-@f*iw)XbzTejH-0}i`(?MkOJv4Md@2Mx;65{hl6o`q`x%#a(L<+97o%7*AGI&3aa<7BkJ;JL-1CS7s z>j2AOHlAo(kkkD`HUt2+gU=7zNI~BFnY7F@UxfTFOdY}Hax8~)?k+5d9X@VlbpXT| zh>|*U`Cm|M*?Jrhik9EzkqLLQz?nN#6z01rPmc0u97}Lx!r$+f94jyudn)oA5NC4zFtCe6?>KWH`8X&`ffj%j z11D|bC`T9RcO7jGW%Fm7WSc}0ZZ3#^S=Zj_Z~wMhDP%I5ZQ6mtaOO<2mD2zKK~5wf zifXX#uLtADEIlqr0fb2=*iM&w5gJzXAx(B(p#053W!=ZabUF$6`t(67qtXM*tv)<{~UOR*uUdPiP(o7!%8LD<6R1gH)wub|#F^2Z`ZU6v;7<2midPAX* zC=UPl&bEMah6$cgS9|=#sm{)>yFYfPAPH9zg^NT&oU!3cAOs1m`@<>7aibA=7Xca@ zflBNKZe3{ET-yySi(I!3Wej8USYEGjm>U41er;Mw8qRe^LzYw>?wVRyB}*=Z2qJDZ zvO)>ZPrUgv-~b4f&YIr-`tN$TzvI1O0J#7F5F+l-d{gfg9g{@ncBWOkEytBUQ$x1Z z9t;x{^tSeM!}>@o)53tXd&}!?kIz*!6C*ShPFuQq^=eTN&IeFG`01fp%F8dk^5Tok zrMhlhy#hG8{99*^T|1+0`@6e}i;H|-KXACx*i>6LecAH!G1{N`{HLhL;m#l26>+l# zVK>m=?{k-a1UQl~)A;aWo8?jNHSj(NLh;bNn;Q)=`yLBC08kXx1j+zl_Ov+XXN{?G zpx7fB0j~pu006=Q0$;(x#oh1iDygZEU24VLIxUsyTOYw*7Wy0&WspnnNgV&*imef~ zvZXWC-PwQQM3X4dlA^-;*+R{XIa8wQK*m-jtV;iXabFr7*L9tF?tS-dy>Dm$4G=pC zf>=lp0C!L%#Z6>vDe`#6i|jZv<4WR6Qcl^GRHjn#WG0%LshO$rR8m%KE3!??cA{7k zMTwNSh&w4!;!be^2m(X{#NO!b_1oXQ^P|Bg2!bRc3C#J!e%(O7N4)dyJ?Hzra}HsY zCKOxdfx{@P)1X)2xv7}4e@rV5iV{B+v0aTTmd)zR-LilMg8TYzhr;2SqI#I0cA7|0 z)0SoFhE@8_Sr8qG}g&rd~5)Uq!B-T&hdEvc*I zC4o^!9lQV1)dkNy6K-t9gbbZ}AzZW!LLDXwP>NrAd5=FDdS=U(a5#MO)J4l<5$Aobk% z)0UF91@SINJM$7Upkv{OKH zb8#cWi82=&8yqvuQ2_AyeMQky%d}qp*(>es9VIn^2lINYpQT)JKJa388X4D-y!u|V zc{%}D?jDG$`YZwd_5NT^=c|00>_URXPb4ONez!|~} zTsB=%UH8f5^O3MGold>>n_VkcuXDLP2lwywxw$vre5+&i`ut&WaS_MyZ@vCcZ7Wt+ zEU38I-Oc86lrM3p0|3t5m0UG5eqYI#zWf|;EUQmA)SlYTrtepGU(Xyq^vFGzG6w)q z{0*-h@dfKS-mNPG$>9@5h-2cMR$3&~0W-iF0#fqWoF4&9P9}E!;#bjVk;mh{a`|fK zQ|mWuc&e+7_*#UAUr{*vxr4Iuj07e=6 ze8Vwj-oe?c?$WhH@QmJm$FNPS@@t^~g3%lT_IMfuaUivT)dS+fxhzPOQeD%RFK=yL z+ROm=_4ZN6866#~si_F}3iSX06zfSuK~z%H#+9DynM}67zu)C@ZQlH}-z#XW{iBci zGMVfB{r$2`w`}S3@&3Jg-tFt_<#};+TWw|SMt`|svk?Fw+`WhUN^e-v`R1C0*JT1g zo|<*@F59$6$4ASG1wjxvN#X#iHt!59sEhA=yR@!C@_8zjEs1}4jD$zTXfwjZ(8k2T zFd)m;8KVf$SmnYRx7RZ`5by7gZ{P8Sci#H#t)3f_Bt?sgZ}#+<7M+}!a2(pX{%OO= z+0NAAnzb9E#j&35E}rm&@EpexBFw7@Zk9cTGD?|48U6f2`tb{1Zxs>z%H(x}b6d6F zhCnNLHY5EEIFobv0w%P9*$p&1_j6`4nRtKy(49N=^;5&z$Hpg8>CC=;?~RU(Zrb=% ze}Dgp6Uq}h z{ifJ9ZmLcMJjT5@5g?2zql-d;NG7Y;whaJ!GIjZrYhzx!Wb2mUp17V-IG&U?FE&PN zzzHG5sksry(E%7IrB(ZuU=Q*9&-5zf!7zRL;QpT8R znvIPORW()NNF1yZs7`6H|#hbYy zB?8yOzV_;>iu30`X>D62yWCY(RY%@G(B83%a6(me-OAN#-+kw;lgAHJhQ9vwZ>(Cg zCV%Kq^yRO-P*GXi*wpmqYrppTe8FHycFp4$k0;kwpA^jsD zMUQVhiZGK}K|2L}DUg!6oOJ#Ab1K zgyXn@NqaOmFYPp+tZYpXC^Y~m;soU6prESp?0|_M09`ky{!n_%p|XsKY7 z(o{+b`iqooc04!aAbUY{(b#B`!x^EpTH3TOe&U0Q+6u|z1^~=)PX7%q6m$r|x*cm- zhEyJ9x{0P{2VOZ3-_PPY001!Zr9!$`XFew$IcdF$p-_@RM00(mM@Br(A5{Pw!2VNv(Zo4?%kPsJsrqUa`M_GD*P2LgbY zzj|^k9S(&lV;BPi3IG{EsUjL{^oJeAaf@Qgji(*edHLN=&kus69 zHOwZ{xj?i`N>^2my>gxVj)r4}?{~~ud?M%(jvd^S$E-~Y%?C>Kpxr+}eZGxR{K;R( zO4lHa$NTpgfe9us=J_2&%0X&`%&W}(yGW>*gL2J=^s7v$M^fv<6hb^FhFvk+qfuG0?n2s^)86Py! z3NRZ3IEHCQqv5nYdhd6q5IF2mi|Uq=$|6e*OCJ}Pe`9c5c=;cH`a4f|))yCF92hzI z;hF#RtuF@H2>4=D2pdpL8E=@jz;lJ_nZ?t+aN3z&GI=nueE4vYZi0r5;)gOuIi`}aUfANM^3SQ?n|h+NWg%rPY{b1q5r zO2lPRQ?=8Ynzg9~g@@R>$KNno0_r#-CbCHZWrgu~fvU{4N!7J{ON@Wj-~=5ffrpsC zDxr-f%t1PhEQ$rHMNS$_-LeeR%@@hR5)brRw(@b@m=?pB@W?&TJ7gLr z;fX)sFN&7xwvyE+9ri%>iv%D%0|fN$Bg+7+F@{9MC_FIb-Bc3Pwg5ozhA}sL&WJb! z0Hf;Q4D_VP9kWd*olQ_(@(AvL-zSMKP_nn;6H6<(bXp`B-RSDpG_@~&+cZsP3(2hZ zi&uWTu5*K@fq(bCcS?)>%iET>t?ayV{`9_g-uV5}n2OO1%=+T_Gv_|~V8<7p4@V+M z3P9>{D&fyyPc~D_^ovpibE2B=HH8S95g;SNykA2T{{@7_AUw{E=3odQnN0rXSFaUC zBOb5klgn2-*RS5beWx!NHY~l+nR$k8m{R+ms@V0P0gFH)`I@!+m^exqqoU`bK2$LJ zz&NjDB5mm`>~cj)OE{iK2qAVV%kt_-d1^`H>Gn2`*qU7>v!NtqH*{p8qIY$bn zGnsTM6^fA2!NKI@cq*BsW;%Xn+|acGX3cS&B#PNg`r`SIH*VQp8M8 z$)=`hPd(OBpyZR@qUu;Cuf?9bMWe--p=?G;W|UCSPY3~k^{op>Z(UP9aBTY$&0ca!JK63 z6UtZw-ri_Ne;o6$o&6_vpa=IkB zIyzSR{ZnP1(P+pt^#W#XJ3x4$sipPVfA=*?X<}m3aptk~?bEMDvr*{f4~smQ(og#S z(eEx2UB!kvP8ma-#Ac=U5zo9|MTr+dSO`MxBQ5|j>(P})mab6h27rqfuk`fvrc!C2 z*Mm8nFEjvPj^j}3XqMto$285=t5&ouYZ)FHH4G#FTEt0JC4=!h=~OnAOl!JEd{a3; zLfEoDN-|%@+(8(iufhVoQ;EK=L9!`hPhrAWWD!5w7?;62rP}%ozW0cYp*=v+oMT7Cc3W z^QN?M&C1Qt1|h_=$8n8<>^BSs0BhH-YF^rS_|UP_r_XHMv@!oFMG^xH0G4gomdzN= zw^nu>695EB!W_DA^~RpvM_QWOrW!j>TY0YZ-n-d`lHg_EV}qoE6{4jHZNOqyZZKC| z6jf2e;lj3^j+Ga`_&;8hL=hpjs;y>u^B?P`I65}AZQGWeJ9ikmK`9jkzG6XfE|>Ls zys=oBKM;ryjz06n|6rI-93< zZ|^Oh7guz&H7#wH zto7v4)23;yTHP+m_TIhkFRX70md-j03^8V+M<#0pSVa(cS&}KEhGB4+Lx40@(^Ng3 z$q4*?fFxzp|I)0lX9NR3y|Ht_YC}Da&*~aqWGV^58=tP?o-&6MFy+({=RXr#0b1CEuIdpsVu+YJEw-#hT?tG`Sn5}{D&Z(sb|P&nLm z3ijq3 zZ@#@}FJ-W4)B48dhV$pozyA7ex5pju2V4&)EDipkXZK!PbyI8fnd<<@j8wj?~mt$3CFfLESRRXZcSD5(#0cVx?wp@jg8ec)!|4) zQIyMF3zDkdl2+Pb1LR9D=Yh}AN(a_G`}_pg21rz zjcB^Se&-R)oB ziz9#}OX2RSZA@$|4G|4(VGc=~(@jDh3$hQ;+=T|Irhx3@mC6`a$KZh)|C=5-FXK2zhB4A>qFf)26Wx5sE9T#tCHO!0O*-f-W|kd zFE|QtPT`U<5vgtFeMkzF0DzfIP{(4H2ABgpOYU~1H=I($rs{-Sn%##SJ%e*UM}cpE zP-hz~J}?w3i%7EAxqUgtm?MSU5S7Ma-}~O59XWE8G3Gc9&-12XIF2I-JOF$={Ck3L zPU4x*-R2NtlOh)QQcP*aQ|OVR$iMvaZ-4dbs{y}D5G0NeX60Fn*&mT5 ziyKGQj7_gSip5HsnwMO>c&U6rc__f zb8e4k>C&cCr%tU~yCx6_96fs6v}}spjQ!j6keDOG&zL!cGP}V0JO~T(4C|~EI>uO4 zbkj;ooKCRpa=9%lqdFc*_9=Qo7JQaTrxz;mJh5%ZF!X%wktoQTmYYl@-+c3}hK5C* z>(^!M5i|KS#+4v>)qS3neG_OFJUjEk5>rVqyU@`L3BCxkOk2;+5>(x92lt-)((~V} zs;)LnQ`59vyLPoKFA9Y{06;`Hr~U*<%fPjE`uOF-cjn#5$c$6R0szrhH20i;x>5CG ztQ`mcSAX^oCpK(dRbEv_rm^<`U_1EO(K~)(d*yst4%jwmrZY6ql}hCt2V5>xB6j%} zZBbnt9hl!C*R$Ha)hK@__}Pg@goGFbz75GAg47D`t$FG+XQeUbbW5Ss5jfE@MXwz4 z`rXXP!bBHg%J{YYYG@4$bs$@!jAqT$%t9pQD!e z3po?OOn_-_ggw(?Zr&XP05h9ljs?K1Y@!gpJlY&ec|kDDscv1<318b#F;@&Ui`ade z(Zhh{LyjToH$Q!Q!=_CYHDw&RZ;QCn8?G+Rbaoaam_o`VPC9qftEt>d zVP8ZXkH*#rqJQ$vWkda`Z*koRcj_h;!X6^_ohO@WXiN>wu{6dgHFZZ<99_Y@i12ik z76L4Tyb_Enpq&7|#_z69j^(HUUUz_S9HL@!JWCM2>*v2D*ztuKHb%0^ZmX!1iJ^Q} z%%WK_-%iS_Y$Y71kKK-Pbo%TwE$i($S+wEo-u*%r6Lcw=kT>FbP84gTvrWC1I%< zTUVx?i&0a5;O{mEv-*_Oc>VJ2MP*epd7jf|gR8qD6v3OOfKmrO@K;kf4R)Qd|oZC@t<9+}+(FI3Z{t zkdya2=Q=+!xiZPjWY4Tw>wccK=ZA)x5(yzKAqEBp$$MpaO$-c7Ees4S416r~n#yGw zDEbZCP4>MuK0f~Pruqi@BZ0H>XEzKCvUlhgCFalz&)fNb5U^UQm)B>F|fa5 zxplhhHn8t66K;F)Akxvo%y`zod{mXikDQDSXbMH;&p@9$lt^LIxD}oF`ID+8by$jx-^Ipmb*_1%XR7f zVDDhIV+QVGI<}-l&`V+>2vLHadnco9-mgGQXWK=qEqaHBouxXnIy!q%x;i>#22IsX zf8GZH$>BH0i#2WS?f?EoAI;3qo;TPZ>Bb(fw#*B`9Y#Q*@n;yweu=b)h#*w0g{r;qK<0V2m@XSEkay*A| zK!maCcT-b&z30(fnND)DhlfXUa`Nxrzh7=PvcV}p8DIB%ffrwQm+Q+u*H{;{bL&?I z{-puif3@7s5_3!8HtZW;V9ecCRaIqUW0MFr)!Sr=6%i3BSi@q16h8$uSg&*6s^bz> zhcY24MrI|RzdQEJ=q!GEYGZ?GOx4z0=b`fcy^0D^z^%kWX1sa>o|a*~eN|}R-e@MK z8lzolJeiu;*-~9oV_FD^db3+KQ}|@5j)={I07Qb2DA@gau{Ua1Wzn0+rXuaFrmK73 z+*Mz#i5PtG)|$(p79X}u4kA5WYN{zI*^h{Q`k>$*RC^wi? z8ndN;>Ot2dpb!^q)^2DHFxi6C!1@$G z9*J4sPfZ<^rXQT!-@ON@l$V;%e&=zRub}k_b6QiY0XqJz;&ZQhKGEJ-_T{7!;K#Tb z4&g?w7thnMUY69$b&MFp{ zZozUzs+sYLi2_zbKdUVJUHc7HRQjV|T&($3%+FUWnCd-!J=PCCr-n&lyjdq?^(TZ@ zl$1ndEXGmZw!pwwI;N=38%S{n&~}F16xsFL4Lagtj&y*5;jHtJv$fnemjPFtsdDmV zcvfY?V+{a)0VmWgOz!Q5vE|y_jQG_F3*LRl6M!jggT%Lr?|s|&4etTMFOWl#gl`35 z7o!sx@`^Q1g15^vr+gx6pVB+deMZQ!MQt@?c3Je-{hI-}vkNKZ{a$U-i|P)YCLko)XR zD()dFW;4b1hbNm7yRI$A!=}}8EtnUAy+2^-cgo#sKRSyo@BO7_|GFGJ+||5HFllpK zeMfGqC}m7nX`BAlC+2AUIyk9C=Hf1I{5O#zvkg z0Ul;67$)Pv#|zWysO!^~)7ITR05J$z)#i6Ib6+P+nc+xU2SFzp0sLlHdWeMjzD^C4Qwqk#Ok=MVQcA^OJ{6&e%E z6BDqq_Vt}tVy3Z$s+reqpoi0k1NAj9>TKTVf<)}LWnFUBC!rs(o$a~^X}=)N+NHi( zkAunAeEo`q%^8B+m&p)L#;P&juUV8D_m-X7u5K;kR8`;eV@tKB#l1Q{(Cc6V@~U4Q z7K7gIK!Gh4&83=;;yZ)v43E2vmKAH3yM4i5qy%JHtUkbZQ;mf|$XmPi$*;&pc@{wo z3Jn3WJYBF zeI)Z>x$N;4 z^4AM+{AW{B6m;GsY-|-7a^~g|hU6z8SXc-G-Y>?1-02pmcH|JZenwv(X*9K^>Mx7v zoV>4+Q$Jx!`~kS^-Cfp`9L=LP{2qGN>IH=`C#x*`Hc%-4jgH3$a8IxLVLJw4aFI1? z&d`yEW05N(7;7)?EI=PGjjWs9%by?J6UQj78~&fV28 z&R$$Fd<@vHoR43h!h-8m^yR~&Ci0z3?qil6+md+MpSsMfrzxK1c0a_x(e{YZ|a!ap}|2J zEzx>Jly5FAYQMd$<*oIY|3*28Y#lrgI~T@S?88g&sR$yaT*%-e9R!v?2xQ4dNlq zc^HW!6_#|+@ywf@ry)U@z|hVeeq4H*q}}1v5YAHoGi(a=0O-kd8yUa@8JSNN{$NaB z`;Irz>x1*J%E+klon>D%>czX621oj}P7eBNTDVwF&f|<0B*Cq6lNPR?rEl3Bfn>%{ zrH`*5bq%M5lQ9&^H13^~V*nUN&_O!Jj0l>#>QF$_QlAOQukGSf8fS5*vZCgpg}<1a zITr!?iy8oTZx0IY?m7pEEROU>_?Qd}Z^dW8!i}61m6TI2p+|z(dH{6E>5_w&cfG-> zLhZD?d!Z!ipMLua?lLg(Y6TLD{KM}wbao0sG+;yevE9_ii4nawkbvDAr`shd{*zg8B=&1R}G@aTe zd=U~%3#X&)F!}uX#mok0s;QzQ~c_Wzb*)Q(Lfd-`(OxS9O51p3N(A4iJDr)ND!a^E9 z4=V++!xjs}bkmA8ZSBHzMXh`u<80E3yegaRC*t4iSMYz1G&S%4jG+_$_+Bj~YN93Z z;ri~~^(W~-uzlCJCgrJ60u5R2TwV^20Fs9hO*t(~Jw6cx7tj# z4j$*`WHYUaMu)SNi*0lXJrWmLbC$&O!9hjsH}5%}wGX$s03}9Nn%wf3Zb?NE%R;zb z(ZZAQ6`C|3Ujx~x0)eh-NKB`1`rr{dlYXC!yA?VKV4BxyOm3V{nPmycRccS%?^&B2 zbga*6I0g4)b~uH{W@R$Zx8iWtR}I8TpW_I)WX{5hz-Wv01V;q9k(k^mWMp{GYwP{3 z(9{08Ejpke`m4Qsj@Wr{P0~z^EB)>OrT{R}5;z-s5Wl#o6STp*IaPgunoPR6dFLsNDAdP5FVrJnCDjb$5AiAplKDNqJVzqo1D3t~pTSuqfbnZBN5u-z(~9F4@-4 zH`xE`5m46&h8kg%axH4xiU-^Y-ino;DSIAs1XN~E--;2`595e;{1Y2tS1b7==)aT7 zcv%5{IFe5_-eq0_d#sv`wC`33bo99`ETqzd3_uoJJ%z=Wd2)GCS#!m%HRn-$6hi$< z5F&@|44#k8F*?BYN~MYVmUK{!(|UWEVZ#_&jI6beNVn0R{c#_rf?4nr6x)qtI;uk( zn(#X?rG%SqN^VDcd#9(T1hBU`xw%OagYC=|y`*=j%M6o{AJ%s^*H^0oRE|!s?me}K zTwdPBnLZ4j0q%!T5|4E^rgyptPBD_Vh)vuJDCB0%54NnTKqu_bdJ&lX&=! z9uyu{$^?c0M*ov*LS%2I@Mx|`+`5p-mjqCP&&A%Ri+)!-5rUrH?;}9leiaD?N{v|_ z+x@Y#GpKK{O1=B`=^Cl`c!>jt=jN0w)c5Xc2VPhk7UW6H?BB9q=I{~ht-saPd3hKXK=ZE9 z$pXdm)Q4c5zi&9-V0D7V#daEMj|sA3zJ)%Bxi}j#XGNc{PflojGBK(5unUt)|HLrj zXaOGRTNiqC(C$EFeto_nPozH)gb~RYp@^>_Bid*dKT0qYlE3(M$!Jr~RhvCDl@A<5|P7w}MDEznB*SUpZ5 zO^<)=|3$i;6eNxe#{hA_&Hs^*+nUEYc0wd^o&R9Hw8gYj*pR08)_BU$fYIR9^LA+! zW349PCfWz0-*))-*UvdV+3uXp`%InHYEM}Mjh7g(?6k9YC;mHrSP(;GOZnkb>{~Jg zT<;mI`sNNb`cOG>9ECcq_)O3dCP!u>| zKIBaJV zZquiYJ#0MGr9BCN)JF6|31qb8+WCI^)YzIJ!hg7Cn=-Z0h{i$@RHNK#vqhOoB#2;$%W+xTl<0BD|$sST;mt-kMeui z8_)DJM>={*syy3icDfq<{4+pC${^ZHILuvX05E}4`cIGg45$8KLA|~7dWpE4t|A4; zarz6ux0P$Vyd0Yc7YN@!?R)O@CQ>6xMVzs%qIFBDVfA_rCSqq-+I`zH=}%R|HDq$T z4OqXLtuT5tHGLRz03tUF-@mW=ft>Iw_>DGNhK8?_=21w4XSAjs+!U1#Zq0Q%0{l>bfQof5Q~ymLgp;brY~A|4+xFG^qx&(<@CA1TpX&~OIC~2v1IT1= zMcHT(&LPEcK6!oD90_-D+Z$=Wh{g0GZp>z|^l|OJzS9R$0XMTv0%qseeJ@5GtPDSW zC88e5P0wN25^^xW*x?--yGByU9>)X~me(uppaJ%!2+yZ$pVi3&0~B(f#REI&oz6ae zpAZ}cPtje}GB8Ir_H@PpJ(tOxlItJu{lI>(PScX4^?;jfQ>oj$r#lrquN%=q-pHpH zeD$zb=dVJ)u3dXRX)1}lsPSh5e3~{9x`fU*w%s>PXzzr zf8?15``tPVb~RMoQ&l*-oxk!)kqRgWZ6Z)Qs{_brbN`filYrd}r_0SD5hL&_T5)Dc zER`lB(lUl3T`!b^7&_rGX@(te2UpT}KY4I^VK6Ft--3sjHpCts@L^Ehu?c}kMnbDy zUpMpQQ_?Fn{Fy3jV|EQWAK{pzfTMl0&qRAwa={}Sj<$mw7uO3$?Zfrxjfqhd@ zj@+xR=(;iCXP?8*HC`?Hm7)z(c;GQ5am&MwFk6Dw>lUrQ(TX1&_D8Q`YLCKxEusn+ zOOgYMyzTdLOESx$Z*b}?5?NZOVV^cSzK~ZZ21!h)!RfZ-i-gWSqq-V+w&)j@U3F)2L z;v&&`%5MaD?JYLh3NJmigF}1pr~fQ~S8QyHd2AECyLF=({BqYohVyYWky*X2fU1N2 z_s}G~6OOd`!vW_glNs+m9RzD>7CGZ)ekC5R`8P7&a(HCA-55ZZflJiA4J$|uK&W`F zx!g~&ML95Z?#0j;GEOA9#%@p{pg^GOJ=L*a!Dx(XdLq~T>z-j=$VbUkHaND2{qT{f z{p-x|;At5uc@WUOz)7=_Oan6;vEv>QnaEggz<+85kInfM2V09&tgNrwnH zv=R}#;^UGx#{d)zZr1`T=7bzuY8TB&vB=A@&Nh)CMsSwA6XV!2m`=azS?uhh7im z<24i9x5}Z0$-0D>(VMJ?{=r=f>zO=8GiMX^p|-u9bhR4?@|L}CFO z(wLi_r4Y4WEi|jsmt@|Q)#|^fi8u?md33*I&#p*{wNg~ciwdVkEDzORH{F$@O-CHr z$9#pweJAs6#VY^4N+40g&5RT10VWr+!fbjPzxJIF|9|7~)(grJbl<@{jJXyOMw=|v zxeXR#Y68M542Tr^W%#=w+M$|H6u%A}A3JZrH!a2HFVUvwxm>$xH~|3yG;AEXvWBYX zCn#9tSZWM1O$;$GJi=3WyaDHiB>h)Vu#7hWsM|`kR}>2Ur3!JIj;Uz7CKPYw7B#fk z(jNA{yjP``wN0iUg1$gcLJ;6aED{GObd0TzZz6-f@gaX)4aV~ahQ1R2K|E5 zEZSvfm{uo!-GsV7LpAM^;Q2;^L6>n$OSIBLUp@Pbowptv4v6U7AIsOTe5j(wqj)8F zy~Z--z<9oW&XPm{6dIGbP2)hx`+g+`(7<2;Vy-N}?l#jXSB?0j-)mQ?X_QP~ z&;frh=T9WuHu>1+jn+j9urMX?6>K{WNVU)IeO*YfjrCt-=8qtfB&F+{O%Ka^- zYq2IU&+s;x-}V*ZgAksqRP~>4+Z+7=CG(s1Ey#cAI2ZJ8Ztq1moU0?pZ+mEfiq{_^(j zx!Wd!(B+4U{BE;Tz+1ac z40O(Wkl5Jir!Cv#7bg=q+IB<=vs@{83l`CmZV#=8}#@y zqUAMvv0jDAiTBGE)k-TDlW4_k9f?qL3#fhw~KxJhVG!Yinn-8A_J3 zTI0prF9z-C3`q?*wOE82-Y+jD7PG2n3EF)4Fw~dNY*~ZqSDN2!mzP_cOC?;4wpK63(mPgjZ+hX;pZht928Se?RIGozJgOpqJoy)Vp^qicrqhCZ*v+pKwzmDHY zuKh@2_c~pw`&}!@3v{~95_|tN$s2QedYTGqU2*e#PCNAkxG>Sy7Eh&|oibFGUx^kj zw+bZ4>TEI9mLPpcjL>QEw@|C8c^k0wai({-7&By8Rr@ri`OVL-pR+Pm>>`*qUf2Lmr)S5lxJU9Kzdp-D{#_t6GTh!m zwRKV~Lgks@Zf(uN*9h(<`F1sE>qI<2c>X;l>u7F@LjDqKMo}yQoLei3z93K-+Qt*n#+qX&jQwipv-LrJ&DMOO_p<0yT4R;I_{7l&+zva1xAMjX_>tueNypkjFv;zC9Fo8%y^(h60Q}(D7M-v`VU{_9a2xmx^YB^6JRNRTzu|aymIhSO!~#AAt^G z;9nT|o{O-LOs2*X$5|q^e>f`W5-Q@?D6Dqw2k-i_Mpb?CiHrjH`#dl) z@`Y{r+M_Yr4SD(v13Py)y_@Vv0_Ke~>X**@_7__l3$3a?HMPQN#fvA4VP2 zf*VQ@S7hDi?I~S)j-1`d=t!X*y3zaxpNLwi*ZG07qLEcr4K%I@qG2m?$4t$ib_?L4 zp_5UH!DrlZ4jMc@w#OLU_R#yOMIOG0naS>uvA(9g&z&$Ht`$SE9C6KBbJ|gvZIr*^C4Dj#CUykC3%fB zIC%10Q^@XfV|9LO=%d%xx6AV3V5=I{Kua+RLfEmA9p=&UX zWxarhCRHFsE#xZi?|V~ux2XE-Wu$vhxeV4JmOV#hF`kN9uA1|)vmJ4;sSzc4Z-{7- zFkWc-bT6hbO&>B6PlaXT8O@gtVhb6enD5_vNxO@0dG&XrD2=;`r;cCBYkgGyfQ6_< zL00UIA^}^r&YXH5%UKethy^|n~>$8lsHiBNf_^d;djTD8q zm{M;zGwZ*J$4Ft_U~MCG#k{p7zvlb;;;v}DB}OK4G<;I}+rvLL)NhylhkYe}ufQY} zVLFzpp79;RYB<%h&HR2JHv`{Wc}A6)v{ql$ftwnt^%xaFL^9tI-^HN@-E^zvT;3wCS#V zsD@(^FN({q1L=3Kn10*H+VO->17{!ig>m?a${|y0;QbHTrMOR(BaN(rSa0Y&o=(lG zy)2}=oD=zRcylnVq$sED&D*L5vbiX&WCRy0zeF${V!h|@QFPH5D0GpdHY2MFEqF>< zR8;isd&2;f)t1Jj)A|E3rM{x`Blbxj0@o~DUfwcU7v+sB@OU5EA4}x*&G})xNm>hf6&P&lvg*!+z0+`|43bfn6-)y`S*CgHw?RUioz`waFB^j3iliM zu+vT4f&HFOW`2&=ugtb_2JeKQ{5|l|H{**t=nzlKA#a2*`2H$2hNZ;P>66@=x2i+g7b=-st4N#_)3BSaqn-KF z?7|k69u@0{`@HOzYLY6qx?U`dnO|qazga%^`+vG;@bSKgaJg z-#OeGf!ZF;_x#bF>9G{q?{WX;lcaK+^01m}IL8OaVA^Qbj&60Hi^yGMR0m9JO+Ds47+QpGhJR5+ARR9#c!l6cTB zp!acVFZ-k69;d+NYa6kx7$rwdRWAJa?{ccj9}J4!{1zFlUSl!o3M}LK?qVXy@dz~G z)D3ET@`L!YtXcm2w=O%T8lU7Qao-$hFq?8o>ro*s|AwWCb!1HN$saS{2i zx`dKFOO}H=JIPOExVLy_XH)3D>J;#c1-&wkPu~5_72HlRn`-n#0)KZWe6^d548FX) z>~*rjfhOu`{1nq*XLtGLf^}s4VJZBoy{orrk;e1*xul=2;&)x5yvD|wtRFTXu<*>u z-IZDi${G1IbTk*xZIC2bXN^FC?%ovs=UtXq!#80Isc1IFY|>z_Wlb}!7VL+AlnQ?L zWBT+B?`um>#j~}xPkEIAF9uU%a<%M%luy>_;LoSfnPHXhS0VNUMMxh^6tg}qc)&DW zNh$S*;>2y9WAI1QASsxKFLxFeQEqB3`!(S1%@b422{y8E@@Ekq_q$(KY zeSMqza4?c}Ew>BHiPV;ku-*q8bSV}_V8?N2g~ZVVPeVnR1c{bnD)N|(N3BZ!a;J{G zUdu@5wRhqsP^sW6_UKIj{~Amy#Bu4Z(x#ww!t>CK(p1Tuoj&+YTFQKFpBkXXS;jJL zlaZ0(49LUIiVKdn6_!@14dIIz99+Vwv$3%WMqPLZQEal;Hs>`mF%WKuJu8pHOf{2g z9{hE^Q;SBHb}TJt9eqp-wwz$ypkcxDD*y4N$_sQE*2?*%LtTxMe$`U6Nt_v zg=?`ccza~6o$Te+!lU(n({ev{ZL*lN<#Q}xqMR|CUfpXtd^RHB!1RPK$O!pIP_!RT zjnhePj%A2Z>Jl#obilT@2Qcum);Aks=^c{?duv<&1_jK7eo_3ah$jaa6#3tQr3VMp zQ$C4cJZ*|x?K^N6P^Tj77%SSc7!h^W(Hhl8nsVUQ$p;%KZuSf&CH7ZW)agWW43{u< zWXAu*(dr=Cqn`YhxI$I(jW<;G!{=L_GIvgWr;)RqHou^JRqb)E393IOKgK+AUW%au zY%sc7{xPHPA9~1q%#cq#&*AgWfAgN_-Va++1~*@!=HSRKB2RP)wv|xysaP^lXe;j_6SGBqJY@ zRM_XnvQVd=v$#HEWo_u$eQvC;_gl}!Vv097k3{S%{}E9U*Ex3 zTc4yNTIB_&#~vS}`5($%AJ4Lp5)0qVaz$w)gpyYmwMW6fYq;jsihOq!HL zzb^VhXo-iEwg*d$%f3QTlR%)JJj)O^-ag~Gh4py)HeWg!i0LH>$+sM+-@dcf>uhtk zq*aMvN4dv@xf?eV8sf+8K43J~h86xh&%^7|gW5g`(-#;?&!@GcHC#cB;B(j_#aqW{ z`e09UOwfU%<}%qmN0VHl(YKE;VZ)(u8eO?F8uBm73=KDSIP=L7Lugeoy1B3yd#DUU zIx2!rG9inZz<+pA4P0u+Z*_J*V8UtM}z1OW+%y53aK`IZk}p%k8mz5GaGGTwAG&bjojh78^q zyj)AEE@jf9^6Ln&>R;Qln;yu>kXHCO20D1z()Xq-=0WyJ>Bx8aft$MeV8S2tj$CgN ze;f&vDI7Ny(wW<+XGw6_21kW}Ul@1)h{wczjS;BV%;K5?P%@r-=AoZME*bGB_2BtR z4I*@E4{JQY+RyHNG&8Dsm?r=QZNLN-iajX6;?7O8@3KV^WItWXbc^+;)7s4BN;9%V z(4zhHA+0b}y)X9e%gpO!Fy)W2m?{!0pn%9)~z#6!43B@FC$;ej+hKbHH3i3!{R+{dwKT#RYux*)fUZIaVkBqTpis&ZvM2vAaP zULgD#`hExNZvQFyC*SMs$tFmQk!GcUbG2VJNp-6u}l z{GZ)_Fg3@A;Ybav`(*>df*@peObqj>;*|+a>ou=B%W)v80qk$M_4EM)SP6#R{oTYW z2^qKFaY`cf<6>3rGY{#LV-d@LCn)*kBKSP@aJ2?HlAf3toSpi?Bp(aI*1M%~Nch6Y zJcswgM=8p&2m?eMNjtaQ%y|@zf4atNVA~ip#6=zpDUdskm>$d|tc^B!9rPecUzZ@= zF6*m2ur9eP#MY8%s(>2^?tt7iDS=!aoIER0-!O5gH)^1>fWVI7FQQqV*S{wj5s6RM z*CrK#*E+Yb3yPJ;tg?d$;)7x7PYw=c&?euM#5z6|>twMnHGBV_|AB^d5eiO9MsrCY zE_Wt=5v?@SVK|f?ZB|hEPK^vFZD@R>XrgDxkreE#I7a6A-Ml>ZY22$<@w`|wn8@T* zf3;|!3C5d^1HuTM_qnkhI9!ZO%YeXY5ZTNLjtb@-%)Ya?7P%u6xp zjbd!>?w91Tt9IGsYs-JIr*}r#dD}kx;r=+osII^>vul*^dfB|OCr8(qA+YU*G3*5< z@XXMbFZd7}j|>f|KdlO@KDl!Y+9%j>8x9Xu+W(~$;Qs3}S;l7mu-S6&0CyZhVE z>|WpakC~XHq~vYs>}I;DbL(I7g(e)spGAQH>(EGIz1W%$8<`?tsdr=VMJp9~m;UMq z))|P*x|sp@y-h?|0P&n1>i&)ss-_S56Cml>1UwjO?k_doJmmJZ6!0FYQ1m4h(&6*? zBSe6Z&Bj)MWN(O*r_)o0)J4O}f~#YBQP=uy0?`<6lu5o)t!)-wcc9=xH79WH;|trC zJ74`bC~?6@^enqTQ>%CRf!-x@;h9}qJ1T3n&jc_Rj^?P$M#ymUo>v>xt6CZyURFj! z!zd8q@~upO+7RcD=|~Z1Sm$ug3mj;w=(>|rJ1JEM#VwF_^C5Trdq!iRPkXfN`(!%r z_Gs35aN>P6KESPyM=Y5MlKatJEFtO$I|bbE<(?v`t)upvvT-P~n|;QuTbhiUB>z3K z&tE^;{Dx_MkJNMZgJG4o9Ba};A4E5+$Bq53sHJ#qz;1YCJ@9eb^G#e!SSR}z6ASOe zDwbMPw=22VBf;H3r$_%m%VJaCSZgO94vM$C+ydiz8?*ia=AOduly%{+^^=Qvj|<9P zF+7O%Y8vZtf43_S-HjiH8NVM08dm0e&R0M>5vT8+rpBnicooLQdn8tQh8$}lL0~Y% zbBgl{19DAWy*q?I$YA+RfXv1bB^cizL)W|k9#--h@;Zt~_4`>Yf3;`LGz!qW6?5`lOFROb|**U6SG zDhf3T;s0va?0P%zw6MV)_1%Q?&=L*ocsp@)K$F`ts{@cI;0bez6~G8X%>?dUe8AwpLW2{D%XI`xQ$kb6ZVXNFA@MQ|uew zg{_AHMAiDhfs?nNUFrU#np)pTK7MP0Ta7a|Wnf@ppj`NRY55yHR)7d0s`veMY?GrN z{O1=pwVw?(h>5htJAhRSb1>8+&EahWh*re%TSN7I%%=P@Rak z!XR~Kgs}NzNTl2DaG`uT=qOa4tXWylXo|U zchL+$^t}*&noC!>;-}I%+YisY!b;N{pbkP5VEN{sLwAL|w1=lSFP}UH$MBheEqwF& zKJu{)TW&Q^Pg>Qj zz7tLswj@Qw3=2HHa_F<;eGKR*v+Wsc>bR(iaeBD4mU&$1#9nMVcB4*he@6o=h9LdP z1Rm{ZYt(b!8R(g+qo_C=zMh##EKfx7&?WGCu`MASW43V&eFO$rZ-8s z(cP<|lh0nB~&-#?N_TW1JaC<*dA{sB~`J41l&F`$de7HhC5zcNi(eOq#C%v z_369>*w}D|SfLSQUG2~+N*(W~F?-bX8_<>ZMUR+8hEpc=RPvQ7Ikaqf@J-%WPdqV< zg!xM#3F6jBHWL2wJV-=J2M6mvM$YFN3A>Z8wiSOlL-6#GA`zQ{3yqZ)u~&PLn?# z@0yDRGsthl)}t3+XtL-Z(wD7QVwO{U_$^n?RT{4%<1G7f&`at&fEn!(bbm*VRrUu9 zjxVEhMso-&dM1gwrXf zIJd@SHdZ#*VSOQrCOtb}97ZY4-*)Z@$&u1aaqaHQNIgik z4y1a+(U$Cf!(>5`3%ZHJ6y;Wf-J8#JSlX5QmxE_Q@1F9R@W;*~<~r>%Oqk}x2TB{+ zKMigg(&-ab|s_Pz-9Q3&2}>Xa+I){r>G!184HSN=k+}3B1e+KtDLdexGskdI<3q5JCo@#Pz=4 zFeR<#?E@Ble;B&?_dBY)nO`(Y*fL z*MH^3-vgYtSUQq0TYD(Z|1i$<;`=uO_-ekC9c|8KX6H{8r|1uDW_J)|UN8mMD?hzjO^pnxbV7fmRtkg&OBp$(aib8cL&%ATKq+ zTW$i9k6D6)*pdYAGRT|%-8>&^e)r7>@(%Z!^VJ>hcUeeMPN=`-j{3(2)mnfZ@1`XG zJ^n3JR9w%*<;VG#Xy7d#-D+B&+wtu=GMSTKRGOep>IKHpy)9s=-?w6+F_d zU;bf3-30~WMkaImZFcCQ{dWb^xlU9>FZgSUPWelo#BLU)5>3YD_d{3d(=W2c5$5Sl zD;M#+t@!^eszOL_--iN5LcpJ>(gMyAR^Q{Mo>?FZfXBtEle6Y(0v6w zT5<9a-dVra?Qg}AC1xU42Z7=?nQDaV zSRBsvubaf$@rpPaep%ZytC6ulPmqA;3|jP~yV8G%lAPzER7KvE7=7=nCy9^$D!gKW ziro_gQWqTl>f)6OZU|BjFL++MA@ZiLfhY;izu1iTZ1%~l9yh2k}!b#i;- z?N-$9*4#za+uNK+tsHt<`fXl@$HlkB_g6>k!!~K_czeU5o!00+M5MF}<}fTrtdzC) zt)w=jM>T#xB4s@Uddx@1-fHE^N-!^X!WcGExz2qory@8(&m%$MW)d&Q4vc;t=_?FZ zm@)-Du0A$k7|@MGjizFPg)R3gtOEIL#ene*F$xL`NObzgNz6t2&$MryZ1ahNUzj=N zn7u_K>@nm5mh+)EsH%nj6bo3**mErA_GU;$Lf`EDV-zhshR7q6z2n`FZxgqh#?jC0 z$TH*Q1JbA=7Nmf8F7WPeFkbQ<&TS{TN45cWt-CB7{GMASrp;6}3~_WP2W!u2R?jG) zRc2wM@n==RQ>CC6D{6ODx|EjPPmI>lvkVL>nPz>wom4kJm>=uYLDJ3DsA3YM18BmR zEtX45k?2lB1W)1DgPd#Qxf}aHOd?O2&jE4K0P(X$cTm?%2~OP@w9Zu_k{T$uiBVQnc4~@`{LKr z&mKap%TER&ZZcNRLih@WF(`IRr9m^PR$AlAB7SkBHq3TLHxG14JF#={7}h@MVQ4k| z!}7)y=hM{8Uf%7Z9 zV@r{{L+)R{es$vJyx=2d8mcO)_XHc#;iuw$3R|8&_O06fEttKlxj2@MglRrhGC~}b zx;+yXWZL=R(4N0u%$jSaDz`DxV`L!lA`>H*=WRqP*E#$(qulU=#y9f)3NL*Lzp4RD zvobT$F^GO}^t&GsCF@rv!T~)3d`AbVXU5;LCHMOy;fYP@Zm-zc(VBpvB8PLrQByQ} zjdJF|*+Q+W{WS1@{lmEg-rncg#P{OV$uK$-ctz8xuRLx4f`r8xw;$PZRF;INMx>-g zLVa>7wu<~*l2_s)v!yG#A(RE3GG8|5>02oTJ>2~tj;?|simnUO-3`($-Q6KwiXgdk zcgNBp-Q6h&N_UrZx1i+GARSA7^M3mSc4lYi-gC}V7jwRqnHt0h9YgzFGkm>mxZ@aM zNfKJ<^#!4SY$w~a;*nV5Dn7Z_CHW2L<4@%cak$(Wkd0*BJrr!n zf>!&E;y|FTmJxBqPx&L3e4Y>#xMCq8B-Se8Iqc0v{&gVb0(~k~OZ@$4Ka!#jF;S>0 zle_0BNm0Uo3%M-QHY`m<`nA7iOn~G?)WOi@bkWU2H6)QF(J3wmvt zOzweJGZdG7lzq)}t-}_tZdTh}$^oB5xr~dd6?&JariQEM_s|&eTr~Jv5Z~Jx4%%YZ zd*orHpF98UzK`N|`XpC(u>o5XOqm=Z0!;@thk@cG3Z@^O-#RchPP}p`MmP0!7&*i( zU{@O@#l67J1V0m-Gsh`1u{j$&Jfgaeg;!hJKi zj^yoFFmjSY4Ck3iP1VivSz4lQWKEG;tvbX=2kuqcK@);JwUO!~^3LY`r}HVieBgBS z)TTBhm9#e2tx0|>kFK}995K&)_31cwQems&^4YRzy(^q@h<-dnV0Mz0SqV~g>EnJy zCl@7AI;sl+MZOFWLh)yU0NKH{otV#UoohBN(km>ggRj0m*#%0yER1uMNcbCoHDElf z9OZ$&v=(UZ@--r2?Jzd(1#4nx$7vG{ETTVcwKou1730$PI_{Jy0TQw8x5Latg#-@+ zETOKFV=P4d<&Mt0=c(l4$3jntcMbgIsV2@ljv3JMU7xq7*|X_sRsiZ1>N;{Bpm4*Z zT=?jREoj&5w>CaG;UFCKNRD$aeeA1T_p_x%N46)o#N*S)kA_q8pR&bLW*h1&QJiYi z8w4?qD8v_IY!Mv*qCLa+REuad2eSIX(JEP%DF{H-$)U;ouT+~0l7_<|yw^5HtA)>| zhDp?fLiO{Wx3}ck-0qBb_y}-JTKHR}Mq72VMVdnpfS88O52aXk=xLPpG!My<{ipKD zbh12>_?6dBfr>(m@)M^K>IdUv-_e|G?mbAS{$iOm^`@_c5?{Pz6lUyYX5A*lJ1Ftw z-_V1Gs>Tr4INUpv&ju>GGl6F-wVMC~B8(Df@r@XniJqa^+apiJ_m4zUGC+ocwrRWl zZc{!=o|a;H5V`y*xc(~CEYTIsolH5eQm`Xg!G#BJ>y!`nQ-=h0uO}tmxAp#HUp|%c ziI@C;X$J=G_@K}B+`+`TKlDZ$t^1i=(wFirHzafjKER;EsA$?K@>CANt_jIf& zhvx(At`?@yJP)sSgUx^ARq4rEz%Wv z$u|SIysC;y`Y!>SRmHTulMhUiDeRMom zsYf3LvVEB}$kQuHhQR=t3(4!H28CV&8}0rlVcNz$%jE-rk84fOtR0;w-7UdQ`Vpa5 zjgCG?mQu+XDB%#w3)RLli@DaVA$XeUxkD3)e}k1g^fSno4KT|1CXf6}=<0L5l!v{% zuacKni|WUxCZ%m4L6b^KAIhdFIX^B{U1%RG1 zh@(*yMDOxe7MGPPRWT>Lt$yW++vg$JZO|vH8xZx_FQa|K(3j3E&lEgb3T*3<81Vjx z2pK|@#iDf5a!3A4bTO|zHn!WKN_da$U`7tLdyji6Hc2Ily*5>zWYZlpuIvNrM?{Pf z5k<7$`G7)+hYt`zxN0s};m_)TEIj3hj6#|=(c;hWBGC%>x%DCZ)lyogK__G6N)f$jOEE%!u&MANE<>uWL0Y4Gv;_wSRu z{MfmMrzgx-VG4%JK*y}tEhGjQxij7jdP%*^xRYd=dpSHC0)p-d9}#%!GwmA|ZfUV9 z7q>XRx%9bpU}dI|)&jkSL?P#Jvc~oDG2w{zoO;4+@FAPy0LYd5r$t_1ZHuNNY~{ZX zOueGXoO_n##PeQ2?hPTaE|Frcxb^hFDO3h+a&WO>y8bXq9dX+vc+K6o!hM%>Kc6W_ z=+|UV zB>R?%fG4qsGWB%<{q-oHO9@Ie^6RwJu-w_U{V@5|m}hBo<6)heiYL*OSUJdM#SPKt z9V9m%CKB%Q^i?SVv!|5{B?qHC`W3OT*-t7YOoq7#n;JwLnsUdQ5LrgZs(7$>aQ3@X zH{seSdziD_hdv(pb2%o5$OOePQ8BkA%&$THQqSh}lYj_j%>w<^*MU=qu6O#MBjuk7 zLXruJ3`Xb~!i~Mk4`>Q0;R}%m$%1b~uX$V}=zv&&G@Fb%m|jdwv`;vd*VEJ)d5qGW ziqhdk@@_WI@HA8=*uFdanZ|AJ!vDCu=uXvnhk_ZZI;x4Kg^xYDCv@lcH$Zpou@T}S zULCc#kj~s3n0D=!(&Yd2Js1Ga{K{ zD4PS9Uctb5Oy}#Axm@~WI(ekS)Px7mCOI9&?BWQRK^AF z!2O9`6)~%vxs&I6eNf71E0Mj+V5F&vU#R@!oqRR!*KFQ$TD-qldPYeVzx1&L!Ub7n zGw$p8Uhf>Jd@2(BF-^47t$jcIU$7t+fuJBa7^4L0HI=Qc89wKGeeBZ5ns?ock9uSH z6bunB@nwL;5*g0+@W>`$i_N+8G$!ScQe0yhkoDVf9*?XDGnoFgRT7kwHChV0+eN7I zOV`_87cLpDc|$8|g5gdTQYg$0!GD#NMfaQ;o&DyH{zj~_vAEdu^M`g0HqQs*v`Kdn z(jNRxO9h?i&+_W+1Wjn$4FK0EjW|2xqXz(C?N~KAIxtKSJ<^O}Ao|Jl;R0;Ji5sKo z5cuV9FZ>72{NYpjW8mo$4aZTiQjetUy3CD|ljkS(ttUfdpd2hx?x!;aj^~F-t09K4 z{8QeR=~#KDJ*(Z4b5(2mgxpi5DBuC{ ztf}$212EB?B55FFM{$IE!|=mQg0*}?Uf@eOw*Z-Vk0Z#Ae|||Bz|O6Rl!+mhqc=RK zEthm;Mk2IvFx4DQpE{^NAVnfA>7H~C?r5%Nz;of*Y}uWFB{ujHs?$_&r7$ffLC4?+ znoaVt!K>1$2m5HSBCJ3VFx}=lY%1o$IY!A*)d=!%@|Q930n$M(Sjj2oX|gKV$UWWb zu$FRDOR_w+Ti93g@rNhOqwg3texY>%|MU}uv(HRi4su!+)%k)I)Kc#z#aq|6KC%>sLYuRh_@K@kXWl{xOC; znViJ*sqz3(PcJW#s?z|}JdMh~C9j^xrv&n&QM5*UM!m=4qTS>P<_rv^hA?40VG4j6 zgfx|a7iykjAgJLid!Y-(JW4$Q-e*mD{&kwn4Q6VP6((9RR9qX;H~feEne?gk(Of_M z$}G?4Wc+83{_rw-f;W=S$?>tb)>A1liRNoYj;9e&xr1Qb_^D~sOqnWpn3^oY- zXaxBwFofM|xlvWQbP^?^GJOvm;h4nJhi0GCGEG!`Ve|TKLvH>|3cH|*Fl;t>;J5Ll z5i*1(>V7gJYU&DlU0q>185P7@jQ!rb{a9;8c$w|EDzUkT7L~TS821J(?Ma?KY4AY! zfme62M3(-Xv6c`oti*WRm7#E2+J7r`Ay zb;;vQ6JPGIu(0~ZDwm3~5l8$&>UP1Ics9&kG_n}Q7Yy_LG6;an3ekW7ZH^9_Qyk^N zJzS|4p*aj{Nc_ujOXyYn)#AkdsNE=h=0S6Qv*^)-5|suKlgMN~w59@2tJTT<&b&VA zdHAfn;g}c2!}w{=?JfeDP6}JOiQ2R^dW&Lvv?Z@CZO?%QCV~jRdZjJMwfc{SJ58hqu4T-bIhq*GBJ^T^HfVOl^?}Nt6Kg(?HJi>zWzEAcQL5%?`bzkHp0*odHZf^K=tBh@^T?8*Lm9W|6Fj(;P@3w>3 zBU4x3l1QF@I-m6@Xd#P&CahPY^zA!csW~pR^2_@`EPgl3o!OxV+Mm_GXlU6Rs#aQ7 z3T_;pVI|4rw&gIi%B-2?6dCC~HPQ`Kx-sMhvEfa9>L5FX>+~Bc1C=@D2N$lP>Jy2F zea8N&*w}B1Cr{il;8}z9be%mGQM?w$li`w-za6N_9OnlXDYY-kl)<21d3EZ+o3bdp zAFBQg!(FkCt5`6me96(Y+RYdD7r-0Eb{>bX1987j_*WGo-vM6-u43#yK8eDm`}3G| z0q%)1J-RDjo8)r_5KmYa=3iayq1;=lz2VQ^qSjs<7XR^`!iyhPHEuska_ z9mG6BTvXX4XZ8-!D3+#^sfH!MC8f5|wh4k$a{?$1yH=%Ba&0`slR@#9)e1KHL;PQl znT_tOA}8$h_NvKf^eOaXuYu5h-F{)`8vGn@w}znhe_XVZhO)wafoC_)|ClLDOsGmn zGplM3wOS`Ah1hE5&msbtBwbn$vNjuv!(5(TOyC!eq)jCW_@0G{oMegkum2d>xGRb2 z8GS(sbGag9LO+V|Ho;CwY`zZ*@TLW!<>oG!H=(*bHk|Dd?x8mx_@k^IW(Nt^|G9;I z1NgIWxK{v2*+C9;M6z+c>?cu-JeNL|UFHw%@K=X5tgL|Hn4a>?N~iyxJx97axTs5r zHPR|L_^Nmk1<3yZ4-mLaE+CRl6j*Hl;(FK2M-8_HfVIGyng)esynQ0*_p4<}Xo;}& zdsFcKyHEPevv_`=rKywcO0M9k*)RE#bZ5A)XT6FNPs^+4u#y$x&oS^mYh-Z(rU3Ph zO3mlyAAr;809@r#qzdShL8sN(8qfvpZeL4SSz1Kd1>={XkbkH{)A6)2Q`gW)B)^rH z?Znrzaj>iz5U&I-$wCr z!{0sI+y=^l=`df?Zr+LG#dj4L9V7UFvT|di2P7L9VDvLew58WH%#sFEH6!kR;7$uR zDHJdgZe3hQ3OIeSKcbGBoXY#;9=LR)NP_k(l4mEP_GQ^Wb3me?e}PqRnA|ZMA*0B03sNVaq~1q6n|)b-X^zzh78FOfwTgelZC`a7z5# zIwUrL+E_@|<}&(1nN*tuh&@fONJZ)ZWWe;S9{PD{=}ZSdH-SR3hA@MZ!?*=3+!1^| zhqyLL&Ys(0sAd(;*`y#OFUQ`uFIKV}&K=p1LcDcwqC)iLhxsR?&j~})PLePfHsb53 z<`N}?wFvToMR}XwVs4K4oijOn{miX;pOru$(U)9WYEru1ibv?3(O^PTAPLHa$k>~U z-VU*_Fu;5o7?b{S92J}%L)0=+TJ3S#=Dlle{;irze2*bdJy->Z*e`L*sFAG-?{R5r z(AO@dEo7ugtaFQcWnhDD-^o7s0hNzk32nHyqT*3O=3_;9B+04*qk4`-yWH3KGy&o^ zo~tKpfyEyI$d&L9BAcb7A>Udx$jNQ7-6VzQ1n_8s*Lxd1<_p$%fcVU#K!^b?tY0nh zm~v{f8htcglYs~d(5d^xg6Vk+=iol#qVh0W-aRn0Njq~eN79})s+sK5VBNw8f{`bx zNDgOZFSFjDwB>T*apX@%LuY>dxoRf|`aL=L3hOXkS>CU3o|3Lu{#=RQH}hVyfcS_9 zgd?l^D6k}K(;-|Q*f3xtwJt!2*;U4u+0JZvJK5>g(E=6{3 zJGIvQ(esls+W=dWh*$(`d4d~tt%ln)0|sufCS1Plc%!T=>>I!ys)Kj5CW_;M)hq6i zqOn@uQDvxuIb?c6zKUED3hqc9R5sTCqEOpth3dc2!*gXKde~X>w!Sb$%6Vq=p#QYi zO1K84{Xp3i23`6t_>V*5Iu^ZW{f!9fmnhd`Sp{G@7^1ZZ@m%Kns+IjBh1kS>U-T!O z*W2xh31E;Zr^NaB3DKC!Md#S6P&gyCEzw-vueI;ni{z z&0kV`@ODX_e!Y&Z9EjP673NBO$7!*Fdi7#qKW-j+7n9Bjg?%NzHOS$&oE0Y~xT3k< zd1JNz@r07z)^=!4(8cnT-+!7M&|22%+!IXR;+3a06p92J0sx5BdQ4YW9mdh`Fv)G> zbEQC@`k53~#GJIdY`mYx1(db@V&Nn(J0EN8Vt(3A@(lL;dm~l zsa#;ZqBji{CDwXoI0!pC3s@aCd;dCOF4WbRBas{yr59ty!m|#-CSbhl6sa9;P{d*D z70_?g40}m=7dr3NgS*WhtbXghrTDylT+m`^xVCETzAF=_`ty9pyT7@%qA9#HBKh&a zM+R_Nnn>yC@o@UAQ2QE&M_BP^ zt8kvXM5-ghI7EBWag;j2ZX)FZ>@AHTsPDDq(S6}mbijqE($FH09IbF8t0H5YgopL8 zSQCgV@%p@^XZEDOU($Y+b=a@&zb$fOgXrBTC}}BMB43DuTc(>7jSDbk4&iQT28!ct z0n_iGwTsyd6#ulNqB2D8YN|*4reK%fe)H0dCc_F!`j%rVOO1FOj{T3+gbm4;hFbCl z551ljQAp;$iV}3A2{a=mGh|o92UaQpx?*inH71tW{!&h37tlq2PNST?`;CweH_YKb zf_)Ej5y4=eHjHU`Ea<~}vKKS0(Ioms?t;IoUuQsj>>=`Pen47c*_el$F2|&lNZX}tQpaH*`X!V~w?u*X!kZOf(mfkWw>Dy(gUa5E zdF}}IzQh&&9@(lc`}yNlPu>*0?O*>~D|sY1T9M+_Nd@I^Zst*>^y_2f+}!^7a1fv(Q_HF4F&RYf4cebaE#{F7^a>R1=o55Qyc zdN%|EXkZH2ehUfm@}fm&1(x3LLtB_b>jsU7&f|ga=AsP>GIDH=VuidJ)EY|^V&?*~ zW!TsHN`RzOKpfqani*8EzWt<;FLc<>5o3?3Z%oU-UXoOQ&SL$W_o4oI|F|2)ncl{) zL_gLEU?#+|pyTRoMs4hDVw+-qwTRbrygge7XXbZTb(xn z_U*H_4yELL3!vkIIIMhY3O>Cc7L+y!DMg1y@8V%oNRVqms`=;hGrz>=Nd4^&0ipSj zxsO_w*vwi^C@|407R&vlH`U;M0(7Sx`Y^nPX2NkJ(z-_?H5L;O*k__rBuOBHPwYEr z(16DqZVnZs*RNOn3*XQKZ&ux{k+02kKk$Ktg-Y#8Dn4)d0?yTk;o( z9I#MBFeZoeR+Pb9Yudd>{(4EK2d8Nhstty8dT4(wRTLQXDTW;1IzY@N`^8o>EIZfTtb)dw4^WWfv0cOo{xK=)D-aHsb|+Q@5Bhc6&^ z9qC$sKZ3;>ORqzVatWR3il#C}4fQAB z8nyoRCx$GFqc0^>H|-i^5=~~sv>j&cicEdY9Dx#ZK=rCf2= zg@eS5|CzWkMnPHPRGj(^|L&XHc4KTXQTw(S`*uW@`m+}R`@X+p-`lAkN`%}ikmRDh zCDo2c;-#m&bZ~u{6My1m+Al;30G)G)vF`5vf8ajDkgS~(J?CLRynMS^)jcJ2`qBKB zy}){PmB%THyVoaylqVQ_V$I8l%s}u-&RVK;dvc<63o4`T3Ohtnggd!0RXnH`^5@A# zZNSs+8bnIIFp6Blh4sdzT(qEHi9j}F zou_HTdUh=C8D}}THgcfY8d->S-P}O>e_3UJU^NcHOW)S@z<{jBT>!cs4kAKA^4vKI zF>2J)^~qEH20mm$MjeXPYeH9?GLA49;|Ad)F`-;;{;j(U3-zTi0cPC+`@kbT@u;(Q;_FWz*i$S>sgNJ@72$(8+q5I)K_jRT zeMI@=j#l#){qh>@JM+SN?DtYfv2Rh5!1k{;rTHZ-m9F%$MD*gK*~Q8rchIBTrsJL# zS)VMl#|(IV6tM}R7uE!Ia*_4bPE;BhvWOi{X3ogdwjnhpA;Rmr5%9n1e=;xgc>N9C zlx4ssJ>V5NL&k1h{lNr3w*23tBA-gZ_o&egIoot!=cliy^&+NPzC_SW1k!&lq2Q02Z;Y( z%w_fUfsw@O1?lv@g#H2AC<`$R;`fhxH4bo3t1kcSULrB~H&XKRF$;C2(bSRFKY|^B zFdm6Q4UcRw;(r11zR{^w?^fRqufbns{r<**b3PBN;Qh^P)-KlH&;zHBjxJsBC_Dka z+Gf4f)^8oE4So!}{2x9%l!;VZ&Uw66m-yi`sqORv5jj!PYe2NlFC$C8D2r1KrAwx) z21+ZwYA1wR=56Z4YSH9G!POcC&8BfprV}G}omLCmgm433jvvOV4v(JoB(ku)w82qy z!uy&;w-u5r*C}r;E#ZIBnLlGceQdpHm4wZyYK*CFc%#dJnpWRiU#S>*GphjTaC|vu zRVn3?eYFRFVEkN;x&Y7CT=_l_y@|U&mX)I~oE`azV?a-=?m+ohB%<-_DjMXfLY@|U zZqJ%{p+uUZIS8Zuk?EJ7QQ2yJ!a0OIF4TZBVLpT_+T)wJ|Gk-A9VJgLCT4`AG@Z!@ zt4Y-v{Vc>YwlC?ePL9%i)d42q;*0-1AP1d&_;+~V^ylR-Tu&r+=|4hBu@($JYr^cw zDWRT}ab9{^Xj%Qb{+%hNo}C^g!ut+9QT0wsedRjU9Qj^^bOaOqidEi_rq}a{6{T;( zsHVTi`+Q^bWaX(<)+s;#u7nxR%Wiyg`GTsbP5EY}wF#H)`R2xB?4tE|oAu;q!tgM_ z_5YX!nA{t<7wq`p@n8x$>tYq`bg@aj7Xe-<5G4iAUO>zwOtgQ&sHU2-#824}YY0a{;+_Jh3WEOL@#E29$GCS#`SFLtZSmUw#M6qS#B`kVU+(|)-X0?^CdYF2 zWc7|<$34sh^`d-qDcc_Wc4m*=Ox9Mej!@R74CIm+>$Q6S0DN@p>k3pjIQ}xd#qUDw z{O~e*TUuJ$%jNMf+4$4l>cxQKOKWt7EqZFIgAL-|zq9r1gqwuG%iC?!d3AJW z_12>h>j!%oS6Dlamtp)TQhdAwYxQRZY|uF;(FKNx=bCmgSDZi>woPkqNFWEC0hAE) z?cc-`fc2J4;bHv8kmEZ>c! z@_RzDl{5xgM&TNC|qGQQOZh$KzlS(~W>)sZU~P$g7BVjZ@^W95n?GuMX#qwh%R z?gYyHxw3%Go#DvQ&rkn;dI?u?u+gPKij8Fd&d1({Atxv7LqV+i zn;LCYvV4U=fU|o?pcVUEP%}?HJ{~xJCjQTrg1U10SW>|rSQGdAKBHLS`hG+S)Jw2A!bDnL36RtkzS?ezY9ZjLM8kb> zM0}@LKI7QL37~>#7y{7&tE$<1q3Rg={fs4?%m&^o(hcSQaEMvC*{Em4)`7t0i#MMs z3QDdhXS!rNPChcSTJ^@<^}KH4$a}u!lpv#lu$FzrgbrNTib`=PPieocr{_!9s zU2B)F+z9lK$aCPF#Gh2D=l~cc9%C)ozv(mR&B<+lZ*FdY2sjrG@(duSg~XklJ1ta> z)*?LXT}HCi`@_l(R%KGbcyG*>BdvOYxC)Eyujh~KRo3qT6wFid%9bCar$~Qn3G^VQ zQ1s~YbV`i}r=2XBgmHy*@AmCNS5o!e|NCnY0aO2G5M!K4Q4bAuEJwug8iE4$Mrh+8s0-dBWC$)C zU6Do(b$&}I>f6?WBg5c-a}~T~T3MP5H6hNYiBrGS(9gcWc`mu@e|os@F*P?j)ov5U z#`4cnd)+KsF^0Av=qAkB%QtNH>jR&XnLdnM&toPthA1!rN^k#N8C2h<6s4 zr(b*$pU7CISgrNPqg#>zZ~0G^bEJ^7&PWQ|0FaEgGS_Jz0Tc7+N^C5w8%NTT?9|zFRtzQzk`wc+>`L( z$_ewXIvi9c)yzE4FLvp+E~=B{iiRZAlWtalBs?z_d{Ph&J*Y1Jw*o%9UI)R4VDnnt z6_wOy6{YdMgG>v`ywI2{yh@315KFCsdoo*VL>7_Q8FLr%cn2m_RC(BWlDyLLsyxsd zK*?9ijAA)fJ8nYu$E~$WMnoidSm2aSx6hY%OoiZD|2%ubL>KF9i_gIwQ|sdFVn4t( z<_#;b(Ap66M1155tZtNQpya{9+&Q2lffr8shP8dJx7RVS12n^P@T2U!$-(i;x%cD< zm=j5qtu*{aQv5m;>EX_s>5~TUV=tt3${LF3pKaZfZe-{_eN5qg7#EM@537yH+aTSi z@A{fb#R1Wc@~NCGF6$m^?G&pI+*L5he5H)9V}Y(&MrYRK`cfR5_|KUvhLj*ZM_|FI zJO~TqeM6ps^GTpSjrxATSwyIUH#{IqdU`h5#$TK^&4MT6yUcaIQN zqHpHEWYo0|>7HxIUjZen4XJdb*;7Vzz$`5H6Oddeppg=PlTUnc*z&h)H zoXzqwnE=E7YxQA9v5Ih(`v@^V> z)A^GtH%4!F=^$q}8GDf#{=TrUhnONLu1hHI`6%&uQAs3HXbxzd809sQCT#+!D@cUS zWxHu$5~Z#pI&xjBpW$G|R(|X77}mp^^U+j}CKcDgc=DE@(>GRD1aA&Lg1HdQ$BZV1 zBn)kuXXO`_Jy=F4n{w@?ii!aq5C~2Bl9wAVv4hnv*adF`oV#)Nxd1o}hZ7CmyhC{f z1OzciZ!VrzAVp$AKc^GY&M5@W9#c%jFy@OKuWw%HyYDrAM#NS;HBp&{x$`E#w%z2P zBvF=w^!6Dmd%Qu@{kPDE@ns19g>U{c)hl#0e=V!6A=&f?lmvvVpx=5T-@C^STH8Jg zjvt41^``BN6jI{~MrHyMOf5-1w98H)ap9VhvxCl8sUMLt%;+uBS_U?oa&Hm6-uybUTM z_1e-UW;;3~(Yd;8cB$&@s+mO|rvITPOqB?)e+Vm11^OIHjL?-hoBGm@O<1A6s5GuU z9pi3Zh*_8G- z8ydw(AU@9jo=(-Drza!19{C|fC~h2ji77qSX<(W_mBDi?$2eSRW3YzwPB3>1BUMZ} z49YLpB0BciPckyF?f<5Uh<4)4DYspTG?JLyXKcfa7lpRGEiS^mroWz#;yn(A(!Qq* zw;bxNvq>_{W1<>5aO3&+Dpl!(`{Do}J#TIg+21#Vs}n4{Q%67g?DGIkJLqlsrkpi! zM(rlFc<^fjkbSHtR}ElCoGl*3y zOdW;8K@{s_8Ww}W`3kp+vZ2=}O0D??Zw|&JIO*QnG@9Dm1Pukn0tYVi7wkdbj`S-- zs1h4l1`LUe>rFiLE;)!JsQ#sKPwZC)E*V2)9IH+o*%d2VpNHtm1|V51!+X$Sq^HJ} zP*yS>)131B{d=cE%Fhiqu41Zr$D7>fg9K5CRWU|DL~xR1)J^4wo*f6G{4b$~4r#$7 zA!P2Ua#JzxuE2!BoQL?Td2k4&f$Pb>;G5s+;bK{@*E2N)U z`c;&dij@15<+AIsih(6dG9!70lc^s}@6D{r!_MzBf<@X5@q6knox((6<4d1}nAUqk z@y*j`2zM6>8>P_l`m@?j=s)X0kLg*J<}*IGhX`%>8on}U$zGy2Or(R7?*C+yTt9js z--?~KuY3#C7>H*Zz1v%udM2lBsk4Mjf&(KCxJnt~*xAF+Ehk8G9|GU2d?nQymL(%^V3f#}m z8tP+KW}A21q>v7 zAYE332A!nxAAf;eUtqKKYxuUaweMTFB1VA$IQG|P0{0*3kG zR%q;WhM&$OTFv9`Z8*qegCm-Lf4b;B%)yeHc#Rtl^LGnh4h#Elp9R(T_U|e*>;xkg*i4ZGKjV z*EVD{XOqg9(M5qP@`5%8gvCG+%VjCZMP`Tu#Cz4{wpN@Mtjx-C_~|F>6svpNe>22i zSNHC`HWXCjVjcD1UzmWhhFrbmt8sEhiCkztC6vTZBnNL!Kk1n&5&w*fY)fG*hHXrZ zg`VX6j0Q^+gmZ)|)w4|Yy;kFQM`h1rBvQ!cBRRV)dM(dD?YtuBf}ppLa3e|5g7$fOhU|8r_fYY;K7ALQ5BfwmM6p zwZ@&8hWr2+P%E7P6X(Ca3)A8US8+2h_{Lgy{_pfplLa?&}D5229YobvF6CQKFG^FH%TobB-qe zOO?GHT5L?)LBxdak3RhmQgz+I9K8X31;YGwsHcegB52>pssDe=KzNfoeZn$=}ZKIr1@H#PdJ~FBpnNjt1#X z*p4aaOE0vZn1d9BKYrxO@=9Bb|}QMg5_)&D|u z-m5{gZDk&JurbB<2{g=RJ8|BtyR#Vyp#}=g+abNW46$g$!uuU;L&mHC~ zm`=~Lc4@C>rJ<1~e>lX%K_`ZJ4kn%K>a`>Ed8K}j>SMxQdVyF{7re1;e!S;( zd}CYCx3Fw!Jz;+~3Ri#+{+R$`W1#=inqZKcOesF9U@fxqJe*);Xgs>Y1-bVkTW#}# zhgxRalE8_LqA&F#w34S;m!r18_WH3;M1glzAh;y3Y*I|mOdlpGZtgi(`wQZ}yfrJcR~Z@GvPpO~IL<2k&a{xr_(_7%fT6SJlM*_)7n z{6|%&!~-8GH5M4B1mQOc|>_ymJ|SzDRhwPL-W=-(JJDM$LU15Uf( zDcX>390ta(Y1gti!FkEe#z!aDVb8FdKfgat@_F34K(H}vPbGj5vfzDPjf)Qw&Q5X? zd7<9R-EmBz-)(+O`&(F>O86yX* zc%Q9t;Lk}YsfY&tnCr2J!Kl`^=O8tR-=ut`J|l&}wt#(2tiO;q$n~ZXoR_wMoiR?|uQt2S_NLNMS+WjI2}1s%#l`JHUwfmJjSsG4G;Keyt> zo>yWHo=?CDbCWIza=}{VtPnc)_5;+eFdtaQWAGk2vzk63{Z)7AOO@wN|J1@b&-v%S z`1c3XA;&R7ZL2!Ob=kpZyk-HE#L)cgM3QRk=~lJ`GzftAuM1{68g-bK{v;P|YfUq@ zT_UFNdS7GLYhOsB(oFP7)G%co!ZB&Pf&9pRET1x1*K{ybm`E)J-{}2UriX=NnI!>S zzSn38l|wZMXBOw)`q1s=HrkyEP2V7bRO9564<7h;cGToJhjE5*+6+2&cEqXYRrff< z2U3z(a{3etdD1ecrySti>SYH0oab`{O~d;^7fe!b*Pan$s8?;3tSZ=#fdTEac45Q~ zw=f%RGv?Gmz(xw-iq)K6|r4PtF&rB6&Oi#Ft5yi~YC5k(2H@5y@bKI?qE8|n3G?>4eL zzwFlD$%p#gd1m9CQ;RBoV4oeN3kXQ!d6mQR^7eAWZb*j1+uX}9hCj|fqrXtvh`o`U zU(X)KxG8K~&0zScS!QHb*;&l&FD=4$qK0oCPHp31OYdY^@(8Bb7{Y{L)BdG^fR5Jm zL04;0d7u@~-|A1tK&|4S?yW^%g55PKbY(mQL@U;tz9R^6U^O-9m@sCMgzod*tNQBU zxl6amYWnN5fKfp6n5&ts`Jj2A`N2E9b3fI*KLp44loeX-3Khlh$hASv6%uB=S-9JUVpnO zh?$NaNzS~yX!xXA?(RP843vZ^ycPu>o}^y+;6#zgz&FkqO27VUA-deSEI9NE`e_A6 zf=e1<2cn^R2b%}4lJ3KVtZ(cXp-?~_wHK^X)^HE89gUd4`%EDtaV3)yB#Z;e{rD&) zf$i3}o5EA{656vUF(c@?)$&{RvtP*~ao3i+)F&=X0!a-$lZyNIa678IaF^7F=@Y#% zA0dr)_uqh}X7<-g;d8~B!zcrOnlQ{Whl9Pq*B&5*d1#C?RH`MPb!;_Tw)+L_cfJ^< zxQaY3DUzJX(Y#QvLPHe=r=9SbIYCmobX4!t_|FV(@w9HH83hpxB%R14?5ek$trrrw zV~um_LNG!F%llx7y#JAO)nQG(ZIqN!kPr}Qly0P?q(MqbKw^Y60*dt5NI_9r8b%}C zAUQ%xVx%CA#H7bYjoSDAuJ5||o9){B-p_OHbDwi=3RO#eBc6E`<%L&n&y%0(_4)DY zcbav4no>19wPP5@vI7Nn$Us= zQCvSNSnW2Ne0^!AV!wV}?s6E6>0ddMuyfpB`uOz`uuD+TE%huw{#Hx*sUYvk8MYy( zHe{^Riv-J0#?UCRNfqu#ij}7<3S1(<8j*%(uuH1F@fyg?|v zeoCG|HFNa2)p6>lWw1zRMFbo#Vl^C7Q9|b;zi0F?vQaIhcEZYIyys(IIh(X6b1hT{ zu9x|ACwtrOFeyAa54=H4%o^{^Yfv{_iZj@5^SOWgC1md>s|?&X5)lb5?!zyAycr{~ zGIVx|{v}L{PW;Hv0Jv#YMCpe#dzq|1DmqGE>%12FxZ2z{|ElzLRRwmOZ7kQbRA$$u zKh{neVrdBoRRuMNNOo(4a{{OJq!r{3YT~3j{l?b3R_|U2>7ElUBS>=10KQ*EuylhTH>FG zyRm^HVq$J~H8WklC~Xd0{P}9Bire9vRtJNEx zuKRdEuNI-A7)o*$3Vpc-z3AWK+sQG#&%l=v7rGapL+q#Ud8 z^t>zBj7=}Rxaw-Q{pU$HWo30)*tIIrYfH-r?d^?G#9rr3k#~ZDU7s@1yq(9ZxF@-T zP}QfBL(Anf?oCu^)A+CXk^y^%`)VZ-=3>3-^h0{_Q&im7!#Ztpav__zd)v5+&UNSji{*Hl&!y*{NsdXrx2$9yd9$@CPyY;*_+bt z&kdmsRu=gBM{FYoNoG6%iAh`qr<0jzNPdHeQVcP|o5(jwQT#S$B*a|m@Ebuaq%w{w!v;>d}go;34}A7 zp;-b*XoMPFAyCCF+>5P>0Eenw+GO#&Jx)u8k!1f^c{H8wOsS^K9&|fSwh=uQ5HW^S zo;-d)=q7J)x1 z|Gw!FqDuJlg>ciGa8%x=vC94d$Zw`gs?Gh##^&$m!#dc*x0H;5r<0?S zQePrE+H@Ywi@D$wg{~y)wMr<#Ixhm`?VtfM+td3bF?peRo zB87TqZ*-S2!0#x{5+H3qL?TgpGx8RCKLYx@(qb)|mhu{GootXS@E0Zq`|r zqMh+qWCKLC+NTygQ~@2Aej&6e8MdI*K(Fp0k<~pia%{l?$od)WSlJF>>g3kzIDLa* z)4c<~$y`DSDZt{bdxtBEo)y-=V~`)XM}F(rOTuYu?dO+}3A%n0@$kjX%|GdV%NG8) z2a>@dml5MLx89q5n_q{bA&;^!+SB?_>+>(xmx2j3L1Bcxd@+CD%4+O$f|reWXsDmZ z1sG^Fo*n#whpvdsIfeOB`V^!&52_FmsNQyc?&RYe9*k$BAi!D^XA_a*PFEF5?5}un zeZGCK*=<}b9jP~C*X*71b`3As6FSlAmXE^m#*`d?(H$0hnZ+o+&bm`mdKfX)-51=3 zDz7dptE!+?K(sHP6pI{NFR6FskixVN3R_GcgY3ODn!Vh{d5QyW+YTSLpI~*y&AxW8 zf|_1%v!Jqe!`464?&_Z*QJ(K&Xs&h`!FpL{2Ce6Jzx=rK*TYTVhr;@++XuDdB-cGV z;PFZUvc+3qai{#s3DbopR3*LBnp!KezDlR?H55OBkH4WWq)rD#%EWn!a~?+4hUqxN!eH+D5ncDc>k$Crj`P>AZ{0 zN@v&$pG}zU&&}?*a-A^E#8P~S^?%OE$JrobKnf=%kSuj6R-EZxo(jp@b?5CwBxFm- zb9e>#bm8w+2Tv6vILk?nQw8~i3E(*s!I~uj35}~TlDZ$fs}S9p#ZHo5$>bIHrwP~6 zwwp&n7^0}_94s~?BZCcDNr3SVK7>Q|0ywbzGo|2LRiBYk8Mhf0n=R+W-fEsNu+jUdXIGE5$@Uh7vrUNaHNgI3TC7RhBE`N}B@PR`8~j|D{ubybdGtCS z@Y#lU{=J@L+|evg{nG^E!S7M+KHmC32y69QZwmfVzG1zA_&&n8v zs0adUB6S-VO9a<#B&Jz|Px0 zZQmM%E^(978NunT#oKLGyC9jP%AdeHhJgSEM zH8KbT?hCtKyFiF~Ds-IRw>3Bcd!Pe9l3)vDEm#^upQQVEnWc5xlmw~hMcK9&D-|oj z8)NqWO?2G+^E)=Autr`0FS$agxsQT(@-NQud!r*n`8`E_>qx@tq8hT_iuNj6_>H{} z>Kv8oG_%O~ie*%tpOnUmCRHQBpr)Uy!*CPm3N?brjSG|GfwL z{txWse$C54x?xRRA3*^rHlpIHV|#P%70vIi$EXN!90x&P0yOYXe*@p-7x07Kw1SYK zqJO;aPIZ8+giN*p;mZsv0;Egu8+vwkfxHj5LWp}*nJXkW-&rU@>AuRiDS$|) zy;+M6rXcN~0d5GIi*;q!wXIiako?LicG4RWA7yG1nnqQUg2pX#8Tg^+k6r)gF^3Fm z*j~yN9o_3)vo68#yL)C{&(~BudM9-$i_cm=&`58c*t~v9II`3j@Nzsms_4x8zful+{T0xWPGcAYN~?=N27dd z>>>4jPxQW|c=)BT3Xs!VNA@zbB+JYPs?8j+Ux#P)b=LH9eQkTr-7F0I-hXy^)x$qU zJ9cLc|4zK^-T3rZ?0lbskhqHH;d2El{qOG>dfuaKW?NT1faX=)07&<{3q6SZoABU3 zf5f#;T$47E%XE8$yQ`={9=yK75kN_C!$5)D==?QUQl;`2Mjd&~I`9 zL7}2_wg9!|4H@Q6dCA6BCnREk#z-<%>gCpnQB>CGFehiqt;LZgt@I6Z)tUs^qadHr zg6$HrZ%EldM3d9`s~2&2?GFD2{`|tHLW{_<)=g<}Gh{lY61|E(dZow)i+kS4HjH6F z2(D$k_VHh~(y8Hf*}8+DhK_Z`H4HO>*zbUjg~TlL+j$KZ52e7#%i_ zy0+rpG^ObK4-$@*WKcB*Xw>ue_siNe_*B2L7UyQ=jlY10HGeuK>ObxXOkf!#h(&!9 z4gdos!J&Wv66WR-2n$^x6#PA(5EEK4$y}RKZoYJL7ZiBAh@r5Vc< z311%In{>cWQd8&iVru>9L9ezN!c)rC(nrQ^fsQh2`=DTaAR1SX#E#LsE=nF zf96iyz}#1%q_0Pe^HV^!-4BbdV3zyTraino@$-A01o7#M$Dh_K2so%sJs(=kQ%cQt zsdPO)xac?*35-+P^jSc0N%9!yD~y^x!*3B1v*D$k9Zbh{Y`c1Gy4T(42o>JL+N1m> z&#XLGyLRL39$bCX=?Xy{K!Y5b$_&c9{q;?2^lMgRTelCdMa*-!NnAXWEwyT?{Z4w8k7)a)al)=3_SRg!R{^?f{HcD6 zT9=hI@ssq_OZHs;CP z^)Zn@a#3IUr_n`^9g0&}t!Eq)sS}uoyt`HDt_cTE%6n5r@ttxQ$@>>+y9dRh)vQO< zSXfiEdtSa2Dz+lQO%VMMKBAu-qIP8=@pn}1^X*Y0sg_2ZW{AX$6Pb9vgUCt9{z{W4 zR4177OKs$#UsS73dPh#Xt$+}78rOKN@4S-Y0TH>HMGD7$lz3h%Xs zBn7ztlu*HcA+jMo=@EmMab4&?azqh9n*8~>R+FFSq56df_t}0hWZc~t?tIecqiphZ z8Ia`&vrIL#m1yYn&l<;5$5>7MO`@r89u*Y(H9_5$9wq3CfP9Ah3FpxjSNQ@C>*RAl z%ts;t!G)qyNFxUhEiQ^fHNt7S`@C9lV-(Y~W(pSH=%SRn45p@Rvj~FYqVM6RtX+6{ ztF*PDtANI;u}zHS`n88ejSj6Qo(uy@Rqs!`IGQ?(ex8aO_To^_ync)hshO=E;*QeM zcrh=soW=pxP-Qu*MtjQ&R7<2;i&G~yqliao9jJxt3p)xpIky$u$0RTZNO5mRx8x1o zc-8-eNoTVBX0I%^JDoG7K9XZ+O-UT1!VW+?{D~T!wycGeH%AHyEnRD6-^tN+i;WYB zD@%vdm4Yw{+#7;yBW3byBiF&|Z=XMmg&yr}nr9OuPs_Zh*Jaj7K5^CSV)WWJANW5_ z?lW%_;iqGCsCF+DG01-TrQ11%^@#}Izx>1MJ$eKVP_;11mHb*dAZxjtJ`*?TEA*4tk6AIBtXP|MGIODn zR5~q)jI>noSfUJ=*OJzae?3SW{pr)Eo~P=wItt~7!kTOnr9579N>MAE6 zO=1G8y!_Vxd>6fMZd?tS7-CZkdO3uXk#y`EU=B3|`LF5jiIKcToRBKrWg9~%@E_MW zI6RIKa>4;aV9zIK$HnZ(D0Ou}v3d7-Q)=J~x_$S(-Tm`A4P0SUk-q+@$wa?2F_tEV zenY9=_g9{lMzqEJi}N(4X9uqbm21-F@sfjry&<*bZV8k>p8kF&FAw^D?-7}w z!(W`D>iNJIYhw$~Ms^$^g9ycMy7N7+#tV*#Bea+>4eMnv2K~hfqHwWSrQbEXp{Q)O z0zj`xR@u`)b{0fn)vhpKX66`kv>2CO8+IxQV1z6N004{@VD3M`d#B*MJU z`NMN_5@hd^nM6QteCU0f2Blj`pQ&o-yd@EaGah`DC1H`*e#wq4SEovc3vAOD{sCqB zDVfO*k*2=q zITN_%E{z4K0yepRBa6sKKLHU?+PUyinqR#ww;6)dE1o&Uz@vsk*)QHJ_1IuPl>$Du9hH6J6 zQ`?;z7ei8|^fcuh`S7A-uV1r1#R?xShcxBO@=fesU6qi6h#u|yv50$ph!^}#!0`pT zA++VBu#Aa>sU<7Ow zN1_`9{T)Y#Au={Exo6AES7)5_>w)b#Kr{{QuqeIzo8!NF?|1FYqirsNA9Mu4raV;G z_>|J}E~ixySmyc4FUN4OWZuA5n1p> zTYKVQW4OZbn{TeIg?F%VZ^AyLKR|&zM(hFB21ou8^GI&7dTbQMrxtqNwl8Rk{#vjt zzibar>Xdtm=L2|_CYgX@&?-jFC4&y-uh?Rt5~o+IxZ%+Z5`G2oSej|>o&&P%IH&jN zoM6^?O{Ul758lzQ#UR}W#uGqgU2B2H@&+$VIIt_)?NSdQja;iL^q~T0%=D)Kl1=L$ z^fLI-&sof&HST4d^Q)R544~))S1F?UKDC;639qFor@h3JicF=;ivY>9+pY90wrnY$ z%`06Wtll0_K`;8EC}7E8R?@iGgJ%T#`-ZQ?o`DDg-0GC5B-I4HoB`E^KF05kLZhB@a5wpotW3u967I2*LLLjCiNIdrxx z$&06-gPrycrJ!no&1Lo19|=V!4?~*7IXJRM&pxU{&$**!iKX@9s<~ z43b&rcW?wbZ@(XM+`Fj;uqgwiFnG|_Wm~4`9C99stqs!F-nKs+PAA`Q`Xj?f@liq3 z0ly`GQSY(Yvt5u!1qI80>cj$a?CVRjDK%g}-WpZcvj-(YIUIC-HB}WpIqUJGk)JO`ccyWYSI`#7D!t9GcafUPirtP|!HA`cawVIv_QKVx# z;O#B73h9275DZ_uB_!#!*a^k|8-qOhJlE0NUv^%OKkpQ;(JSPQKONv9S0?B15F4~H zX?gt)Lf_Fnlf5)4el~l5hOAE$6TKQn5(>ySrb)X7g})ibVzc8iSd53}XjuK=oXo?p1!Vo5xQksFNJYOpQXXutNf=cYvj zYv|{9<^{c4R2R7p9&An6L=e-jV?3({4d}-qU-403leyzxfpbn9ccszigYW@bKtm&S z=(fD*A6u4l>l+QGzmr6agimdSVMqvRBui{GhJkBB^A*uNYB-J+#qZ&$bPPu!wA z+Yb<%FE{)$-9ue2E&w2LGXk=-GtQd_GujRhhkwWg% zx#{`X#IyTLlJVSBNiH!$J%kOYxs@yW;T z;!|8Ji#|YOKN|hv3A76!`Nun!pjWdZ&ZFta)3DnUAjc(J26`yD$<}rXL|h*H-iBX~ zOfk;i!fR*EDKu4@1^u z%W|F0W_9wFr+$^r7I*pMV)IQl?ixEsjOOtDosF}%d?lI|qBa4W2JEklX68756;O0|uvKN%-$?|R~aBN37B^j=rlzXrzlTSxO? z*{2JpucsGHuK0TyZmF5h9=T$kK2CvgV_P03`%#JH&MiK4&de}PfA8PTBkPnXk`0mw zyJo=5Y>SX`4@F0bSakk<9WRJl|d0YSAmnc zjkpTC1QkI^Olzu6f(J{%=grc$kl~RUfU|>VUfvub-!3y2eDC64{%^ud-6eWHg`_<& z7@DsC(CN3`>JS^M&^`wW$vv5;NJnZiVLKW6P!jX&*FBMebVz5fD9(4wJHwU0LNPZi zQ06^gkuu|F0KmCdJh2#gy$8SD6Y%Q1t!i;9T>rZF$m>={Sn~J}4!b{^p#<56j(XuZ zH#-(?NuNDfr?6aMok84oGZQ5%%@evBms*>`O3{7I_`$Qq)2@+-`L9Yv%1^FtuhCBs z7asATboq?)NyaUde!E8dDR|^BwdJ9j2zpX~R#MWe+G+;0xPWrsUp1%c`)TU}aU6 zkcfzZo?g@@Zh7@)nF=B*#1nk5<^4wvU+v0ApcZ$#~W=9{`^^suz_H= zdRixeU+15+XY+w(odgk<3H+0gTroW>kHO%{0xFp8J)D3wqJ-F{Y*gop2^a!Zrdrzazf-*Jt;UnNjX>! z21KA$nM)t}o44EcD@7I-#{ED13-mZh9mppc_cH8;*7NhFn^?{41C8z*)?9IPT^TPNu!OjWgWu>oh6oAHmg`Gf}$L zNjK}RXLx5+VvX6_>fSF|&8a>Tk(RDn_sVUywO?F* z&f%c9S_O=47T<4nJ4y*zsY$LTKGyB!5LCFk-RDevdqAm!%p~c-8vXTxojq1;)hC-w7J(7OS%R3#; z7l_o#ynBmRuP92J68BA;k&(SNbcld(T%>vskfh&skNR$a#2Fdt`Y{KaT&%8@HgMsM z8KfrX%I_)2j-vULRmhVk)i~aH7V-n7OlBXmQK8%Pm_+1Wh_B}{SujxajzBO&dohaZ z6IhB_9y(WT{rrivblL`R-V$ULYR0P&+6eBtz@6|O$>aZ zLPUuZV+n>J-(G&q>nN6tfGSpGqG=qm#iTR7 z6=fYobwe#2N6 zHFFFPJ`O;3oN>hfvjWUoMKwBt`(vyU{;{MjRN2k1`=0_c9-4IuZue=+5BxqG4Ce+| z#6zkS;fWJm4())C$EAyFdWE=;kf_Hm<0J+&Kf?oHRJIB&*3|gfi&0j*I1i9_f#~9i z#s?vL(m;@hAyIYwk4!}26?yDK<33yrC}h{p_u#?HY-VkLRcJUEC8DBdbz`T`)Y9tM zq@Ul$(9PJ$_LYJdb@`#4CHQeK@)ZSbWL(gq#rgk-JX|Yl*UXHnRG*Plj>}FK*H0BQ zdULbgAL3_&xNGu;|hwXwaJr=W=l?R9AoEBN@xM|`RR7U9CG_^|CK>-6(nywn0lW<0MCa(7|H z?=Ng%w|VK8Ns$h;XSYdy{@g3v!`BW}SXG@zTxG}5$`FrRBv2sriU9AVvb?DSs<=@eq`<0bOm+tm40xK!IyzH7rZ;yyAi_hUqj1gFHr4j_yaWZkYIdtG% z6%NK}9~z%bB*Z*E%d40zdR>aeYP_IYuuzK4`9d1H;^TEPO87b~#CK)dZ)M5Z@i4z& z(5m#(a3Mo=>6Vbt?Af#XL?PdA3>xZJ;$o+#u2f%kdl`=UG}<5DTD22m0iQ?NkHQ~g zt=|q;I;Rc{lf!ygUl3f~-{bl#8AWZdFLrOUjw-l+U@M{<+v15`Kx>wA5C^DkSmEh@ zNPr9M_+{0eJ8z~ruI$+}DzycpPi%-bd9MK?$3PML__yLHZYvFRmNARGgN(0RyOs*f z6r7QJjPo{54mcGvTFT*PyYL@HfB+I_yR!saC^Er#`;*l*#1)>LBvs+11*1TaU>GqI z(SSThdh%XOkXL(ZvKPqb;wd_j{|nJhn%VVw**T`7*>E$umJ-+v|{N zVF(9wNRxN}MWzc3!{#=ja+w!<#rr9B_P2@al8v;&tI|?U(>xA31(b*1zTxn79G$p~ zQbzzQrz$~!=yg`t$rH6u^rir&OXk!@vsQY6`qz-sEUL0l`r+vi$vOaTcn#E^N84l& zCRTg7kP@%6)wwd@>J8=#0CgSXWkHo5&F8@+GX$t(?3P#fs#@`z;Z@_r4V=@Lv^`ibm%7TpKHaVtz3d7jW7uukg%> zB9B1lidu1J(eBtY{4JxR2^>%X6>Rn>U9$D`bk-x*|MRG?Vez%hVHyzLzk!DF*8&Rd zeg{dFxd43cGogBAs;vkMDWFKa5|w=zn3$0vTOhso42E0Vc2#ueFOvCcN7{0UO=k!3f9t(Z zA3l^xXMnB%vMMb~HkB|2{$?sGfRG*A(Y1^{vrL$n_>*p^;7Vu@cij8UXI%0H;QW4F zh*~tU!*K^TuZ_CnaF2!b?ktb!E%^RHkERJKw^$aP1WD_E&Mh{3e)sOl`6nvqU7mw^ zi#UyU52L*=B|3Uq)sDJ+N9Ml#0s+xxx)I&u%^zfDfe{*owYA{4&~WMvedYoMhi z<8xMN8vxMHY+=92sUlPRF~#?)KV-SMxv6Xjf$Dl856XQ5pn&Bh+8<8yHQ3q!tRap* z{m%>;5h|A5C{aJx+_`M1STj6ZI<<43&E-9w$hLwB0X4f{msm{qar08QCMZ2pOKT&r z@Hx_*6>{^DbrDdQJBGYES$y}7zS73CR3CO{SDtd%e?k8WTrpVx1!3{R>qlc}3DVNi zl0$P|O&pn~DYdn&(Lr6iCxCM)c`dw+VX_Xqz?fk!;^dp7KSm*O10j|pqo)xS~PZo2YqMjSU6+T!;;KjtkAdCU19`Lx0Qj&s)i zs9Mtbj`L+mg@ka?afjhYFkk4=Y|iZa8uOd_iwivSo0#<*g@^uEZx;&hNyUDWN6vX% zg=r^q_k4I7u=n8W-`NO^>dPKnb4SO^B6&@>{7t=~^O;~PldIofo~J0ilx8ATPQ8SA zy*=}U6W3HCKmmlcm=Y`_1B)IJahO$g!I!U8RAUv~LqSZ6PR@LNKYXZVSk=+B zO3NXhB|$Bn`}x{k*xs{4#XpHUqQwfvg!b#qbHt$oH6Igy#`IC%7wCCqdQm>QoQ8S+ z-A6-{1l>PNvi79o{M2&Lrxw?ZKPPae$JhD$;F2?8YH_Z3@F3_*v7Y^6!vu=i$MMbg z619tgq{JgCELYnl@asH#Po7s7Q6+NXxar=xuA1$v@GJr9`+V0jaI@S(u61>5 zjY^;|Q(F!;U6ILEC&5UG~Jy@m2;2A_g1v`xTXU`5t{vO2~D!5YX1d zcJMnOR=*3b@M~dK3dCE0#!&O^+Q5%-%|&QJ<1R3Lp=lw(geu(~N<%?@0^fc!_Egw0 zmL2M-(mR|=p8~h~EO2uJpxhr!N&A(_;dN!Go@Qcn)ofScZAZP+vc&P?`O@JHP2&k* z$hP9k$GC1XlNo~w3Ck&2g`~aaPq4S)*f>tG)D`9yXpKzPa4zrfX2S>_T-Yx}bDI*OjrN{zZ^P@p zfOTJ8IY?aAoTMDC+}n1X;+9@UFeq;M)d`IN&pO5^2yoMd-sJe}&YQkOO%*)!7~IWd zB3t7kuH6-Ka)=+^LezOF^*T$y7%;5;lxRm=`a5tn^p9xDAE&xs%s=w zlNW!y*ppqmm5P|one|?RqITh$d*aq!KhG>P$;ou0On)Y05~`{tcRm;3+x`3;h^ zzT`Kbz+td6yLVuVZa7^$rsWZJn~+T^h*m%xv&Kgc4t{+!*kk@;oPxW^d3Dymq;3)f z)t?aV{E%2~Wz&zE#L0JgtuyaKV}C33m~GcL^*U{8(qNb5Sq(frt3$N%_U!HLiR07-FArp2u{vGuwP!C95!ux3yt~hg9TS=R-fgD z;Bt<@B;^Pg{{GcU^%)Sb(DO#~Yx>k9t%tC~48kE`lJdnRxg_F>UqDP8+hxCtk-0ef z_b(xHYws&kPQ9yTLHb@bTKn`X2GtE0yN|bTJ-k>ag+70?#PT_xX)@|Rkhe7Vd)Aej z?@`H7{YAh}qO05WXg2!9^^`q=i+E$>IVYzA`3UdRS&I+*t;o%o=&2Is1Li%@69X<<~7EHlPhZE1;!Se%^W5 z<^Z)`0*Z!>d%=9=X@k`G=4hIhv@qFKMx}8%bi9nW;X#&w&*Da$**JysYIF<-5a#e+ zPvZ^=pq|pYoLCU^3#*DB7g=m)D0x(|`cza#xp!go3m`rQGw;&nZ`cRmyqAsmRsfv^ z7Z6G?!yxa4rBpd;YJ1&90k)wO-7!4ctCy_F#e?iO{U)^*UFAVa7ysg&FYN9Q6CvGX zY`k*M_qtC%y$_sdevE-;+aKG=4CuB{`H|`eH6Zv3XB|mGUPV)NJK@b7_)7oovXEs9 zBq5~;Uhd_nqJp|96&ui(l&4ToomCwIoM6Q=(EKT!%HlPXM_c*P;ZEZF*J8`aJ?WV) zm*7Ld5#VB465dgk`A`KF1h2`2?N|CL|-1fcdb`PnkZ>HOP)bp{9VH)?& z%6$VHH+Qe2(!+U?s~w`ti~E|wcXf7V!2u!s{A5>a0^QU>r{m$?_v5I;!Fu1F07sFu zNl3yF2~2VM;$`UHg6ma-;FnK!!~GIO?y`*|&CRerHyi_l2@8|4`&9Z7y+B6A1}f#$OhZVSkIX4(&YTBU;2`HHDY{!zLOB zoqh?LZ!h{A>LMx;)7D8mG3{Nf0?^N7B5dwsPTq(zCDZX&vN3Ob>}_C9IywsAe0z#@ra#)653Tr zxM*Ya4)AXE!9;B;gB(ehLPJD{25ZlUQDKdr5BPFLxsGx2}UGrM|K<0#-|vyVAB* zP(7jAFAiba889c{qi6*e2HzkV|dC zCNBZ{xE_XK(~CQ1P1wq4FvUcWcd42-@uordDh{9wz)TNzMwuWr2PeC zPCK7P#N=J8{6n_a+oHgXkTQ`g<{K~FZ9cu%#U{`0vMsXmu?Ge-Uu47VoY@i87td)_ zM zJUiCr%n7HfWgyR^jKo?-o^&je0A#=J*Kr3Qtn;TzVP&j13Ed#w+uuCPyn&tDGH&?$zmJ=z#lCc004XB=i~Z7eZE zbH&d+dQTqkAc}6hou}xNo;{!wo}rWDXUat=pt^i|Fa8TWxDP1`eZNnZlta1sj%i(@ z^}H5akofH`v$)L#gW+7-r3Rm#7x%8Q5B|iFS762 z@QNK4@!#3zf`YjPG`BaXt9;SS%&KXcXsgR6!AzbheiMz)s`qdC*%_ussTTIEUBw=< zL>RnNSNb!$*1t3dn{&1+`VfoXPBuu2HfVYZ9IPt+yS6*&Co9wS2Qw%gX~>5zqR|Uu zC;h1E6zj^6FlnhGqf=g}ejg*MMkIiIdzeA(dyk{%nrv-CuE9K^lLw?hww6QBcMTsI zinDA1>;1(}+4`90>5IVA#;?yTPkOnTo3ofq^e#JR307U;gOi>cC4A@@yj2lD8$Gmg`yP71i>fr!TRpwWmY%ocRF)=%*a7r z911XD1!%3NhE^Nv{%g0Rb&%kvgZ#}|ORPt!WopJYZ=I$OGk4Yd%syJ>bDPJBxHGvU zEw?JsYXDb)?;H?V1leWNbstmXy$2Moyz8Nl0=^wB-+TBU=+R-WKS9A8UG8MEnTo7H z;zSyS9W%tQ6Y0$HsT+?q3LRDz_r)$I&;EX=eHCA>dC)L_ht&S`RWAnd^fHDy{|v}* zc+<8WL#=4Mi%+)}q}HXjW_zK{#!cLZx^aa}P@*TV#Y^lH4)zN94oIc~Wq%SSuLFh7 z!otF3LbAttn;TnUUL!o4Jq+eeRT@He(;-)3ay%DO_;Xa~&W4lg9*DECMp;jAG(cR1 zf?gkL@pZ9STW|Bddm1^dzXM6xL;+DS!s1{k1hV76b0B2qdWA0&5)U(CH2d-T2e;Zy@zFg-5m_RqkmysZd}Bvz>7 z278-oY_=NnNkKZHW;t>l<}UZB{HVmN-+iq0^e2RJys4!y7=L{Y%&KD=Kl6Ncq>NLt zd21{G`XKsz65|rwO@$cjpquyc`f$p|A0${rh#eRePki$&V=oy=QD^q*5^&@PcAYGW zaqm(nwMhLa8X7c=^!n-;iCA*Fx>W1hySoZ&p=zud83%O2lyEujP`eq$`7>7xR|61h zn711kuG`F4^WB`D&C7B9N@Sb5IX)`>qb7MRM)X$p)i^vNt2C+#e#vXSW#P&lnla`3 zMaSr*ai>t##($@gkUOkREY-6iwt39eYJe2vsvQR7wi=tS*G5 z7p?St&t#4W8)$8soBYJMmylpia9OuHJ>&HIM9Q;iFXq-u!58O+JPx?F=eOT-l_Nm) zTVYRa={ai^c{IyHOm22ho|?+Am$Qx?xvbT|{X%SS+s~IdxzdrwEzm<@zovzV~;2JOt+2d zMCUu(MM!og|B&V^7ef(GiyO)3#3L0dw&gWV1Vtao^?ximyQs#6x%VwFE%aIgt3`QL z4v1x(YeX>Hu;a@gZ;puW+m>vZ|G2y_P!eec9cwM*PlvLwLx+Y}CnpU7PoKEbpDE5{ zp>cl~X~WayAewx+@|6*U2ucIN<#{c6AgKSplCCGx~XEvW)Zij;zM$3RfJ1Vlv| zL8L)?Y@|qcODd8QQc8^!keGCrbjjFYV=%V&^ZmX2xr@cMJ?A{<+~>aUQxMp<&TX=x zcl^yStM?_G_jNltk8qYfC2I|p@ocaEW?bU|(IRM>6 zt+Hlk4B_^bPIBt&zBAL2chs@=N|129v54SJtpf~5# zWnjCRsrFrxJB81f+L1<<-0ktJ(410+Ri>^A-IY$jv~3zMe=Q5egXdK#K>qjvy==2T z^kdMbpS#2t(`R{a4y zPfKJ)F7d1*99J)*;%C%m6OKZ0&UMLFLH-nR|0Ve^(g8=Z#cWiV-1>-=^s3P2)QIHC z3J~o%E-;6v0|ui-RBjt-lkqp%&EuZr{(i}r4ZTXSx4VUy{_)*)t#k*l0Ge&=sZiMd z-4!*+MFvQ(&0BAoz8jj7adY&&!PPK2G^0h&uh2pc6vKc-Qd%&msM1APND`xcrY|#31H=#Q8_+X zv8YuwJ=>r_7JOq!dv3LNMCW5R3wSe5mg%kZR-zjK)9xG$8y09q>8nmwS}|sQjf_!; zjJQ=|anmzvsgmREnUX}CBGqG`cGn|n*Hk&OR={85QDqM}>OC<+VSZ#D-RIb=*`gnE z#tQ|c_G(aQ7-#zj4oEj7d9YD{4;UH&qm2^R>D{RrhE~es&SUT5>)ISkfKK9ODp6E} z5{>^x;bW5x%*UA(g<-qflTmX6nxrXW-`e?2sYV@+uSOAavb#xX?01=-Lb^%CBec3k z+tdaP^Q2=p0mNWkJZEPaXbpke>bTpqBGQKW>c6=e0z8c9TX!aPQBhvneAM(P^Oq{e zYtMB#cI>af!VAEz)W65;NKv!SRlbo=f>_t3q ztYYpg4qW?X?mD=c4mSwv&F%bIIz~Bbj3het3`mKL=xf0H7I^?y-Nt{pZ&fO^6v27S zfmYs%Nvd0Q`r04qkReL#A;DkubDvq8@MbDYVg!f^7~BDm5tA{h3+ac4+NK4ji(-E5 zc_E&?fy28Xv+tVmw^k&2GFGA2Ti6Q?{P#g_6gvnt*kG`&^2i6+b)8Sk`i@D}Wm9w^ zoO!HF=Qbh2tZ(Erv5p_{{2~Z&q*s@HAZ4_HCR};HU`JxiV#eY+z;Y z`n`M$`Sx9{Xfzs`EnfupQ`y1>^4atT%1)g#GCtf@{a2T;Hh{{gQT>BYJl)Kw%4WB= zU(Npz)cw+f$A)y2Qm48%0jx$3k6G%Qlo%(5ZK2)`wN7NQn8T}IOsbygLSahHp^6v$ zG1)3)ORa_{+^8Vc)gesnuSWXKmkG=MbMXJFwBkp{Dv9odFES|&iZ3*uo!v)iekT=V zRGgXQu-;+-lu@4nM{|LvuB$v!$4q1ZU|U1^Dqax&u1N*$puqY= z0k0Bw>q4}IeBo2b;rXFH<(ri4xkF0 z$zlK~%km114eC#Kt~vH?O|Y3k`3HPC9tm0x-0ooN0ewJ$6Kc3b78o||^s2=R&wWH2 zoP~02cKmhOx$7x6M7lkFE`XD)VmQ5p{weGDfPd7D@U3+`z}62StMLE?*^_=5bi6m?`GV-SW`}tq?vE=`_wk55xl2Ap#KqbU2z zV4fdb^LO%((8{5`f{ zZXC9c$5)vbeJL{sxb2n5$4PxAzd+#3uPZhMn2($Yy$a~)1+HVshm7MFEyFGh+{Qkt zC;1=Uqp{gK|5Bxb=3ZDxEE=nPOf`F~Nrwf-yZ{lV*3gKS5_vwZTyY6VeTY$%Iyw>( zuZn(!W@grh_N%*FSpg*xB1p!9QUwm}QTE}z2QmVxkT$d}BsV{tT=95s*YUgbvez5Va|WP?Ax=*j&2GH*NuG@q#DLHgpN z7Zr{HE|Qwzp)g$C*z|$FzC}rM^Ep6r83;KMk%Idj5k3q`&w7p>sp z$nR0P-Oh1X4~!90u(S3JdK#)aD@tn<5^sKITz>ely!?8&b8Yh!yt&1!opn<) zmgbvSAhE-$4aRU&(OSVXq*wIw%8?Cf6r8W4q_vjgDVV^@HO^q3s-rrYjsZHuF~dn- zIIJrLihIGQTtvDjy&e#U1NclGjLM`|;$U1qb=j)ed;bHMpwQ)T-$K)t05SMa0E#gV z?q+C>nfE0kC))TsBZkU2sz!JT0G`WSpxK-5wv7L^!m~*W0FMBz!LRpJ7_}?NXpuYi z{S&BDLWym@eFQr&BdDNEC6)p@ER6lT+jw!CDfYi_d?mX{pUr^mlYD~!--<9YC=&@E z`SiNDOI6T04w5hJ^32cR#>KAM;z<}57$(PT1iYuCU)qw*>6V&&y(0ATtxx{C^YQSH zyHa-35>RoTE(xW2g7TXlfZae&9TK2G01SlFhQ*$CHSKMObvma;vZYquO+6Yt^?%Jl zncd!2j}wFP4xYfciiV$obgkao z!On!KqD#{w%Y>)UecS=4?oU$s5&%TY1^Ix z=(((%TRc>o#ZI;xXf^~!L6@!}oMWT%`!7kFBHkZGC!t(LgUh6fj(IyaFbH6NvghyO z>b-OHQVnuC$*)r-6V5Rc3b;Ix5%UbDX%)dx|JqAu&MW8V-gcybb*6h3=aA${oTSH{ zR#uVNpP#wWH5g_VhQo#ab8`5S_XRC#EmKLQh#LvQxmZS0)0TLSTDV_ET&XJYeD|7VMqA{^ zjH7@18$LlJl1Hn(TWtAG1YiZjM9RVLP>0;t!z|}fYGLHcUmRe4eQ&P6I$1#qNF8ZQ z7@+dq~l987GMjMy4m^u6W zef#W|5$5IVy`$-nsEa}>|3J(`v}x!#r{&Glu%hd|PR#TJE%KjCTmOrcGdy4t^j&}R z;^mPeZ61qkGq-}0%}GoNJ^Ew~-gy}5VLOBr_Ut@=esgE*RrRH$5%n%KutJ#1X#`*p z{4E6^niT=LKz0njIxeqs9{+LSWT*pEiPg9PX`RMz44@#=3Qy;6q|rw+;AfDx-vK%R zfI)0$2_Vk?K!D(nPTG$y=SIfHM3>sLlONVq%xC0Fy%LAnZT<>a4Gjo2OnUt=Y7X$) z3hIa0!3+@9G%#@~sMb#B`+I0=yr{P)AjeBeTT3+p z2h0@l+P=PoTbRgWAgyFQCHlx#b8V7Z)<+$QeHa=#xym|xOU;yV6f)FdP*l`(7KsfA zD2#p!lwXlkUg9Dm{SzT2g5d1Wo}(CadV3*!Q*rm?pIhqFP~GYgBJ-KH5w~NpPf}T_ zbp|c%`(OW~={f3+)VypgZsyH%sjU2shlDxWI6oB6mc6xVxcYee*u2S1Y$xjO$Ml~zLI$+k{&vJpG zfIThIpoRo6*Ty6DSe0w(AXK0XtxNx)fPF{zSKk9guF&D7WNST8Cnu+mDTIZcF<@MZ z?f;*;p}}qfmRS&f12(vs8t>cnS)-Kslfpc0Z3o<=iovy*&|hyIYi0G3ceoHBXk6R* zNP^gB+5JsjbkyAwv#gJsSStBY)|aU~nT&QK*eSsaE0Gmy@{2OhLEskBQ8wbZ5{J6X z?OKiYrN6=bn|Hi=+$&)Zc=q^|G)mp{SFLV42xA+qkd*a^NWa^!^raI+hWd2NspEhE z7->b_WURWU5+5r?XIkB z*+1N+p7TNb(rS}$#)#S%CLVM3bZbn=6Z8MaCh5T$CpZut|K{U~^WE{CXv(WR`B3m( z(4lHF*PjFe!^yIy4@aaf*PcK1>=7ArEnRB!bwq zv`qt|;TyI&CrF4BUwd}Eei&b6!5PxTPTZx<2&XHk=InNQD{b*x67d>KH9gJh&d&<$Jw^hEy5iBB!>)g-MYprXv2Gph_E@*=_3Jq-AJd9cSFGTuggGpY^DCeXPMB!pjfoGcW2=z7A8GWyX}`RQJn7M>L)7mh5rIMWkzJKCc4Y)ARotRHf(%ZYk0 z$7;CHtHLF6?7XTMB>pR>VaQ}M$rg;0mGvvVzaQlvi?qWeUYfQeu>f+$lrZ(~xFWxL>WcvXc@+H! zh|QN*MUS-<0XAkh7NCEfpHCcA=z6d4+WYwWZm^baq|*KuabjtYCsSJ7Ru2Brf#gxZ zVjQ=;YoY}6Pq36LKqOr5ACrSooto~P`-vNPS`yj2%c*?M<$1x&L_`1-I{mT@=_rdT zJ>ri<5j+vc!JxWo&5eg35RFYoXu!bo-^MCAO>75H-Z2$M_hwVEKal?Nh@~h;Yne>~ zS_b1b({qzwFt#^&GgZ#wRl!3jcG()*K;n0+Rq@Y3*3Rzg~X!lbt4^Zh>(%wI&LZqQNg zKvQpbM)>!a=3J^)V>FLr?)9<`U*hl&RUXamURbitOde3;qNGGA#X}sp_&`!prUxJY zAOO8GK;YGLRyR3+3o$wAAl z#T~bAu=Rx!MWLfFjFvA zbq5Bfc=mUIYi$dSEM)wvlC)Qh&)YztzC8jf@kEsYhvAKh1(HoB)6n@0x3NAyy~Lod zTEX4Bax94LRR0xeII4QG#n)A{RwcBDTxC5z4V zoPakCwYI8CeU<=r=w`ri*KydT#qgn4`%wz)hQi`36g^}%#^kidVIMydnJIoH-U(~2 zHsVLo>}+Ta=@$K($aCIVt4DD<#SRWS?6xyA!&*KBAkSig*hh9vU(L;NzDQ3UhbnXe z2p{+tZYCiGWATmV+#LCGgLFMyWnE5^0O~ABY~EO3Zg_G#G4wf5;d~9M8%yW3R-y|Q zYndTOSp`y_!6xEM3cT?4Ok^9K6;PNUu|Bn9HtW~V=gY!# zF{OymuJXS11?pU_=YVqf<~n%9KHWH-VH?svK>PCb?eTY(IF0)E-J=gUX~q{`{=Iqj z!DFM>)$^XiPsnYhg|+-2)#y6ft@hHbbM;UQ{?8tO=_QM1CyX#9BAS0gq4>@X>ee}C zt4RDG_n;tV_#A!jq~>&cud~qvDy&1O#N(e^v&702jZU%UEvds`CO9UQi)mz8fEFqPI(*gv5GHhnzpR`&{%kTtaP zp+o9<>IchBhM#zx-3(-vL4cvZsGcz}*{-ra?pwp>>6h2kTb=cwrJQ2~P zD2~s#AdU(m(W{-#+frhIJvUX!674*oQC4A1B*UzR7LOTUeB{#cpD(&fF~S{+(g`N* zwY9d8zw;w-D{@CUJkz=FPQDjGi72acS1B*p9LrqGtS z9xbf|-3Fi%Xb$X0&b2@c_b4E=_|1L0TLM?)KwnRiLO)LqNY$L0>9uZN%2w?_?dR|E zMGJWJLPwPOfmIvpl)<|z0o&Gk?PnQ6tIh_*_nC+gdWvCV4ICAr7Wcq%DV{Gn3nts3 zKgLhehr#PMx~9Mw4WNWovP1IZsZRgYYB$qqHRe9Pu28BMRRuMvcE4paAW)_kNn<^Tb}7CjVbjPT5_Xg5H1k$-y_{YXo3a6xf2>Dpha3D< zxO$-c_p|{)@-`@H*c<3hJsRz(;Q{;&yA()Di8_YkAFo;h>h3fqT3Nk%Ru5R;+E`Eo zJ`NyOrUb->X<9$#PC*B|s7tAYGOpF#$ME=c2D5JRu8=j(dZMU^ueha6krrmAcYy(4sRC;6CXecy}6F~r0?8pjwwUwglLbeWr zt>>0P!#He7A=9dpuUx&Yo<-cWfIVyl$}J&9hzUf`mf!;Hk9iO2UyQPP7y0k?B*y06}g03Eh#y9c#W_y5; z`>vCz$3K8WUD;yX3RJr0{yV_X8v4(Oz1L;x`KxPOohJ`y-m8FaBl9085GWjd%fK+R zl57bO^Ia$a{376H-*NI+xR~3HmYsKX8$CE{?eHrgLx;CIo);ejb3%1nF$u3Tf^ADO zD+&sD+zJ$mRVtY%?al=X>yrS*Em_J_yEFgh#_?9!&*GYJXwIjO(7_~N#f-m6(35Bv zd73+`g4h_Qd9y2Eua6rrEx$X&FdHM7rA(#e)R=58s4rapo^RGodR`+nu45c@|nk2d#d4q+BW{@G4>IcO3 z#B_o>X95sS)XoE*iAaD6MTYoz6Ri`?kW2tLua5X)oWzOA#n*sz*^tkFpMCN?zX@cr zLNI*5T#3aYVwgsbe#7e%Ylqi(%MBN~loRmfiKp<1)N+3l*rc3-jXNrhAI!i0!bba_a+xIJfV*O?SLLDboVMK zv5Z|CD$FpXr@#3&GM-hM*_SBeY}m>+&@->4>d57HBMs1Dc6V%T=rTX@Z~rnF+mcNR zK>`#+U;?C*fy+xc>n*)knU#nXRw?m1${)gFvj!%``p<$Vm34`WNa$`bw{H3 zn85cyqdFm~zGW2ZwR|{Z4RiOz7Dh^r$a`GQ1#T`IT-B=?h!cCXiS4@9asE3+pYw}| zz^{OVaHfSB5grgcIU4^StQ5X0yQ8gvX{beQ>|S421ek1|)V<=TEIn4BAmGr-_uc{i za~+yEG!c=z7@$?>k&;3ws;h$Wf!oTS#n$px!ZHf0NfkiT%4%D@)qvh+Yxb-{$PJ*L zPRIxwv~w@8wsrI^Zv7z2BguUox+m(s6<#+?^^6oSgS9=gOE%eJ@V)}N#Uqd-uRP__ zC!)0?EwbITTvE<3R+mOWb~ZBq1-?SVCWOcD)?7EPu~Px zkfF5T@ec6z-Zdq$bwFXS`74mJ9w_u>_QBsO!>dn+h@@eyi&}l3S516`9(++-SnUK{ zUg4iqmJYQz5@fbWZ8s3Fr2Vvy%_apX7eQOq^hTS)La^U^4qwSKLBT^Zb z2G?VD#p07K{@RM%@u+GIfeT2<}E z0^76LrCjI$O3^yGX;+?nLrJr1VXmhXoNnhoR1n#l(dwJHn=P^tb1`sjN(7oN-u+u8 zG>6MJlKZ@2Fg2)=5xjR3hZB|PN?RkC2NcyQ-Z0SaB4r6JptFg&M;_s!mv-|?;PaLELo??WB9x!hk zlYIOb7Z+B%Jm^I|>LoRl_^GrQka&l{3gPa9Tx{)HIs3UT2i3KNv-eU`E7w<0*a^=0 z&8^ozC%-Lfyn{7V#(w{2ZXjb8T&Q&^x&OtY<7{R!htU6G`Bocm!D1PR}K{wLRn(XxB3>EL_hRkHBqXBKpB$ia4L@`GDP4y zDvWb5Bp16O^r2~BC!G#1&z9+IKjH`!=_~s#$(fgy3JTz83AFMO)*PNH?mF4T;ynk z7C~8DXb2pKD&7N3w~O}w>>`aq54gLn^#nbaC6_tH!Qzs-SLJBc?wRWvE+OdYKJ-b1 zlCz;cL(r2y2c!`SP_qi2=H$v;*rMF4t+hkqFM@`jDtyKXRk~Gg)C?QsjDsJP@WWJI zDAM5<+Z6G^g+9tjWMpLAD!PQ*QTGJpSo5HW+X~Y1aHAV{dL%}M8wO!h^!&S|_g`fl z^13rTAjUbU5rcFo`)`|4G=5;ch#2awW1pPTkc{9NXK04QKEPk3GDJgpWoIWR(>N}| z_)SjF66kCTzk48T&7Yf_urUmZp9ZZOag;YcB>d$s|NC2?BV>@!k_P2l8Er;Y zT9w~6VYryk)VM6d)nRJa>Ih|NY^kp~l$rAe^n%qOz6-b1YgH?*f2Dyqu=A1k;&?d3=B#=5 zWahK^!yt6SO3Q#t&P-!J$y;-UgM%32)QI-Zm;si9LyuJNqvA#I+~5hl>N*6koE!}b zu91DYg;}*_b|d!O1M3b9kHBNHh_IApl*ona2=V_;5LL{dycW%q%Iq$7rz5-DVTO_P z8#?lK&hJ{5|NL7OWZ*6~yavDU}f>Zu0o@&?JR3K^RXSYtHVUh{ z6&2~EJfid;4we{s1AS=t?v62K8XTr@Gf8A_Xq3{m`z)1Rk-Fx>>F^d+#(is_=Sbo{ z%0*()Wvw^_h{G6c=vign9%96>5JI|rpd{TvN4KOE)o5 z+aD|BqS?7Mvfb9)9~m2RpC{~mi>XsCOCL#0ZR+{}%!8Ha44zho!52fuGQpGS%0v($ zsKG4JV%;lJhlAtU>JJ4R3Wkg-d@%l}0zyRr@X^XeKCrzO#Ls+~i<{fFtGzwnu;yp5 z43GG_I^V&Si~{VSLtadcz6mJ7Igyx{R8RV|!p-^3w^H3NQY?%beO3JyCmw$&At*e= z65;3?P6ma;eo|{oWh|^Mz0A7fI3Qt{^N92E`p)B-E;K-szsH?}L7^OqD`u%tM?Zag zv?3`w2;hJbb8+w#dKEJl5}BS&F(xonP5G|4vMinS1EOS)2qZ$l&o=zk4oGKRw zNO-T`ERwBKzAf7oi>IZ$a6VU&Y|u&=qTCrCT%BE2ozIbTHfiwiFg@0X^DFzF3%SHJ zGC%s+b#bV{G3?lTYQ4rh3R3BR1s{b;yNH&{O+>!ePzhjU?!`6>qCe7N-9`PhNa&?{ ze9g@{;)7VmKS|Zh#ckGo$Q=V&Fzl$bcee!B=n37R7~Z|W1&X|NEi-Dh@$WY?e8=Zh zJ?I82FG8rM&zs(7kwJ!IffGux@$={V?qj8ZG}CAIM;W*xx6KMu9g<-|@U4coWQgJv zKL>6iDD=D|kA#$Xl-o3cD+k0lEiO`)Z2FRc9R9~90DjJhUx zxvL;Wb)L=7baEAJPBP(h?(tMn*c;pL zY-T%kxqa!J5{v7@(KC2194aXE6URU_?l+BsZJpVJ%Dv9|dgI=Xl3-7SU`p)6-FJ$p zo=!>{2xBCLA@Ml?UTgX z`(k)&6$5A0j7AS@6;%(A5EMTO70&=UNsqG*#WvjrRx#1!-XBgn=t8f*J< zC;Q}{6eBv2)I(Q7IBBQcD}Mch@*$2mla&U_CIp;L*a#azD!b>9^kc25lhUc^JPr7x_;$xyvE_wko; zV?zRNk@x2hb9bu;%SwJ<^l@pS_ebX}W#f%oxkEKl2%~>=6l1QX@1EG*W7OE-(Rx&P zgS_a(bjep5mYmLaR~UE&%*6%OJ7$sLP2O@|h4ov0G3?Mft}kNqmiwUZ{6oisZM@Og=9eFTMv>te?2Z@I_rR-5<|7kMZwg=B87PVpW{NajWA3sP_*m zP~h%Dn1#_)u2{u^H5|qyPH}73=0GEt>N*wrN;d}(sA+piYpbgoF>p`IFJQykqNB-M zh!m{TLKrOVuX-LC-XwrdcHAz9PJYe=|AC8%DC2gc(HO61?@x^+H3Zp5jBejSliup>_|RA(9O&NVT7-?T*IPN1s>clJbDg(| z0MBQLVF<8-86F~X7>-H?UlfWBRS!y>BvPT1HSpZIOd>wN`*4VR?A>+zA#x`hi>fSI zsv=6i9M&zKR=s{V2SXO(t=L2viNgr$H$;A{>}uPgJf6P`~ zdbsr@Hv5r@%X;+u3n_Kg3@(+k$ENRR#zJ(brB;>oUikA6>fs8s>om8_ck7s*p7n(j z;+-{s6t(bRMb)!4mXEt0KKi?N&~C}u=-yQN-;3=yGA&!KXD7$QHXCcTOFGW#VYh|a zR>zrHK61(?-lVUHK{Umjr9w(RmgEm@aTSPWH`p#w@9`ZFZt4l@TBd&4xdsYoiK_}0} z5%y?@Vw)0*4<0Y&CTs-ZQed#%oz1(gh1Xu{VJI!g_@kJt8pRfefTIthf{II)tk5Ia zM)l?)jD{c~!bEU9jZ<@GC5)q8pS~{-QOeJ`{E7CA>gAdaL)8RK=h_-czx5n_`xuZx ztBXsj^yeYqQ8EA62LHM$d{3~FIvS(G_PNv3hi)$aiQO&Y_Cvfg=p0)L`S2nJCN8iZ zZ=ki7%c|&zqaF-Ot=x~BK4U)m)QXE3Q(~$!qR!pCv$(J-8)CAKS!L}r9KyREnJm35 ze-4H8ek^oTE@;Nq2EOQ7!uA!$uI5L@Z90>d6A|`#^%WaEE?`R`Y+?;EG&t@&&}W$8 z!QaP_#P3Is-SpMFYOdP;Sp{3Og>?V^_qTpHR^u1t?OWz!mqP2`7)X?51#hr(O9x+` zvbvY9dB+-#2pNnBT}?%$z-m6WhJq&1q570s;W!1oB=<${VW;Qw6)qe8hUJJl8rqS> zzqyZ$Q2kN{$(7^vl=lzfaR#dbz0(wQn?6h+<|ydvGIz^Q=peZB0r|iCvAAsiX>+iW zyHC;B^phwYVAK@}frt-n{@v^u<@G*o2nZ&>%IY)xve3cpcKCkO^~$AV`N#1ORD|c< zyO!D4*ZoT<`t};CmLRcp@$sDqW0wc1n|VW0*89$Ki6W!DeAs9 zeY0~iJJtbLQkkf+t!qS`_%aOc7Pa&zRKD8qz5kJPJ}bwCV5DLYS`})-V*_xq81IH` zQcQ{Rh>NWF8}8{%@!3F>F3Z@w18Lxbx_wx)S{)9IV!`^8i(Qby;DtmdJi;x8t>YHn(cTfmi>1 z*y#H~sXQ^y4~#;S%A!hbiqe&k@F#U$A*yGZpY#QknApSj(QgKaQhn>_2s|^Pz7JM= zh2<%bNNCZsx~1?ISoy1AK@nwTF}V>(p@-8l(SP4dos|3=sj5pi5mR@M8YH!G# z6yAY`oN1tfRh{Cr!O}y~MLN=i`-REQh80ppq5k~yjSrU-L8C#LTnJyy|!tt+<<84J(#s${JtJe=I z$aPqrn_EI(w*_k4yYlGAE=$BW3e-G%B_?LQl_~4*vU-u3xoKgRV8du-{XB~FwaR7< zp^9xc3wGRn%{VfI^_J&~2zT-zEjx9{FUWMdv(w4rtm?XIT3R@b&)F(2;=c9%@@Qow z;`tQTCa0Y^cy%1j-w{o0rgBWl6bRHCA~HnyCwti4Ez}(@tjur86h!)IocBnhU~Of= zx^>(9L%h1buE+k1R=PzHTnD%;F=&$Nf#`s>ArDB2(TL)iRVIMcF%Sv z`k~>YEIZ8Fs@+D$g5$}gS%!D0ZiH=laTp1j}RQ_v}p=3+%*R`A&=9aK|6p(yInHbs1LjUz8_7iMSMtE|-$;Dn;#( z=;`T=(#iw6pB)k*!rzOad%OhkcU`&f1aEKI7`A0(7jb94xcrs(xc$bQn`)i+lK)Fi zEDJs>@;ZH1c%`L$R!-KkPPfr=O?mjU&u3_%r{igVbDU0*`M1^c^_z#8!ocsR4h_FX z#>GiZO#Hg4$FKjTvicHf0gswO`sqi!Gv=bE>gGGuenS+ec_GLb|487y5*95^aWqVb z$!2Rz2+O*>HEb607EkvbeIprXTK>5z^Y_u;%UD<1uWtMGsCTbhHp9saUmBl&)nY;~ z1wNQNauUJ9z?lgSN3t?Xo^>y_EnQk0@MG|;)Kl^!;Z2av;3*@Wv?nULq~zl4)AI7x zeTUG?Mm}&jn6o2TpeU;@vcJFISVmBq_${a?kcOO~Fb>tPfi>0(jjpKmfDq7LgvWs6>>3eWirw+vW>>w@r@}lL4I}@ zZP@492!6MSj^KveespR16mL3)52fJ}?@ zrR_yTi37f8gtgGePf{5fivAb9k*MOUOd<%iQgYwD(LQG0BN07%e*CVeK{((bDXCIP zQ83tXRz{pXB-i4%`|ODKe3O+NFM7!me+iH2IP>>B;l22nUTA2)uaOFtOdjMPfot?t_l<40 z8^iz0s$PS?7Z*n=En1S0kPdyjb0_GRox|dEf%_C5bGh|xCcY!yO>Wp+tldpc7|hqW zP^Y@`kaSQV^HXKzYBuBB?{!oV11~{u;r)qqhs^#6$JkRq)N>*41rdjC(yIO(KdIY$ z)*j^9;(}EqLq)wASUdQV2MXg3i}8rLIiwu!OBV(`qC`P~4Do|0hi~XYE^ri%a6Otf z%PvfaE7g(H$||X-@V-=hgPbcvuUSWToLe1dxMxar{ob)%!Z>EFeC=)ZpI-8;^L_!X ztgbe=UcoMj8DhWwhP3<4ehp0g%DD@;jUv2%Y~LGg{`1ItRmo54L|wN`Q$@W;x#XG6 zmXQo7<5xT}5kr%M9u^cw79hLJU^z+nAbKt#jx;~JV#Zp$xGXR?YanE}5pp}O%Y8pT zve_WYzkl#db@e>)cdhG||AzrCz?0_VjwNsf17OwYoz=$)iY8|kv2-g^zVC{nXBWpp zlv97rs68|5QMQx0=`!+g%BfKWq=syD_r4?NPqiN;OP&Q@M(x?B&HwHw?2jUSg zZf_4M&JeG?vx6K>$l#{uUfn&6*SUUt^z*1jdY))lQePn?BYF(9Y4gdk>w%hN=c`6WdXjOJXSi|C zOSiHwPYi!55~G=0nyLQbH_1?BVB?Q6Q3e;CR;?&(`N=N&d;uNl134oF-Us%rSy)J( z%3+;_0_G7$>8`Z5OF91h)pLp_f(-!*q!Q3dXS}*Kq|4;XDS*l`9{J9;t>3I9@vM&w zig;CCA&9#*D0b3_qAa^~ux0~(PquKbZo-eDlfAc7?AXj(qj45&BaV{lGK~SjO2mN( z@;$Ha7uRd6UB2Zn9sv+CsXaG4dyqsp^}5A#^Kf#;qf=H^ZZXJKxj6Ga0ONKUH(Imj&zY zJ||M!ERE9ax}T)g;6rnd84d`}x0@VcFUp`c6WcytVyXWQ+g*pQEbCy)O1#NF;7+ zKm^I3s>wWc+c?w>Sh6k3ylpbDMH&j&{pue^UEiY$XkC;2qYR zBMd|BqUZ-6*rB>XR*Og?wJ#(0WA6)4Lr4N&dp9R{+^rx$ZB1X*eqG%#BLe<(o)~J@ IKDLecKUd8F8#4vbt^n0Gi^*4ub06T(<-ORzAr|ifMSQpZNoGahBS8z27r>9#VQL=Dr1; z!t!iF;i_Vj;^M-&IK!0$#jqmoHd3D=BBH7iN?D2qTYZsi6oZ&yBEvFpGpH1*q_Gpx zXV};qfTAa!3w)LD@;2Hw^}4NpT>K6K`dfjF*jq!{}w68P^jAU@Wh+S#= z;j-!a;Tn%*h=9?E`Ad0YN_de34cLHxUymie|8I+_Q7{CdG+;Y)mp);a{?f~Pz>f^V zm-+MljOC-90F90b`s(m|H1S-&gUR0m7-y-FQJ-Pl(Sy7f?{T_q$Q$=QUYubZVCw2| zcQY4_KmsGv+vZsF+B%|20`Vp=Q^R#-lzw5s1o1ffg_753%MtT9ZOR)r^fcQH`D6ut z{dL;FtkY<6cd}%!)nFlj-^&@e6OwV?_i!;F639g6JUs~KeCaopfhXglBmGTIbG(Y+ zs#`J>HdlA_Ot)pZR=36VU{bS2YbKA&?fGo~k2c=6{$ho)e!IgOvO4HyOgZOi5aIc^ zfcxcK!07Yy@0|NxT298IH!5i6{CN@T;}1268h-SE0y-MtUo<{aEazMY4eymR<;F3dGgbVXc=uTrPjBdw$58GT?D7d997`etH`SMSVz?p_%=4_5|;<#}vK<7vB%8w4^zdJ*b1Cz~C-W*SSu_b^Z7d0Tf z65~__3EPCS>{u1S1=8BTU!cB&kD1$S_uvq*GqQ2EaX{G2=kOgZ3`mS4*!(gcw1w{R zT_o8nm-K#iIb-d9bFD@A`4*e|($2)(g&#Habprl>9}h0r`o7q}6n4&>!y}}n3-paZ z3HTXcQ5Z}Y%=S1h_WJxlTp;W4t5{Od+6Bi%2b6x;FK1a0!wX#N7uu~NctT>{9{b|{ z{1KDC*L%~+MW!D|2CS!Cx~Kf}^bGyigwCCK6MNU&oI0)UZ(_xKL5%x{`B!$vY#EVUTuYc?hE@n2ryul>kA3& zeH=^+@P9@l=XPJ3#BH>GYOR7nKU*r>E4z^Chd}irIZK+>Lrc;2RH$Wjnc?|E4$=Kpl7+v~v4!)4PSl*Lgl!f!)C zanr_Cv;AICQ!Op56%A|r)@n%R1yE|fv zo9o^q&ixT`Wf$9HbZ!FgzdM9HnSD-zxidDuE=n~p=~ec&y$*je_Vj*)g{QfKQHrqclxl8tsGa{ zWk2ToSMjr6S~iFE`1SyVe6vg;LzSA`*JluRm5jCUUExL@7Z*3EphzMjj+6slc%%UD z-A@7{w%~dGw7ozZyCeJN@L|meH90xg^*?L6LC=Tl>u6<>SCU^BYkQQu0nug8LmuDOK zckVe*FfGi5jmPB1G9Qg2U?Y(tp|A0OzK0`BClIh2F9{ttMHiOE`E~tqouBES+vCem z+o22B2SFA|#X)V}ZihNb-H+)~*QfSl3EqCLccqcO_poKq`xXuPLQq~X2xtVng@M{W zO0H0g<-JV_QH#%i`_MIXHzTG1l_HxhW7C8a`>j<&k!T}dTHj-KqwGKOm>Tr2Dm;~O zv%;)LoR=^E_sxHBax=RDxf)D;h0xBfHeF7_eEGco+#VxPLnr8|oTJ|5!RxOx*HS>R z7Arz#_3|H4kt<~M#tb!+Cv*fdyZ`{BH41n`t$&x>PH*Ofr|ze-lfxX7G=?HkCBh-& zEUd!OT)Yl{;s`0OFY$u#_}wo}9)CvMovzwmwNOjPWj+n20hNXRJs!<}f_&a+b%DW5 z*R_paKo>@Iw!|O$GMB}LoCr(n32&f0LamU&)bu~5tkU7;D7`d-tDOsC9*};$wfcRK zknh9UpRx7ysfs&$K}Va#Jc0d(huY0PH|&c*-<4UB`|s`c&k^XJydL0!7GwR+IRE{n zk>lwM<45xU=E}wWeBI-?MxotlP4I36-`L0YEuo`*kIc({dauM+=;bnMXJ}x(=l$&& zsMO_2%H_JhJ&*{!3KG7VKGFR2>61*q*ICnKGBr6&PO8W`^>?3vfFHK+ya6vCB+KpO z1#>@_FJ#Z^JwO;Sgs=v8`C-}zu~M15&C4JJcOvsM`<3i9&?o%I(6}oa)4y?>DF^q3 z9Y2etp*B1myFPDrx@|4CA3x*u)_kAY$}Y&hlU)%oy*=?cERSd3=rdbmAL31$@H@69 z7#+Z!g6Q?)AOu->U+6o`o!T4wnojNmvGbwNo4S0kOq%sO>VxJcmX@#!ynU{AN4h<$ zoiEAXdEY*eBU{M6y!lx1ji!;DQ=iv$tEcl4(*70wEHaTvuNC9+Wf)tzAL>%-1#Y#Y z#FhNNX6T*WbTBbjH8_y};|48qFcz zTK#7FN>@N&?4OQ_&TF+td@Aq^W2!rB@zH^q`Z01K(%LbHeBd&6qG0g*e-h5D`9qkjKHiLx zzaB*zgWqs2r2cp8&R3F~oRU}`sL_wEV;PYbvxTc6R@MBf?Eb%5B41d}Zf{0}(UJL? zDrOctw!Q41+waf+eh@|#Dlti*{}kjy3C;fgD)o=@QkMWVLFD3W)cTkI*XNbx+L(to zoa=miHr?2X#JAfb|NdWn*AIQ6Fsu1mJO&5?r0DH|%X6k;GWklUyn5|D?Sa301(rzv z{tf*6Iyu;^zy9ZMckv6$%UR?Tl#b*ZuaiN^Z%D1Hcj4W8{yUDe2Kc_70?y%buG7u> z>)&Nm&x?S=^F?KW^Yc8$3z5r#XY1U6_1QduVRS63`%rXY=Mx6Hp`#}g{Kr)Pp50_4 zXYnLib29RHfpi|8t>yY(40Hvs?A4ANIamAi7|uGqBj@|h{p!j>E(6|&Aq1Vf*Hg>2 zMhEX(12P{nwBBE2vkB_;X+T-{DlFpb?_XjY-P+DGUa00{f&mwN*5tm^Up522q@A{I zo`@un2R=J?=xsF{gl;G|8tcbB2y%VBD4Y2loOij*p{Xea_Ayy#k|NtQ$h6KdS|`b; zeDPjN?=$XGipXpHbyIYaJ`}lUdZS*|tnbtmxZGUvK%JP@=Gd+7ukho1zCQ~%+==w{>^USGTWl8jrG$nM#NXj~ z3h`gxDw2R7Qoj66d6~UXvt7gExM}j2WVM{DbNjpR!{%4xii?9^f(?ZAQ2TY;57M1V zQ$kR-z{2Jp%TD-?f^AR7rcJkR)wYY}SSR;ZWm7N@?-l~e(jzo4fg5$_+gh{vTqTYh z+zxcpKR@XDF!bSJ$kV%{{xdl1ELyJ7le?#htmC&%-&<}A@g6tR)@8*uPfxe9z}~I& z?!O&RSKjS&LxDAKljQrrzwKW(kkk=o{Z1z6+P(y?KxT4W-v1rubqBoiZ$=z@{UNOI zHR8yP`F`-|TVhagkl!z1N`A;TVf^89=k9i$i+|pS`9c9Nynq+w9^zVKa0pE+`nS%@ z4-P&wcKLVE*S&lFv!~O6F0T8c%lvv@?(!-&n=4%Z@1t%2N#JG^tD*Dr>nrP*1k2|g z<*9)N7Nrgc@0kg@yeK_v8-s)j%fPO@&GSc5^TPzv!XaZf4~91&U1US3%VyKq<;tI% zY|_C0!hr;3Rba1G8q&cu_U$!HbOrA=|Fd!X*|Mi)Mc(q!ATmoPmz#7H!qpxDn+et^ zExs`_Eqm}o**3(ih-63F`n%scE_Ah1>1`)9|4|W()U8Kfs7Ew zBkqpX0w=mexKvhA)X+P!jyZut2c5I?vBw^(vERhhps_^auVd#O(QL=}*wBXh-+a+{ ztS`=Vt@FI4F;mGh-5vg-d{yshIcQrk2>j)NY~(XBXFVXy&|P-^!5stN{{7)jy!9b? z2t|cyM_nZ-ybL0Y5Ky+i-T09H6ahTFO}!G4_h$}$%zPesFI}(tn@UcdC}YO^+}mNX zcq~EofO9mKYsQn12sX3TM*a0Iltq{Kw+F2TBe)E@UaP4L{G9LszR{?)_< zXH3~|ANfCIA4xU_zRh6HWV` zl7}H>ZiB7j4pp;2dA%6&DvvLk6&?f;ro-1mLnh6jXDkzT31gI0L=o7H%PAJvs6qHm zeLwsJ5}!#qXz=p(_k9zfL^8abri`ttt|EvsqO3?$c4^GU_3O|Nvm8f47?7mmh2S{B z+Wt){0+6U5_3q9m?#;M(JF+hWqJw}6q_B-DnH3Cn;GM9Drz zn(Cf7%L*)%NB@TV1MwO|I3X%LPt&r_gf16#%VXH0xfVy-&Q95Y=3GM8nT}%d$8(G+ z5KGh0Cr`uQ)?h)7ZmP{9k4ledpGJ1v`0e_TIXeSD1O@qsIGkFd-xl{jh0Up=oUQsv zQcUEVvM_I2wJg6)JCmoaZkB0BHCHxfrU|khw`OCI#@9;*QB95QcxNFij#7N;QIcdK z0os3h<1`pNQ(GC=B&p3T4XQN(QpMoJ7uNO zjL1nhIGOS%R6oqkv!3Md#auIMX9jW#c>)!%2+SSyJI?=RVBMlQmKQz1vEi{LR2z_N?xld+>D7~H+(<2s&|#xKEhJj@t{~TuM`GSF3643Biod2j zgxgF+RLKZt90wWPC{m@O_zWnTamkEx7;Z(h}C@$?M6C0S~j_gpPWJjn6&u2 zN9OE~QrzQQrK>B}T|6x5SI>lnJ5|&X0t3iYpvXSm1{^XCD+FO|TIvan6#V%uubxDn zHbTvP4hKHz5+j<$ZioOgsH;gkGJV~&KOn@k0=@ZJf`nBXg{anx6sOPr3a}yrhVC~J zHbaq6x+nO^?;wd)f`GN~qc}?6DD$Q5#NgK;2`N2!fAh8tO4jIl(lkhj`Tp@?P=X!D zxhs6M+vKlf&q-7W0kN}@OqF(!Q&4F&z!DeM`~$MMiW{h|cQ$a}rh_bWw;iI;%iwM% z`!lY-L1$vR9OBf55}q-YYaqxDDM^+KvBEsCt``3(8*OP@N)(w}`^#wS(a;UOg<~u4 z(_a@gTpwq1RjEvI0`1!~$1RkTo!Qnn`#0*AiSuqqX$yh=41)-=dx z$5PW+_k8L3^9Q@Rx>sZ2I*NT2P%7HcOx_NUjJ<^r=GvPfy~usFcMIi2EZ zyBq+GncCR$f&G_s^feTK^D$Pi+Qc|3AyyKINsfg1+$AohxZJ#x7$i0XOJVBwyVxRo ztMBY4g6{APSgNFLR!#&=f;fx!)2*id2RIHGd5}n?D{$HCjnXxN+gumP>!T}FJYH+& za*0Ow7&^ro1{`x*?q{=hws<*bRT-lG{-9EVRI`h)%=3NeRkP4K>T0bDK{Bk}&-PNm z85WtapD;a(8SO>j*`i13%&M)_<@|5VMjt*4tpZ@Rp55)MNf$D}nXytKm1Cr-eXi>pI!afJB$GQ`@eBZsMvQ2}zeF8C(s7oXY`X;z z@knWV_a~OQ6?~cQGZo5Xl+neK8z4*X0U~5Zh881iI?+p7PlJRk4*re%3CCRi;SVS(|8O?MYrBz3rMJ<>lA```Qp?0y2)4p^ZsXUu^cXuGAiO55b=@J=9 z!D_g>pZ1BA%dEfCd&t(?2f~Tw<>+3kJeF5BV5G=$*L zvN$)n6uh#-CaAz`lG{O(lxTaN*LKKAeI1-m$P-fIGy%~4JEWZ+$^rs00xzhDLaODY z>s%$N1(}J~DZi2rH-r~&k5|VF3GdjYsV%Jt39%nIj&z>X{3E(20P2#uh9_d?=F(U# zc{k>TTw>!gA1;TtGY^@kh+TB<-_$jHFI8^n25BZ*!@Py%T|$ok&bNNNM3djyZ^T7T z?KZ9G>}oV*)9mzGm*bqP5W_kX!8%bb8Z(+|av=JT%4{oFCnlnWix(^0$T|}i zHbk}+WmQ)@a~SI5j*v^F{tGG{SJ}Q&%o|t`Io+r`@5O3~39A)J8<2LAG#NOqIpPxD zE@0@<#?c~sPV#-Q^pZn}5QJyf;-tRT+=Z`Z+=i|hIO9}$mFYfi5Z_;o&GBh)jc6KG zAHfxC3J!1ya3$P8_8ioAATF1pLz#&v3O=W09=EX-iTZ)n>7ctbF)8H{g(-XcCQ*4ruZl@qW%LDR{wrPX*&<4;E zq-MS%$}>@upC@;u_rDQ8GRF8YSoG?(icgwb`mf()iI<#>gD#gT0 zO_-Q;IsWcc`-?utUIV;PiWX|3ri!wG%oe>Nul0rIO_W@9c=e}t!zL!j@9c72OLicD zauw20&Z8(_;`EkV?=)|Xq{5Yh5*-|WIr4?xFrPmx&!VK7&zg9puJwajFEK!el`vI!^OI}^8_u`lo}xZ{#Q7s4E%suv_X>#HIuAN{L^#tblK zBPVsd@xh7N{FSY66H=p$P!x2w;Wd)d#uS4V`MLG7=!L4NEs!eti%eYCBaCvEN+KT4 zKX%ouD{FGBJCy-uDTbalV7zbiAY@g>Y}K*MG#b`OPO?-QB1$=@%-SXMGWWb6K9|gl z^;HQnCnz*fk$@?XA`umlZIMN)qxIKfc#kgWh6HnhHLfr%81bpxr`Z(GJ0^+KT@0bg z2|`vqu5c0u7Lj z1n<-GRjBjn@!p{(<=>aS?_L}om2bWhHdVhmGLf<3{6f>AUusY@&R6;6~IPPDjU>_>`N zZhe&(X^?Q;BFXMIe4^8$u-m<_cVFOsWTlsOM;JgnG9ch_tnPq)HPCe}L-1xeG#Wtw|}p@Ek@9WY}y zpS#9DF_ERDNT<+Gqu4?7Mg0~%$#2@VZupIRmseF?yK2_-)W=4G-z+7|+UFvBcQGe> z2!YkUz)Z70BxPl-y4{NW7xn5@9cQz*0E(w#`viJ#h{C9TC@Q%6x{2gm?M z8WlAsa_RT#B{TWNrj)z%7q^(af&S7Z#2{u%uEwMaYw;@HE*mWqwwg%#wd+HJ#pKOt z*DJ0TCE=LrWhjuUdSLaLbpAFYncgmC>MKX3(6~-g>zc-AWAV?YUTGxE%HdeQao`Tx zP+gmoMFw=2xA`rYWarRlCVTep*zSD66I-*C!4gIaQ;jque2yOw=(~t*kA50!l)% zR9*liy{|hhnE6MglO_^5J|Z}{1&2fMG9YU$G}|h+t#m#HxtfhgkE^nwu+40NpsD^%gG-d5h!1&j3?__) zp-RHJZ7GE)BPlLMPDfdEGnnPcXRcekA4~Y)&*K#eqni1zrS)Og>9&z67=CS0nvlxj zq>}2CfRz6PC3B%c7F*gNpJB@j+0SxTjKNR+f)u~3&UX-H$1uu_FKpV`Vb+CR?hvu` zx3Ai}W5eV)QsF><75fT&exvTdmiJ#_2k%wua5(L^zIzGbky9K@+t#LV)ldCkKkSUT z;ONb*Po6?1uzIW5&-6R95+y4S+F#PZ$fJ#W?=&Ff+ zBh(H%LoY5~?TJuC&nsoT1f?kpL4J&JXv$K0aV_O7ht>`*PYve{iLAH_C>*T5@EJ0D z9OI-yUIdFEB&up>{Mw<3MsO04wIzL(uEDo3B3tZ>z=jRofi*&jXs0>T8G`q4XXro zv+5N0{=gZtBB>;!q&#j&c6}pV$7i08e<>VFEek}!vRzAC!kbVNbUZDy-fvQ6UCD@vTW)>f%<{60rYrQKpnRy-Ky%dBrlc24zyc%V;s93R3#a8(Lys3$J^@JL z(0U|VPT?5#r>*H;VLJki5$_oKy!n?Wp`)g-1vy>b@h*rhB5bFZZgDP=01@B2VtBpg z`i3h*RcvD^=}SYIo)^F2Kl#aOtW04u>vLYMVO|4X{vFl{bvcf=g=JVxPn|IAmK~e+ zczZFth4~-PYKsYW3Wd;%O{NR=>B+K6TIqB?Otib;jN4Nv2#24ZdFJwaRYBK&<3|Sr{ zbaus&Pvufy5eS#rld&DkrjKJFPdcm;%wOFYXp&F;o1vvJA67W_thbh!mh$~n56jF^ z^|3Cc#~J3;TST;|!EQ|K7UvPr~Qm`bSrF_wuoUj*V#)Od~0&QHw`w(J{8N<0cURCWUp4%Ew9|#_VH>e9#V>2%O%rYAkNvjAJ;foJp zkq2O#Oi)3U!ni;NaGJu{QdXgHOJO)CV<*{MhPUi#NI2Q>m;~W;h(fcqVPSbULHC6m(ri(e^O6D7^@>vOOVz$<9 zrIfPZ2|QdbESn#oL=S$dR!+uFofdi<+h1`ZX8N3E+Zdx2RZJEz9Za9uYTua5dMt7h zlMIkly(4w3)=!UcekmkFPT{ff?Jrbi>cvOYi7PB*Pa&6% z+FRGV%S`%~APfoM zr6iPzI+0|e)bkK=QiVMRuaS>jr80fCwzoJ^ljW02s%ltGuAZf$%Ipu9OTR1*U$AGZ zV);JHHn4!eyqHmM%AjXk;gkgyywK8J0!|bM}`#%{H zf&%S=_SHK`^bc@EL+||P&Hg4R^Tmm9d_3cjoDv_?k*_pP3f+WVwIr(zv3Z}XwmRgc z&7hd!E>j`oP5vk8q@<;_B!wvEPwm@>Agrk|x;72uW$ES4;xI9KC`t9UI-*@h+#d51 zBQ8v5NrikbWHWzZpRsFJ7TuYQB$QIJP%Ip>)FSgxLHuMqP>LDQWZB1FR#O&c!d~YY zW{TW0i%r%v)ef03bwp}31XI?VN4AV9uLI(d4Wg<$LbZXV#lzKJ6{W=!YBB@+oS+S3 zI7ia@zTBavCaC`POCsDnf)YDEZn9*s@DeQ_IkBP8i5M&86^VC`b``4)kBv$ozjc#D zF#%0Y2|02^Xeudcsz9vfKbiK)?-!b>sw}W5)c`0b*7>ZPY3Z7x@UVXPrx;44MVe0N z-qm$Pe&6wCO2B?!0MQEx)elIvnxwbhKsonz3LV*G-nw=nIna!CsLXL@kQ4RC#YW+~ zFmr;NzsXZ-uYKr$*^%K)w5ZVqJldayiY83MTfbTA@qXVeXSOPLzYHwQSEI-)C*miC zJVsi!73X=YAIXe!+fbpRxykpas~Hi1u}Y$>RR4Y1JU?V6BIe9XI)>^%>pL4vf(y9l zJn5_!Zu*=>P^nRh#0e5JsqU=Tru9fRFNS;pceM{xc8r#m$Z-BZ+>!9qf#db@F&!7m z*Q=2#t5CIJkZG9COz&ahMY2_{w&L5n#R>Tk+P&R#_&n+o3wRL$J{__}c zL#F7Rsd3Omt66+eT~A8%v=3dN`<)BKa4-{9A2-}D^NFHU=E{cz&zq$@e02|B)n2Zc za5N?%r;H1CEk1`OsxV12v^QMSO;%^_2bAHC$?^DObnf@I1qPY0sTu=kYcFt@NT-iI zxQijxyEr|@RF@SM(n7tE{}8qePJ`~mD*mJF^I&^vs5t$Ghsuq$nrQoros&29T;Y>` z>yLD=IIy7tLshv`V_G$_h4`^6G~)v0p31U2pUUsB{q83#mmijSV`ntAI`t%K=I6eE&)M#(3O%y@OuO5E)~ zt82Iz7V%H*Jb29)9C6KWM75TlA|_x)@gn)>HE;)eZ?2Z9V ztb1M;d_iCJ$lWd?w4JA#z2+rPeo~r$>191$Cedzmwb&%>yp&|~W*2&2n_N&hn6)g5 zp9VVC$Fs0Pc{mlbm!PLqE4Y&H8=)Wr^(cznrtYk)sty}$kxz+1E@-+vV~_wOt7~EA zd=qh;sc>)4Z6Fn^ceLUsERsgfm(UF_(Ra5asWt2*=eFHZYbSVQcK&3ac=BlSR4Rl- zUmoQFeH}fdiBncllMlu^xMs&_pN|JuozMqY{@f3Myy$674LL+tWzp!g=IX9@#NN3U zZmFFxA;**3X-E8{02He+OOlehQh19aJBk*PW<}9w?Jo9R?jJ)r;e|;!55VmEvXM8o zUn*<)$CIaqum@!O)|XJAE}(3>v@sr{kg2Yn2?BIve=ARY0<5a`G{X}-ETQH~(K2wa z_3JWPq2T1`T#XF6^Lbv=Hjqi>z!;7%x2v*#RnD zBd!t}0BkW+R(I7205Y^iGuCf^9!l zI54Z)bp<2veh~<|jJP$KxG*JkNCiu9q*P30r=O1l(!U+{$^uV#@ zGCVs-sMCOLBehVvThZy4400p}9-^#mE~>}wBW3t$O~T~SEKzNF+63~tG`ezVy(aoW zY4SXk9<4B3`5w;ilDcT2f5<=e0CYhZ5qV#>IY6Z@6IVOcU&i9(k{7uW$l{zPgN`M& ziunV?5y6I4SRB3~&4{if{<CEA{0DJuGTIFbnLOW-$LJ7Y1wELAY z0amn^Dyc{s)v56!{5a6Bv1>gm$#;{kxP=Zw&h)M=;nQFDJM64sx`boNR#m+ePiY17 z4JwgO6k@^$1gp%`^vDnxAyx_^X6qnwOZ;OfR~snmD>JON_e~Z~D?<_lE13JC@ngLq zABCzNf@vmNIALO`M5$oDCOwQpc)Fq`|Gik3$ zfZlS5@R(l&0&e%64Xbw}Q-~@j7KK+<)@4S=t2}A7 zGYIrL`x`aN5|{$c;ibH$uI`s_M5FrE3!iShZEmYNNo~cr${Z^8V$u*LEq^EvXbuby zVbG?Zxc@`ck!3>xb;_$Z%Qcptz*hY^8q`HK^h)8f-H$PtH`8JfN;E}hD3Gu}*@lUj z3+cZt8H1)MJ0Y`N;qHh#t?xn{ty?j6A}plD*0~n8#$>{FZD!kaN8nWB(rm04`~{o5k$enYb|fd@_fJ(Cy@;YRQvg%6ee{BG(mzVMSh!pF zP>XoZ#-yV2wECf9eLQknnH`KwMTS=5ETu3$Y%w}bmEeh*-CY6l-HfLm;52O1Ts{qn z%gV-K--a^pU$hwY=Z+gFu<{tX305gpqL8R>q~}UdQtgo*B-|_pb+v)MS?Y+^8eAGt zrZB9=lKQ80pr>dEG1%;$uZZ7nHsRLUMd8mlEg9*~m=w)MT(WjR!+~{-%3(t+LJc=Y zMWRgRWNre_Bj)Orgf_)D_~5;<1UZuIY~kG~>i+6|sN)Z@*q^y5EoXdYrNj8apc<<~ za7iy`k}npzkco9H#h=ZEF_mPqR+M0H17B)UdCZ>vd`X5AEO5i4ym9;c+$?8)IB0jc z)P6g&TzARhq_zsi9u&Q@{pZnT*y3(=hk{W|u?>I)Pv6zmj%7<;7&6Gp4nO)EHiFis zJxFIL-{tG0JJL8X>@vC`%`B@-B8MIoLDo$7Uk(8Y*sZafw> z)r|ks@;~^~sd+YaR-I-p%~Vnn5l?W6;6(LK;WtQH+L7Htf*dW^kIq~sCf|z)S}Y(< z-4pBi)<6Xa=xR2?g#_m(C#8fCPrmwqOux4?ZPo47nueArPI|Cc|51hBWV6;8X*$R& zZRV1_!(n_NE;7jbk@VlG@wk08$$qH@oV9G?iK(&WHcG1nB(_hcCg>}=I#_S@Fgs^) zNyr{3VL)k>{9bL3`A;aph`ihD)_7t|L9cX89)}ndy0Bxrxlju=0LN!Bcfy6qC`MGv zBX@53ITzZ;q1nF*E;WiDBc-s-XzLawsG#M7QF8zxJ?1{WETh_9M)z-YO5ub-{TmtI z+J57i;9;VFLc9*_I#CqSTg%QQB1hL0aA^0XQ!L^*T7*7#^pRNZm{&svdhkG zKiRH-g2al4{-}wE?AHl3qR|)T%NeB_9&7p9zJv0PD+v0t$(Y>FY z?JY&)=7sM~izFljXt&QekM{#Z2Q|5gd1#v;xn-^y1r|eva)SwFJA)6%P@HC-e9}ZV zs94ku%M>+VM>)*>`QrUxqgW-7(lmG5C(2ej6AVPzgBi3t8tn8Lg^Y_C(rMC)HIOPB z!(>HYgd+PEm-@(!jsmbN8c#8c^MZwo)xAxy1T_hTlmkl9Sn%E^-0!Q1_49%;AfhO0 zocb^chns5?r)KdXk6~^crTx6H%$%Iwq!E;;3;O|+v5ukwnrezrULoc1&OvDx-rr18 zz6Kzw-M}Ufi5jl1Vx!E#45=7|Wl3j*YVhi_!R}C!fn!#tpo`KP&h3%ZfVd6=`;E(o z>1YHvbC@mPoh{N#q3QoTnJ7d(!6Gf~{BiftKYRJSnBS`H{np<#dy<7Oab(&|tum{uo~ka-8FAbz2WRqp8aXrfj-`%f~2 zHw*(=oxhzOQ5?GJ-G{B2skm(kdn+%L}Li&?(ajCAq zsO%XFGtmegEFT9OT%{3)p9!$lurx%nqm?a#wy<6PqO;5mT?8bd1nzFyv0w-!pYU2Y z+s3UG>h;+BNBQC3%KP|6n+=q1Z*?-V5>k)G6bj>;u*Yi*rAVCpHmbAJCn|G>s?k42 zR8VvZmzyh1k@62On>l9m)-b(khJm9Ul8D@v`$4s_iriNEqaxoJ)v zMjGUHdrbPz@cI(&n2&R?vI3lywQqn`C?i@^oP*d^bgh!or6r@y@ zHQexEBzYR(OH;8QH2${*+2lmIBwwp;0Xn>b)OKh2kN5P0TslO0{1Frd_R`@`I8<6U z-u`v3YkClDyJ~{9MpWf8?vMD|W1^QCs*^)>&~E}HPWdi*n3!8vPVI>XyumJW9*81f zWzb=U{Q`_b86A#VXE6&h*q{JFcQSV%#VOTX62KgW z%hHFfK8yy*K^aOEZXpUElrQU**2|SsV^)rEDqArV#O=-kyDrVUAQjV zUZ#oxkeI+fTrrb)UV9l5R@d0qWH@@*E-f&KPrm8Ht(q3d z!$*i>ORbdy5x(37I_=h*feCbnTB1Ad`lPbY?iGn)DXD(Rn%TB^oKG}Hh#o>?PO}z3 z!ZkGP<^oVY9Cl&?3>-*wN>z5y`Sz*MapDq%obFNywN-GcpCDGw#iRCxv@5)*ANp2; z8*z*UgE-PmmGa*9UmtqNJ<`=teMo=PHsc7gX&pMV`H$v_D!uPNzyIdKOXN6d!A zsX*V~M_$jK?5nSdrpy0vzW)nvL?#((zuW41@+&TE0-W=mJ9?p8oS?0yM`{?m77Jcv zVygz}pqlz31uKWopsGnTnNi^+#BighdCi-GCb}=Oy4l2zU!yxMwJlu%$~I`a6@TAM z%>a%|>E8nL{nIT<86G9Ib1rOI=Tk#1EgpRS^|E=>WzhtF$X~|F76(Zb-%;EYRdE9d)xiz7`KdEUYv))KSzcnPfEa;-rTuqa=gqzxY8*s>(tOo91-drT-K?B(JyH zBafPMZ^kRLa|ljat(I_(WuIIqOyhj_03)lEf_Akf4^X1C^T5(F)(pCcVvK6t#)}>T zmm~-z@CY(Y>&I*19e|M)TXHjd>ccqp>6t-T(d!XZf{!&=AaMqd5DWl#B$d`Rs1~Hg z)J=d<2B-I{QhN>^7m8HD;V~mX)vUT{%F&t)4=%O-6niEfmh)P=Bt<_`p4Eb@{i)L< zVFIj$y|^E6FW!()&4egH1*{^R;0OT3Gvxz)n8+mD*WXANN@+cStfWH;kj@ zYY~pz5lsOOmRqPZDR!}dsnsVxj1t=Kz}Hu_nVAXAn4ZnSfhW?_s6$fqgM&?o;MAOb zKVIC%mYQY&pZh&41f3`gpbblRW(|ewg}iTvlKvN#{+D2lNhXS6W=Q)LRbIXjqdmnG z?H4wV>7XLioyCDWBQq4~Aqyu8*gRXlgCmXzLqVq{zh5phD!X;7hSng&+JdsVK2 z02)Nvks+Lq3nDXzO7sTkyt!5Wl}Ll&Sid;jfqO z*E+>&9#>6LEul$JSI&fx#cmW16!S#6pvXexT5iOvn+Ghbrb{&95z1U}kH=GrEcH0~ zqTh!KUU1E-=D)r0kAFRY`ZKmBv1o5I<8k6S`pq8}ypSzYTP^6~dde>=CFgRv+dsQ% zX=2!kR8raS0lVsxuyALHY`?U0!fYxCh!`q9{)7@8IHI-dtX#ZCc)oibj#SfCI=D4M zT9Tsj@tZ?EaZ;MyR6;Oa#zAeN&6A{p;NRCDSjxBI=oL}2MN987dL#FLiMdT!^|y1@ zGBdto6hQQ3pxhbn|-ZIGnC4D78TyYLF zE?5f5K!+@5?B_p|TeSXC{T$}A0EA3Qdzz(+9Q}Mw?t835@E+9Bv+Ba@sWwnBMomiY zhF*#@1X}krvuaVrG`L2oSphY2BDXOB2i8a7=A-(`l2KJ>lp7)K0Kpx{dbII(J6EfD zpwld``HOsH;t<;*GBv%8r=I58(`KS1m%r;}A5}{^n0;?ks+H&j!m}H>pklurHdH!d ztiwOw8$lvdgi0>LF^wXChPG)z9ooC zUG40rJEhxjRAm8mELO?#NRsVKF|f&q2P}+9)WX9HlX6m=D#@pVA)$x0^fu{7`PZHt zo5qWSA{rOTEMl@wG}tt1ur58esvNb}j6O({%QU4u2vV{k_tTYr0#z21q%UuJ1~W1- zn}qim5wC2gc88NHyJB*PhE@g*Fk-*pe9({lc$LK5dOlmFh{-z9UCA^)9G>CGF|AwHcU6`*CS&u2OSz`0>`H8i7&3LI*OS~XuEtjplXY1Z#LWR~ zXch1lwxjepw*>tW_*!@v)dWmp-Df_AiM=riGV5(eS`~E8I58>WquNl4@lqpNu+&kC zTDU$%Ox6W~E9;Gr=CpC-4}g%ww#~tOeTk7EFzt*y*n@0!hEMnts}>;N9riFWi$2?W zYGiJn6|850ztEKbr;M4?p}RiYn~^x0nqe>rtB1Wb;7@skCUB8nYc8UaC-elelmv`C zG{vr z0U+Jq^10}fBx83`8NIF~u|9mG+Rg83Nf|w0Y{{2BnOn&-ZB~w`b!7dh#A!GcsP^O4 zC5}N6Ff}!$GUByiYXiF~3kbx}CCX2v7GQs&oaP8w-%mj;OmXdY;^|38my`apsJto+ zr2Wif+U+WBasYE3KYZVmclFJM^YLWd%h$8~(R#>Gja`Rv6qRza#F6_dCmY9-D>SBc zOSe2}79auOSwfeVc3=SGLI_Mh4Nhe5F36tGC9aeaKeZ9e;#X!wl+JwF%zCl(Xpr@E z9s6A^;?x!wQtQ7g4c%A@emq+s0ecyv(lMP<9Gnc`V4!)@7FCw7_rvOO3HrSQZWH&k z%A>$kQ@$cGJ_41VjeS@ySag+5+%(ofVpGxro%WKYh#vg9dKU|^h*MUznUkgU3#FmU zRS>mD$xt33W;%I^MGV4 z2?YeI*fbR*mN$IBn5>VO3~ZWeb!?jYtr6_}S%qD>i>M3}sPyHP&5A^`rdCCJKY-+k zvUw4h{yXz|@$AIuQ5l;T6zdz9oxiEFMv}r5DdXFpWLKwYSvrppD4$vhitFKl$)Kib zfjzxZvS#7=*LJa2svHxzG`W?b#b4|T8F@jWXo+^h8LGz)FPwMBelRO(rO1}2Aa;fe z*r$BZ0zIpK)?5q7;UsisraXB+d=d%%t{)GN$61`p`4y*`Xf=XiQRbhSBY(iqDu`N} zpVdU{`|%u@4o2vBaS6;oUBJ>wc<9;ZZY>cac7_Wq4m(`2K$ais9G2BW@!(T?VrzPa zCjI`XFe#w_=TDsa?-1B;RD_|jK<`T9FuN$LFdJ3IwzaXtjOdWs1X~&==55+YttP012kc> zUtaGCsIrb>O#cJ>WJVI0)I^L0lb(D4fyLpP0O@5JhA0@#vog_`{3&A6L!8RN#A(DB z7wt=(^KrSl#O~>VJp`-yH;joYD-4Ht2+ZzDcqg=D&_--Ee^LXK%P2L zhy|K=5V9Xz#AGOOnoX#4tnv_OIj~@o1Vx-YJz!G8TLt*xsXpBt`y&Jrn1Jmt!4mgC zh`lVRnD;MUD=qnoagiuPM8)8QxZHo1*1ZANDqw_OhVjRvhBBGISn~$3hS=6@iaizN>F8Vlp;X z9Z#5oWe;oD8$L7BoiORv=O-d1Jq(52sXv1MLD&T?S_BZ742>*5OLgs!MzlV6ktOah zX`z?~!la;n2UUgR^EPBRk zygZEyo0D2#S%;pUe1tKPj+dG}F&rixdrSy&K0F!7q4RiZdoj8Ba^0gK*qqD2dH}hr z38>jcOvY9f>hm-zybV=AUdV7mBS z?Z%`y9T^^qh*g(~n#U8gjDY9Zqpc}ovM#E*1PjTe3(V>9B_%X9qVLBnDdHA{bit&f zyb?qL#ZFfpBm30gkCA{GrNgwWr;3=Yd#0=R$&-zpIXzWJ>DtOVOpb}@`#}qnc1G~U z<8f>3E6`m993Ax2AVrqib(qhP(j%{}5mNSa%TF$1vJP|=e=fe1OZHZo@?sl@lKTbh z9>pqgg{H8ZMJw%ktPPWH4GUHGbR2PRl*J2>q4jH`uW_NP7BEU(5tFe5Lelw_NF?}C z=}=~pdU~8n?k5YG!Uo@n_3y&7WbJxv+B-5y8pxpo(I|^Tkt-(GCK*#*0JAPoLXu1< zC!N!?3{SWmbf3-zvj>Uq`)pU>il7w&bMV-i z8+4>GgVD?+cKRV=MqVUC!{%H-<07{=ro(+MVlqZuB^_fR#6A4r+-RK!Dih&i;#Mwb zR^zQLQx;a;g@cj89ztMZPKkwx_pZL#zfOVHHEIM50}5-xlhKji6D4QBPa34aDahYv z^!E~pTEt{*>!Oq@OhCes(_?5AaSRUhk5h+QKP(y`Mn50!ylw zo6|Kwa!+jvUJCuOV$N!4mL-N&a+}W@o|i{%uZ_A?#AN7_9|h{oPI^u{d3xkziDU5r zaXPQ(l{+HVRKse$M;>L$;37f`Oy27g5tH^`!9tWEMfLd991MhHatP0CEi*L^kk9rT zOYWo^hn##zeH4_GjG@&0RNMF%`45ED&ZN)QITs&L${fo~iUVaHiMd`uS7t?fmMM!O zFjl9QVThGu97N+)f)a+(rtFj zgTVM9$wWT^pyosW397!jQPMq_Wx@?Ra2hKXLExI|fA16oYsCyrk#11+>F zfS;XcWl7PB2a_&?pj1~oGjU2AfS(JplmfN-M<7lM6A0lHE$2X`rnDzrW%>TzUM?ID zE~wZRjnEmz(9{sceh4g^otHmYYH`R(J|gNC{BV3Ie7%o+W3tMr@i1t%<@+|>txBc5 zINd6>2c=h6lkm9fV*{U1Ea$w0UI=%m+_9ZA?^;BQML@3;^XA2xZFG$tGo%AMW1F3) z$#T72TV0)MT=2tjEdy)0E-~pSP64Pe&!{*hK$Rw)AXS&tE4>$!f`YP~^P;}GG!&uj zoRju>;MTm1)F}>CiMS?0XB4X*3!<+g7%TzF3nn~un5MS7=@)kPd7Wd@O`O{G-p1I8 z5hfq;{ufr9(oz7{>huvCPR2+{_vG1ewGNmprO|3COD$nEvD}E#870>|mP44A0LwXp zN7O@;$CG{T=IeIBWOe<*q_a3R0F9kBxeR0i;8Ut@ASas;l*;|UIt7>j?bgA1O7& zoW6O^(sF(Fq;^*OpgcQA<&zJlOJ=ZJqS41R@c_BZ`@s34pq;s#3Z^M3z8)Nw$^IP+ ztf54Ay1ME7e62bdiZTfFM3~-TFbR4lXq#Xhf=tU8iNZp();fg=s>!WDql2G;)+wE} z34_E=omw;FAbaa@T5TIaZjIH7@cnLj{HAvf63593C?E(y7K?{9$ZQ z?RSY!8(n>c<;iF1SO3Z<6Qs51NgfkYTWvWlNSng+G7+r2rP&0Ef z-4lcgMNJj{z!qwkdf1TOhxzp`9I<&h6{)cH) zQc?ojfKo?ss_W!{L}HB7Y*vypCc;FeGP;zkcK^Q7~&AW3$_KP4W+(@ zFZ~+DHHB4zvLx8r6l%;eab&CeO7qPaAor4Gr6Fv~`XDX+G;t!VI;YzXn3NZ^1rdVl zY(}e$50g&5U_+O6HMv_Jv{h>V#+)mNIGuDt0mW%G%uzrBfP|8dQ+S1{DuKcP!c$Ya z!~Y;iaTvM^$HT0e~o!@iMlaFZrYvJm_Sgm!~zV51v=$;L*6Xp=eF#uIX*D?SMAPVLe3b$LY zzZ~`h2(fP&e56T}Ghx8Ti580OwkR0K+8rUA*|M0gVez3nzzfQAo@!Ff1j< zG!cD}Tbf8L5r6-BFVBMwlsXd{mYF*B#D=aX3P2z#tKE?v&Ylb!ctzvHG0ili!~Cwb zPP1GB5T`8wsDC~%JhUuja#B%1zalJ67>G20DBV62&NY?Ux}GQi(x5h^#VrCVPZ&&Ej0aCE1(4^IW~zFM zQ=08~5}z=m=av4^q|U}?vsBrPUiN=;DQwl@p$;eAcHsIO(>&IJ)8{z3KLaNTl) zG76ARKwx=TrO^_oMvV~h525#l?v|^` za-h&9lJD}JK@wvcEhvWechX_iwl%`a|l7q@5 zFHQla2aGLQOlE;pqcTjwY69OUWvP}NH>&1(ipT)F_Mdq%X@ z3ajV5x&h-zMztr6$R!oA0)itf1L;~s>D44APEof6<8y&@1CL8PVJxok}9mbxuysf6nNnm?zjyr3;u8FtnO@1l!}$2f1L!vM2EL@L)j7 zfq17&W(kcFi8#H~Nl@mWXD3!?PzQ4;1DkEg2Y5!pI<{)VZf#i7ABScB#Z|8eU`d*J z`GzId^2u2XNDbI@ft{{cc-AcWlf4+M)@uoVWX(u5PE5vCQV}cM;4i_rGZyX4pW=aG z3=~-)#Nd_@mzPonFaSKznkHzSwl_3PT&yN&BzTidSVo{Up#N4d43fy!nR-)APA|Qx z#_&HCr)kF`J+z)(c)Q(33k-8WOe1Q zP>UNWANRv8W!-^541h4qPNW(ib=7Gl5x)ru!p~NUA0U6yhVhaWfY!NlfvrEBIJ7Nx$p#R5S+QxCoY5-x z$$|{vf~g+Z#T}-Q-7QB~TL=3;5+*&L7Dzu!w^QAz?xi5`nkQ!( za#PtrlB$513i>S~8HqkbOd$!0q!f21L#qSwy*&bi0v4{#qW6kJ+u{gwxJYB{`B460 zsZ_MDEWK1yRet@2Cmw>K;b$qgMG8y<2`4|yd3Xdgp(kO{T1n|MCl6mFO!|CU^!l5X^nd89c&2`3?+A zV(}(MruuXn)Zi#H!^`9t`+wyu&G2zJ6#x<@(6Z5eGv-6DaXcn_0z*@}+s#M;KRH~T z0h4D;tuHmAmCR$(UJ4;kZ8`j4Qc)C;hp-c=b8r~^X@V`wj8%yUkr>6?WCai#JHsdV zfy|@~hXgVli0q;@O>(G6Hac>?WhpJQLQ=!wGEkP%=$WbUGRax^f?%Go^l4abczP5* zU()C@G163dR2BBaK>m8Nf5!=Jt_8^kSi-m9Nca-~Ow?UESJI<;z%f;w+A+N0F>t*7 zBOGo?ZCfxKY#zacxHLp0-8D1;*U=nxEaOFV<2<-|7$)J~A?1w_{hV}Hz##@F@d;%y zsqE@>HiXxOxcMin8GUG7|+B)gIg%hXnML1B9!eRkKriBGjoKm|~i})0<&ooB4{m&(s z?l64P?hq}AN%Z3Kx+P!|ELGHR`M0u&BI_)oj^P;&fWc#u{|N2@u?YiR5EievXQpce zOhzjeWv^JqEf1*?D$x8!ebKGHu1c7 zi}+*wg8|PCki_rm+v)oK_g{Yd?O(qB`cwQ~*nmG_XE;p4HkEZmxdnW}h96AAo+jBB z>ce@e&Ti3AXd9)A1zM=Cb}pDi&Dy24RRzhy0Y<5|GG5mJJVZZ1ONL2A4i7xs1}vdi zbHpV4572^l(GTQPPS)C-3#T9^DaXT?UKt*fp6uRQ!T`-AQYLvoiie-b*mvK3 z@!4k|A2^`x-MeewzTF>u@a};F@4ffl-hKPtIdEX#r=NZV2e=1+-FBJ|9vtiq*k;1e z?kh((9d~@;>KD>@SGgldOjFRzJ4I)289`}*OjA;!7rxQt@#eU8qeh+y#ivCJPfX&& z$S!0o=B%-2<`odhcpoErNvP`k^|OS=q)!)lmQb!@q|uN8xz>(?(i~6~czQFl2fP9I5CMycTtL<>4p{nlQs;1m3ZjTfgL-x9XRj- zT;ccMfB)UL-vV-G&zn8yu>p5=z5R|ZxAq$NaP5P#x5QGWHI8fvXF?*W7wS|i-Sol>3-&kwm8QsKovTfe-rz{G%^NYW zghonDl)|Dzc#DQ+pAw`~WrY=!01~k1(vNgakLp2#87BFL+I2aC%}TyukGtg+)OxDP zb~+&^%tpCtHatCwe2PXm@yy(L(mC8rwK~6;j9!>f;M8$|CCLv;@fo)te6V-x)=iqW z@52v2{QUFJw{G3qe^}oZ_cT4`re7X&=YJg2`R8QZ`JczM{^fb?&UmO#`RY}xKKt~u zkN)(hef##Q>h^;N5BT>YQ1*G2Hl=B?qy^?Ro0fRnT?p#}j-{8US0(Wn8Z|W;KUoq| zzBOLbhk`>X(|FN!gpncg=kq1RNyB6EsXhoHBdg{x@(o5oka?4y|16=SQ7oZ52LOz# zF#L%gAj$`-#U%0&(8Gl1XJ@uJkT6QA1uPmG5|a^g1^fe&cx5vN>kI{p*1DB1&7V6{ zQ8vBv&O3Ye?yY)uLbF@WKlXuN9Nq2Tj(!Nn&wkV6-*AvWyZzfS_y7Ft>rWpvY@oVx z=k7gw0Kkf}@ry4G`K&{eb)S+K39FH6I(3gp0WaLpRhK%2LZz9o_Shp6l1IfAh^ZUw{4eojbSrarvkw z2(#`pX>o|^kNFsuCg-#TkAj7;XgYY>v9=x7>>=#u2qv*>f?me302P&95B>TsX0tk)rX$HoN#*Wax~F3u zEvyd$n2A^s!hbbdK0lMzF-tQ}5?2$nEXD|`HmtKg~X)5p2;63zx(cs4I5scKR>l($&z{V=AB=57Dz|{B#Z{- z|2ZNxVE)G0*74@-d2h}r>2nkub4>ZaH@@k&6DLl5_0?AwEO<$lS3AWm4+E#Gd2K5u zTXATy!;?)MOHNuU7Z#wdq@Wl@mWW?zR_aa@;Q zp!Ep0AKkn)Lhq-g-&YnFxnM{86 z*~f>Y)0HJ6i`Fug4ySBd7O@OxSbDY!_hcP;iPp5bc2t&y-x#AKCb>6jKnlev*sh5~ zD+ay)MDO?Bc%$F(M-FXr?y#okKH2)3LkA8pi~$ajzT=Kb6{Tp_yxdTenZqzit0Gkj zFbSVckLrQ!AJB3Of=QRO#&Biq)+#Ss)_cuR?D3Cx-+lYJ=boE7b?S4^J$uFd7a!gI zXJ|tbOy2wJm754AS8iN!Yv0zl^=o5}fo*Od)w)5?|3pl}M=t7c?%2fGY15`vS6A=a zwM`HgskctoS~;oaf^hRJA~|7y+WcQ$Q{~tw#%#JydK?%rx_qP8P)B2xFx0b!_DMdV zCh1C*N$#aaDI2Kz(iGtmv|U$BIO>(E+i8ArAef#!(@WBTMD60#$VdK+JKmG2z z?=Zsyu!P25ZlW_=f;VJClo|6+QX$QJeQr$A)k-0EN3E?nQW!buNEis+CFV7G3M+g@ zw$&s&b&I_H_S?U#TeoIHRn_y)PZ~Vz(bGFMWG+@7lhdUaj=c8>Hje6e#Q#2U1b<|s zdyenb`{C+I)lWbDG}x7J+QZK2>VQc=BiuazBy8|IVNbfJTf`ECkJzwuWGpKIdmIFE z(OZpGT9na|nnJ6tfQd+2iDI#p&pz75NL@8G-~YX)=7)d$D-nROw8Ij8EhEAZD}f4XSVOXJ3ktC}#OTki)?y6;F9@c7^EwU~TW zdF9$M&08iey>@JK*tq8LOD-O9Cfb~Me941H-_!N(r>m;Qj~@>@ESxr5-a@fWKC9DJ ztshQT2TWRIrk1lG6fH*$77a_7({6y2%|fq9EbUoa>s3?yH-7ZYRS?&G^hp=9FCNgpu1qW^jTDAaA>UOHGB>;u0A&OPYY76!%RU}=;ok$o4laAhS zIS<9GXv(XCU4fUp^uiyPj`J0}#q05uX|NFKF%K(t0Mva<2{Y8`mjF9 zdEbyn28^qIVdl=AyBMkKPal5x()h76`jk)T)DHbUE>xZjE&Ru-?`W3dJGpx_uY$c< zu5WE+?Z(->Hp2KtmQol@+P&faJMu=r=ZDpko*goHNM&VZhhBFyEdM29?D*b4KXKrH zp8oK$tG7z95PtT>XWMsg-M)Jpj<>XJQ`bFz;-LRJzR%Bp(}PIM6CV22?d4@7M?N-W z$dD;hCLtzap4qP-K*(o=kmd#cx@#2F?}`Q_0iKJqhbpv+GRQ5*5=5vY8&go&#bhjS z?O$uVsblGMnrvXMcR}n=yFY>yE)?GIg9ksblDdYBz5JF@4KE$l@TzGIuV2ydhLtB= zJ*VMi<1V;vME5>pc~aMwjT;XhJXqQ6y!5CZd>tLWNDof{vRB{XtM%~d%R%zmDp>6@ zWGRK>py%3+mO58?8ee_&$;_G4`u6Sn=%bH5(5KVM-TnupA0X|Np}%T0>d3PPoUms5 zO2FijO^dD?aZzdIMezTX!<$_-vf1TNp3`Xbk*5s%b%VaYfRB_sbadxO?j1aMaKC>2 zCQW)4PV1(1+cBxI?i#sVzd8w!n3x2k5sgWK50<`53e~yfSQ5i>c{~4wm|ZJ4XG^D( zqJ-4?zyj+2?N7@>;28`3@=N2*H`j03vc)8IO`Lqmb&s5Q{gQ^QRyVv+KH;Wy@PF+_ ztJhAre(5<^4<0mZoJs21vIXS+kV`M+^ByfldTJAzbE@xX=#S;A^@PBr;|POEjan7- zz7sPeQ;?qW-+c4=k|hg!_UKtr(fi>(J^MQ%C%F!~Xwq_CqK4>fft(@7_Io z_5hH>X`Oj$8H1I;!W3uPP$Z|dBSrKaGE5F$3iv8r6h+yUxt2Yz7 z(rd+}n(_h5YB$au-;>lcXFZTJFF=(Yd;J7qj&2UA>xrk&zHU6=EepcePiVFBw40Y* zc<1tF9i($_UsZDR8rVrONk*fV)xG*wBB^W1;>90)@IfsFC$~tC3np0@v$k3f97(Ca zB$tJd=9TtfK+?ON$U7#Zbo`An4jw#EUf!*9r_SZwx;-$k(-~urKIfU^FPeTzi+PQH z_v|_H+iL)m2fzGy{ed-a=&!9m@Y?zh)~tJfHT>Ce+07RxPdRtu@n$d3AUeQ{XzX5N-0c6~`_VlY0CaLSC`O79fxo7QK#G9o^k62Ff)p}^g32k(o?ytPq z#!RC@x^I@BL=(7a-EDUwVHiy2sSdK|`1ZgICX-JDKwi1{ ztgBzVaNX|L{*`_GANPVk`>cE5veao!r<{1rxQmAmAKs}`CpbyP!#$S1{37WcqRu4$8>-@al6IItMxEq0IV!b#|>HeHe( zy3V@0e9j-b&G`dtJecfo|J1v>z1-oJ)t6tnxN)nQ%^!Pi*gf~$bNlUg!2h7OzzJPD zBiPvId0K`tM3P7}Z^$%Ftxc3eaO+gl-74#8XBDx!L}XZU3Aorog0ICI!pH8jKddR4 zvquWRoJ;C@uJpFiC)^}6OrFzr&c8)EYZ@cMIIN*jA%|_20QC?mSAH4bIn@5hUJap)QfA%@? z_vFoTwzy14*=0);Wyu;~z(#AHX>RZ8o88I?nBDt)7?wKqAJB!8Jc1BDcAuh^>aOhf z5}Vu&{5+{^$k5^E+&Bp_Nu}TGzx%^HZU3JEf#aucI=S`IhOJke+;YnFmz6(%`q$UD z9%|5PEs9DQzil<|p^8c*b)^P8xO?DT$@Zk#GkY%ne=OoqrcP!sb4YALQFsJmVJeMA zcs0#gA%ahm6tHk|vcP#sXqW*+H@&`M;oR!G+uwe}4L7uI-TKzsZhbsCyo=oKF73)r zAGhrG*ET)>QTE8cZteB2TOa;MS@~bvbUJul`+Zk+d*hBj&U^gcd+%-2y7l$fU*ECg zJqHeG;tC-4Ku95r(7FA57GS`+o5Q4Y8V8Im?k%FK-8gp^!xGUFdr>b>Iu}+sbX*E~ zQdif9NBp+cd=rp2$qie}XWl&P^-cRgas49m&BXa{_k40&vrey`+-CL(H>Dc3S#iS6 z>&Rim)3ffx8`s==-{VN?dSTGRuRm3((Y|T^UYcD?2hfomI$BlQ^_kM22M5sV!G@(9 z3M>jC?a4Zfm4k7=X3g?@?&)y#)mLA4UCVau+VvS;G5)Qg4}W-PkD6NmjgMqU&%c%R z`sl7Fca9ic_1N8acYuShx#pTX?znx$iX|RdTDCw){@CaCdhmYcH(?<1jB_NN%T|<% ztCE9NoNovT6f{f!c?9;JBg&OySt7)`Nri4$P>%nIQfo^C*HdB&i?bh z`K|_w7{bP`_da`h?f%<_z1irV4JY6A=1I41IH8Rh8fkU+SR{2lH}K(|n>Xh?bZIeb zJ57ePSm#(is@=ap`E7tWpNa1V>E)aQODta!&hJ{s-3Y(>>XXfz-}u8H?!J<~0GwsF zm31G`b?R%A5^oM2`p!c`clSta96EFTivu6;e@FW}TDELS5Bz;u*)8kWuko~Pu(3}z z9prCo-q@c%Od3In`%bA(j*%EZDVEhWi4FE(44lfP{A5igLHa$b$-4Zd?)(J!z0;?? zfTXVWUB{es(<;CiAnla9m6Fa|8$GzSq|?fFkG{O{^>_BX|MAz~e21RDZ@&Ngn7LZX zeOpewck{`&zj;F2^#qfD_#=|Ks`_@{w{IVFA6$3UMV&j{UNzxyHsDX#$xRH+&a$A| zsTM%J&ln8AsXpDX+d|0rWSuk1zWL^JMOi<5_>jvkyR3Qh=9gt(H{Eno$BrGr)V$@E zTdug`ipyHGzysjbw{P!_8`r`IeWW8P7zi0Z{vSsi@qaM>>%ac-yWjoe_S^rSNPM44 zeXFWp$Mr*kR&4MaipYt?@F7!<+W_y%)7&_B{%h$6f~6c>$+GLcJgMvco}(JIS#r{C z8v&5N?XvB(hjyIVYx~(1O5=+4=l5QH-O%NCj9mQm+}A$&JOi-&-JOc z-E#7sZ-O3sTbC!1)b+%}_wU-Z3-N~V8#JgNFDJ44Xtr6CRZgOVD#KyS{{uy7$x82q zvL~}G9}GjQ!@PSi^gsT1|Kw+@uDYSOeR?7Z-T z3vRou-Q>y7e)Q3Mg3PR3dmW>ZH5Xs}F977f|NB1?nf&<0FaCA<>Hh?jjvN^Yr9m@J z-NJ_cRb|E|M^CSabElNWtHGKPj(}}u;3Mc^Vv~(0b@l2!uvv%sAo?0Txc!V?JJ0C7 z^iOdEs&^ZwwJnAj73t{ z#BS|(@7~QY2?zv{Xc@564U<^kggEDdSBHU*5lb@Lk|-qK6`!b?%fpsl(=n(Q5}EJ4 zv+aqok6m)f#f=*`KI^Qr&OGzXGtM~UO!|U98#g}t=9^ouU%z_Ww%7bKyx>RCJ6`jP zUl90JJw0vbmiYax{{^zNXV03ds_!J}8^ic2boJy| z!hHdpOw|oCPId0Gz1b46r~61jOlTS|B3VbIt2ez@Na}jvfd{&G@7}+E|JFTbH|nzO z^oMtzHDGt+M|a)-)XJ~F`S$z2{r!(C-o5ZCtx46pXFn>9o*@Ay|Nf63t{k)TtVj0{ zSa#d~`)=vJ{U1eA*X*wC_Uze%v~mC*wr{e$U?;rAwk@t{EUFelO7TK6Bqkk=8IAh8 zd0JUEi$~2X`E&2yP3zacI)40^OD?$tgdzyc^XTh>3od{^Z@H!I%P-IS^wW>fPfc?F ze0cL|S{A(i21q;d$p3Q;gzVb#c53FE&s81l*zpjc^jE+7yH$|>{9h-XL>4bK<@gUTF%2IX?eGK_%zp8~2fHAtt9!rEXZC#Stp4wu zQ~BLJJtL(5OP)5oOJIFeA*kKw_6cv5{&+ne(dbyp;XegJ@ z(|#_OyGwGp?>+nboVWb-;ss#x$dyOs5-yaAzf>aTT(NdA{_dE9DZemrFcbf6|t-S8u&|@i$Z21YP?38O? zjlNiQm~lkxSS;%LE;9V|cWnSsII@Xg(qv*LI5C2l#AODPcumna4oXFbE$qt~L*x}q ziYA>b_M{W7ONDwVT>s(m8h!fo{*fd5TeWHdP^?s`68sMi4jwaR)Rime1bs-_u{H{J znK9Mp2?->?6A@t(gpKUncye1k{BYj92fcgT3ki7$DCL{G@4x?|e7T2%2R~o1fS9`! z^;C&n?IjOzG&i6qki;1qRzKYm7|i3iFhS}G*puKdHj%4s*M zCz~Rd?4I!BvDtf0kKcZ(>*^CVmR+f`)LL=j9e`!c$v=&mk%mQG?V2}R)g%0TOeBLz z9Nlf=Jzyog#K9z1FC#I9iVJvttFMhw5!r)DzV*e6;Pcg~Mfo)fuw1ojX~TvMP+NZY z-C+35q=PzwI)|w~@810dz-D0I%YqSn6$OT%7{MtW8+)&9+k3RR`xhUjZ@&3Y!GbTq zFYVCbk8$IE=U`QZUUe3Pk29sLFZsW-&!w<7TGT znSHj>;=94C9#z};q~`XgHFx}6bNhoD+wa%RcwBRbt@_rdRoDIuh^;!`GG@Uhrl@Op ztI)%PS^^lb@&+CNJjWQ(;*8+zZFmW{VR{lUEUYfNL#ouVaJT*3rh!D;4UXZo-SQ{ ze_&rLn7e?|{2%_#M=2Yt!NJc$LvH|9!9g{deo+`kQZA(C&Y2~|pzoI__gjSu+S6yah?VP8R;xpf2nHBlj4C$;48$Dr`_E*_~b@-8P+_*S$WXy&Q ztL4YqiSUjxLD&EOmn1@_PWyrHBuKX`xEK5&Km~O=W5z=;dLtqpRIF&roA(bftbXvp z-=I{xcmD%?RE#9qZ0_AAObL#fyM*YT5di@Kjcb;-TH)o1vyZ>L2~5{;~go04y6F`K#UT`xACv7(Z<$Q`Gf! z)5Zfss+}9&mU6}xzWE&=>HH=uG zKPxFm^EFvm?M`-!*Ws65xpFqL^}~-pej%Hd5bK=8psUsToQ}$npez3l;%dK=r?6_+ z^f)n*U^T4a!%qwS_U4;^OIW`MRF`w7Hn2zH%;uQDG6evTwACuwHR7(B;QVuou~bU4 zQSytrnzv}#XVB0eHt$H$LLsJ3FDEblA zxmBRZ175;yNaqj@NELMg&m|{J`x=wd=U1#-S~@9bbEbhQSLQzdN(H6IT&93hxJ<=9 z2|a+ohC$cLmH!b7y21rS5}w)0mqoXb5Fd5Yq(@ylJ*XS<_|rlH@m>V#Ck3YhlAP(t@Lys^6KCb(171=vWaU!8+VtlGieAm+hY37Etp z0l4f`^vXK5vUzQ)R+A-Ge*w}jr)Ix=x3EI7Hz4=ekA}^XY{gW{$$Vsq~x!={w z7c(qxs<3NBP3lFw8i_+EsW8rrMO{mlENIds%+;cEipbj+m#QhxX1p z|5UF0Ypv=x5)%Gu+LXY!ckdg5G(7C!klmK%5qx-whDS9}0dK39-vgkwZ~G8@RKeVg z1E|lMhMg(!VdA*ht%gnOwrrUI5CuCH{!>&OPUey>?jZpi@oz25jE+@eDw47H@84Xz zcBO1l7i`9jwa^V2TgMM^Y6OuKN^Rwagw^U5Ov+jsvO!mGB;^Dh6>u^QwABaKXyuAe z^X3siX=1`p0*Mg0p)^7$%_7V44b7i&pe~yCAnR6@M>v@DfB*Mu;m>Z@s(!qD$xtQ5 zJrdkjqzqj_y(_7^a-GnC=3ll z6->&G3~?#w>Z!zoY<{09#7RgvFmRxydv|cX{_fM~g&_C2Ya~&eCSCtVDc-8U@?SXj z`iNnNJG49ZexBoRzVVm^&PzWXNx<2%66NXmWkoy}SLPJB-rM!)(VdMOHw+#;n5pPu zin_**jRT+a(xo%}c6KtJIV4CLlOTXwdd8L;Ne2bsS2>$N-0Cn&2V>-du0|E6j)JVT zQW6Ix11CKYbQNR}cT@=b3C}t@mJ#);7?S&1ug~ARHTC`XD1c>Ti!;n@S;`cr+%`el zA(wlaU1@@O`0&=wo!j>w*#A|p#wBW1L^Q&qe5H!T!-7+nFI~NQRZPrxH*Q?y@2g)m zC#jhXTFlXrV0M~FAc`Ji$f6H`TxO7>{TCGH4qg2|i_9}CWK+CMeZC+i_6=yk-#(7Rtn8!yu zIa7X!VN#k1*fW`Qx)wM46(-=#mJG0L(Xd*t_F-mo%=j@|=!Vi?bV=Rl7;G=2E>N1U zRf1N>Q>`!$9^CrjhsE8xc1@g~*lXPAw`1$&PO0#pm0#rV9Fh0EjM8atK3eMdlvLI8@Rn z^gzLrSQz5jvqwhbj&Hy1(WFU}X5r0d&YW4YSEpQ~>*Zct;hm`+UVG;W38yzn%)9H; zcUP1JOn%g}1#Hu)Q>Vs_8@FoJa%mbjn)&;xtqpodo#n#K!^Lq?R z-}!gnJMh`N%gg<1Lap3A!^e*w-?3xI>eZ{mKDBDq%Es&YotdM^Ds z#A=O_v05$3y*!UeSw~An(Dk{3=@h7+5h{}JE^{2F`aFyKTp)vD!Sti@0gPVaU!*m^ z)i;qI>eY_z7nyB!~Od=7B61VxpU`!{rVLuz0VHF{-Z%X0vg{tP$=)7&(U@b8QL>C zIvV`Rh=}l=JAcF}-TX)-mJoO(FG8{`3anO}DVChZ*eDNUQaZD*E~O!x@Z@ABq?Dmt zt{EyQpIOkzP# zyXJK-T$u4`p{H=^7bq~tLKpXtL3-j1mQoIgYs5STv1)<+`wtc_d;~tyYp* zU%jVSzc9n>ce71}_%SpUIe#{N!p0{{Aj4m8{ix4vf*GlXSF)wTPyS26CP*!Cx~Xtg zg_UQlv*sY znz-wW1>tpm+(XbLwZYTvq^MbgUR0zpo&#ZI7jlQU01G@D#I~{RgPVI6gRzK9HB|)N zH;{_V07(wG7CPe_4OCT->g95`#?LX!-M}qM3e8g?I$vd5U)-3m znyi$H8K+WIuVVVIreSr7+dw}q88y0ZW(;slt}*a{X29}t0E}6zK{RIdVMkfh6{A~D zbNeCN!Xya;7-eozVIL`&Q9BarJKopnF@b4)_CCZo|A&h>{G7k+i?`n%WDkB+v1c&Z zPN$BXDj^2en+GwIG&-tgVj2nlolcRs!E(M;0IdhA+LL=V{pPU>DY$q z97Rc&-$^B0dL*8?nKJw)2FIKc zsn+4n>Ta4fA5&Is+NA8PR{qgDQBzM@MZO2=pN5?=RxpAt>aUOa+b#iS4U0K0eLlZy ziJw>f#Qgo-EoBTue|h-cAZV>Di@(zbV_^Nm7h9KsrIPFs;^%L1a&@gWYCT)4CC9_l zf<8^9vq8Ovoi4lT5y3iUvNR;;u+_R|vtx?1G#oa}4qSh^D`R!_Oes6lJeafm_FMMd z;&6PrA^`u~$b^B90G4uwsPbI8c_uzbNjV1eFU=((c7u$4b&m$j3mMamSDpGV+#OY8 z36fCkK^Nn7&iA=R3sPnuYz3qMjXXJ@!bzjr@6@kiF6DESu(5=!Iu%g4BQMWa>?Zb@ z+yr2g;-af&eHx^S(5oH=l}Q<}xC?HOtt$E9nD0x$!; z=@7oE$_s4m%2naMpT?%ydTS%QWF6w8J{bTjVR+1evjaskJ7(|w%fSgtjNS&ds1x~| zWvsa1OV456rE=_3_Lq{qg3xVFs18z0AcHoLl_>HZIb9ytiA5C;_M59iB(DO5;0;?&Bn7S*>?yreq=V(b4_KfvLlBHpxi6FIYK6 zSni^SVS^jN!NG0&!$6FO`+nJ~yE}G;Xh=z^gq%A5HPdL-l2Q_tVY6KVBKoh1c)>L7 z^d?4xbBm;+uC)#3B{Dmc17<3fDWps6$V@%X!;_LwhMO!w#MV{^U*5I)UJ@f2I{Ezp z;E;mGEln&y&3jCi#i?7Wm|n(7=6jE7G))=k z$<(Sjj##7KI5Y6&fv_C|*_IK(00F%}1g{ru3$hetzk>koU0!yU11l;lq*JE zhYB8!Js97WtJ$i-tk4ljwbj!xHP(6aMq_vToPcnAR30FJt_i0s%|+6#9tq?c6+bCg z-+|K%1#aRbR?Fy@%qKfm<9B8j@Dy;?nZRNu|6nP6&z$(t>4x7j ziJd-%w}eH_WV2Ug#B5g<@#b*yP4E5MkRSFK{cHuV3rjE?qy3Iw_Ivv~J|8tzd2YsI zlZut#uM=fMp(O%JR6Vw1;^PI*Tb>wY1UFdl5-iY#`z&YlqsgO!eG{S|;8;Fd7KtH! zS9M!}(O01{Ybu{xIGwz`$OO$ZJaZxAXI^xh#g;%ly45$|tei+w+K`34Ekhz`pUE0D zSnG7p*xnb}nS{I46a87umA8t4i;{SCrZp?&0~>aUFyJ*jqcEBH(J?cI9ZDI0smm+Y zC?p+4)5@kxBtP9e$i857Ag%m6rcab&ItD!pJF~8`$x#1}fg>4Km*wgS1?=~vU)7@& z3fRPk;dv%q3L9Qo7Of)|9lHUOGp~rhrl`DJPv{1v9SCTi6JA$N%t^Rus7oj2$!$*ZC7cUiqot(wt5 z9DdBSG#q7vTB3j4%+*=Vq^Cum>)wn%QglAUbDAV2@uXWV5^+rvX@AQ-^JvcdL_=;M z!XWG;FVz!}T=t_B4w{QgMc6wrgP^+nV`csQsy;Ya>ZW&{%w!g7KnQ-^hTpPX7rW2* zJh3RxoqgZ07v14Ae@DL@Mir3o2U!3G&P7MJl0nO@EBr#o%{tCUIBOHc!elE{$X~ zhyW}$sHi9?Frd#%tvpm11a%5f|}OZMYsjSFZ`UZfv8o%c3v1PZVy zn-F<)Je>i)}Jrt&+rB=lJU*NVJuA z;YeTt`7||wA%h~*D+Hn58S8JSL9o&q??|9Jds8d^(%EvOJEcDjPbusaQJAHAMvuu@ z!Ny_imHD*@Awa!UZk!Vt?!=Q3sH)}W>)wy}*(UB<8|yn2NMCV4fa$>)|K8vPk*pdX z={5UpbXXRGa8$YNy{ZuDs^XoSC{@atsk}sjRwmzOnHs>7<0$F%_l*;=6&6mh9`Dja zMcsNqV(>JVZDZ^#B%B;)SVQe^riGPURQy|${eZ3{dBi8%B^Eu#l8*TZpUdx1IV8zo zV72y}@5l5l-$0F>YgL#U+rc+4T62j+iYJOy>%q{V=MF0AyR~`vlKpF~%v@j~DJ4BN zM|WprwMZhBiOXCds7nQ1HYUkCMc^7?6)xw+B>qOOOYlR$9~exdt`LJ%6m4CuR^6<> zY*iRXKM1Y_LOJO7BvM)EGla9XtI_w7eJ|$gF8UZFe4YbKR34rJz0G;sWI{bJo}+S_ zcO~+3ZALoyfC?i5m4?xU7mCq{4jH1}L~3Hlwa3 z`^?{zdH(4lG_D)aVmyPk+`4-vn zY5AiJPf&c+#LKC^tOBvD`5M6NV@p4YD0bpRq>`a*G4|#}wF%2`N$}^}Yzczx$uLPmHF$cbc5Fm0rc4(oY{-rfl9W@q$2PZ}d|MFsmGv_9z z?LfSjhPx1JQ4>0}-QxG*w4Mqlsqxma%=R$q6rm!Wt^ZmMlwa&!&ml6*+IqprnCZ)o`NR6;(Df~As9zddHqTxJ|pa&-HG}cxvBNBlpJL89;`Ke z9}W!YK9y{|;lfXI+9;iT_iPr4Bv`|};$iU^XFadu|7;%75dYT3W=ix@PHnYhq5hYE z2JyG4$l3a-j(^h2upM~seNML0GolME+b)x}C(kECfmv#V7i>3~oSI6|pIWBG(<>i+ z%u@7D_>039&cbswv4!&Ua7(%Bs&|_W4!92wAe&5G;*e6X7)r@A1EYZ~|9bTMbgOE#}7Z45{oQ_sVuvB8Pjz>MyO2WyFNq=N6dBx%5VpN$YP*5R+ zq^aEHXld)cUhEl9s55GlexgJCqBoUhu|hbOk#aCfwJei&d);DQ4{i`|SmqBJW}psK z5k??byk4-2RuIbT&BuqmVF9EUuHcFZ_+>O{au*C@cEx@OjBbqtCR}Q}Ua(G1v9hxX z76L%DULW@&;I+&}4Gq&y>*;6EA7Vq*xvaroUvAmkp`1lCeve_YE~0 zrQ<+@CFq8H^gNsl%6i7xj*0AGpWtK0fsz`!*5WiZcqZ>&G)7r=G-fGgrIQWy?;8CP zEk$5;5@y&WQ9Syi3;NG(&1gTkPGZYEQLVSqsAR|enr7vH}(|c94Xy9}4*NAy7|@#{K@PbbPog)^87i2Tr|mR0WQI8+%Lv*bnlzj{-bV^C~N02nS1rYNG6 zCwp+z*_xX@E-iyn6&=?~UGN-*uaVPIA=MrW>6}fs28rXGdg!s~|E?<03?kYX`NFc^ z*XsWG`8*dlQBHe*n&)fqoM{9Nz|m|3ubey`rowm~JAIamUy%bgj3>F%Du0az<2A?s zaAwIrKs4nbnxby$0{zfx46_(~3Z*O82j2JK;p+o;V;T}y)48FARw6QX*i^3ZnA3-c zx=?2@Ekjzkae%S6f{86U%w6@bXi}EC#VuQ608n<`p_rH!N>RsD%xVPGB1V~BH+$h*H!haZHL5U6bE}}X(jq2JT`{bhld)`I z68r6wT&?JtUoyN~Y|85`kTmHZ$?4ybCwu8(&J09!#5-E{DIp!Uk?YPMVtD4IQ*`!4 z%Ak*=w6j2k({K~cbgBB9W{u|&A33aAZ!Y*aW8lvI1Ag6>f4bvA&x z5u)k3)%2?}`d97)%*!EY@XbrQ$W2nQFAxR>BzF*KXds{I8o19{uuV+$nlPkFhva-RpUks;48NhMOc7bGCT9&bjP4) z6LA-Wx|4YBn5kES#=|lg^hqa!p4M}Q|IJ2PTKWYb)nsXS*39?T)jIZ#9<=TvXvJG; zsC-khXviQz#BwFwSv7+M@^cjrAuOj~-ebTFKQp=#8NMLE&b_Nj`<3Qc85B=HW$oPK@cBz$XO?Z`}HSyF8%iC1KV zUuv|88qQ<19Mt+v1Q%(Fd0R1Pqn93kFgRS-xXsp0ltP;Ua8+;5TxZc6g_F@|eF>{$ zoj5HaS8{d-)OjKnume)yz_W#FizPSbCKc#X)go9ewosoujOy)vOXw3HiNUYgPYEa= z)gU{m=ms;QNxPW#{I%s3I<2yfuJlh7V7)&#ND;oc);i(FkeR7|zC)d{nu^NtYOOSg zji6Gr@^u@>hTZ5dL!y$`>)wh9(kU{UrT_!2Cp;i%sij5n*XvIQ`UtrW`75}xACTPe zcZ+e(_&AsKhgV~l-pBPV{o&tH8^}gli+6TZFTEK_B*i0tth-Y}Ur(&;fj~0^nE7oA z3CnKY;?@&t)ZnQnT?Q@7hT_%xXT?hN6oX*DO*#_&sI4b!TzM5Zsk{yr$5`MdRyc` zgM6bflb}aj%m-%f_^i{k{J%_&sf^FnBE5mgM zR>vGR>l|SJ;T{qdTPqt(seWz+_yxCUCVlKN+Hx+vE5v|JX6$E1fkVT0MG3}W*QU|9 zY#-=|K=g5<$1mJzvflp~D#D7c%q&1e`Iog)2=tX~lDS)(nxe&Xwq6GSOP0ZW=Q0jL#ZzoNa3TJ#{xR>aEE z96`h;aXNAmVc8+u&)Z{q7ch;$n;b`n_+^n+&r{Ox3rZ5W}uzLtdkyYI>iqYm5mN!zs7}E_(g+StTJ}JfzZfs+l2xf zs+e>e0ghj0tk2)t6RC?DZsn+V^-*a`wdtYMH^6QznaxM+*{Uh8&1|FK_@pZSDJ+15 zRB^Ae@O%iS_OvZ}a8?9St>+i1>g8ciG9EIX)1h1@q_sH~Rq$EY$^M%t7@GcPO%=X?M=5VLuWC0fl9hBG?9 zso+g27Q#Fa&gWq^4KFgvv1ZTGYR?5iRS$)zVgFjUG#|IaOM2Zd1~r9Bh`FkQ#iaG7 zT~;!}tRkwQ6Vxw1$TAGeAZ$q#qVlUHr-;&{ke(nhR8eRNYK-%yEAz0AYW+uLA!~0U z@UoYp(Lv+6yjZrdRz6M^6DIkCgI27g(HAz;h$tGUw9ZF;+R#YLk7;$igu|2MtGv=z z3@nASxYV#o{!L-E13KOJEyNxSLD(XML4Ah}36_ih3)5$jrT2|NyJ#el5%EBi6JL(Z zD+EcU94Eud;KS0Nb1#rl2_7wx{TtYpF*RLQz1V#i=&Txr{cB%E-=4GH({G`6QG3>0 z{nK(;O`9VpE^YuNy;zLw%m%dc6k%eG92(q`yk5K`=LP%Px$lK|Bh_s^!AE?q5~=fxs>>#4^t) zU9K5mdv&tjbK4 z@ndk(7c!dD`hno(EklI$Y1@RV9n+T8rvp3y4?cW9SDEny0uQod@(G8F8Xug9Hb1;9 zg#da#JF`|e+bc5tn)Ats52G5D8f#AhxnVw z$bcT+_Vl7B5g3tzM0_q6W&5#3gJ#_=sxjslQ%rt%fjBoePGH->zya(Xe4swr8&OiY zu7U^moe%c>8p(4R*Nmzqm9VyOO!T%BDmy00T9NPDAsKIi9E&xxMI@39Tu0_LedutE zA=v3C4p!K5u!0RkoyOy*`q{S0kA(PyA>Ya1uYRe%x%}*=**;1nYii^+V);=1oYyOe zd+Yid!D1%%Ou_BA9xuo%O!?6d0(AczsmmD3COJ*V{EX1psBb4&DO*bn_wTek1-p}e1 z`-YbW)#a0+5>q!a;#EYvQ167U7h%k41ZkQvpM_9`j~TcNUXi|q+%a(ue5$7s)wD?I z3^~?o6>K4B_-6NMb7@p)C9%7@G+L}*ORMN_AL6x{JnxqqGuZ3#5)70ss(G6YD>8xu zqEeY9z$p4wEs?HUzRP9TP8S{LTtx^8tQgh@{6@;PNfGMF!E+-$j2gr-2{U^l-PWk4_O{=tKQIIk4&CBn3d0{g3GJ{{DMHf!Kmx) zS5GxCm1juH;;UGv^5Kj&-v^uz*tHcd7Gfk!0b_E-dJbxIxL}M;GDZ1U^I&N98FNVz zp712u-S|XXtZ^L;Xb+`5;qBAXJ?j{fMSZl{Pt58DRG>e)s#7$CC-+GGY?%?8e}(K) zN*+80Xno$#;XE~vOfM$f<5<--0}9wWWdFwVFFfTog9}sIz^070ie@I6-o6f-Y85Tv z{0uZvjgGZz2*IV6zS>qJO*_g<>UlOx`f#;u#uV6FP%IRddQE*2kv@HE7tCzC@F=>FWebfyKhqN{VQ zN(N?O;R(mw#J$GIOy2(Pg^P#NLbe@k8^J9L5t1~Z9rhTS4LspZNGBo@<-OKiVpqG}W#_V}$TDgOYIk zL%fQ%GCbdRWWOhr;4?9b53yRQV1IMLR4)igBsWYZM+9myE|o|g$uMnmc!nCFwg->U zpyjAV9v587wp?q`jKYQhz$~;)h!RxQdnrzVU$_`6l2J$JG>-h0g%>JZ)l>Aj zflSYv76W*OFti;h1{Ql2=It)hPJe1f_)1P$YkxeE06h;g@IL4)ZpH!uV#mxo+!H@l zdUgzD;Wxs$0#Y+DT*U`$BFLG7mltUcMlI^wc_y#F*r-k1`O-uclS>#dAD}rZ`_O*tab;ral&z2D#&^)YUv0?iqyUBaEloef_gRX&SiF zxy~J9zUeT7#DHQ{wfTWEg#M0#mPneuOo-bD9!?LB4rfPofFThTuJC)Up=zj;gk!PC z_dAH(4fTvZ+iD#Cug2(G2REd)7Zk9k{1VL)8jXbm@C%a4aoXtFiimcW#OIDFp}pnG zhnhOJ8Kk)^=ms}mFoLzS-YbKf#I(qWz*YOV&^QS>H_93*jvZ^{zM z!Vrq<8q#k-oOxq5{OjK7Hg{ITT9^RRf{+sl+e`w=SMKT8F7|R|x{vJs*hJlA(h^Mp z%HCTxi}g{CMKSscMD(}D0ku@%lP-&^%3jeGcKo!=Bq6B4i}prUWBy2orq@1bZ{e{T zGGxPzdd%jz53k1mr)pvd4@2C!RwoRoelmKtNB=0hpz0QR3TLcG@Wxi5TTcofe_XH**`WL^wHm&8)1EeGpNl&n>qu;4`Ip zg{6(2Or9*~UmU3}LI5kfoW_wPi3Lb(8xb{YD-m`_m~3w`tio-KoRcKy-%Eqm;-zLX z#=vTFcApTQKF^f_etvN?Lv&1O@V~A}XfRxxYK&sNGdJwItiS5`fpL^yx5EA{JH*1PF2MZ$NG=WTNAR!^aeINzt zQRhu&5dCbw*bUlaeluwF3#eV}-mG7vZ1zxi&ZZRO%}x^HW@EcEM$)oB+kED$2ckP` zf4gdmearCp!9O0|rM*Ah`}L`pL^FEwxDVo`q~Y+Ox!8=a62mF5$i5f9iiQp-#-&h& zVIT*uDqb8YJ8Ky!j?F>SpIrFPNqC>49s)AMv5gRaSC3zkNqJ1J)f#t?0GivmmOq*0 zIUb+Qca_PXfy9i^>lP+Xc2cV?U{N6*Wa6?zg8pN5YL#> zU20{AJzRV~0&8(-I6Ww}?zC~-v^r^XI%DCg{Rd@wkZd`G)*9b_Ha80b5%&6iSFy-q zJ*Gw?W(T_H^Y}Poixr#m!(znSF4J9tNB|NLM*r?1{>U}>kSmmqBOH2@Ako)dJtRf= zZY?pQ9 zbRiakc!bf<*e+WCA)QlgkT_P8as4S}{`$%HdMng4Bis5f9~Y)^Ls)-rI1WWqepgrb z-C{|E2kS(-=rK|*EKZCztq&ZdyepRssA*U8x~2M`nKwo3>q73|oAe7-RVLfg26Tok z9@H*eZbx1+bixVv>FWY-_6tZ2kGGWZ<=j-kk7f?ecCt>JiMqrc0(F*&&b@yH4 zd^ukEDRb<|4@&|N4>0a{y%)HX=no71|IE=MXCIwck^Q@f$Ikvoy)C5W`|KHi#tTQ0 z(TQCJ(Des_l}pY5e#iY>G;!1!N6QS6^3eXw7L?0~aeMS*N<@wBv#U|${t8$gxW=|VuBmcIHra^58dkcAPOThKVW%!+(M&MiTR*i(f7*+87 zuRvg6&?LKTaF~;eznDf%{q6x)iH)m{?%J$o09`D$V z{1MB;|B42;*EA?*H@>3QxMYTZ{emi*W@cPrW z5Aj%s+aBg$^E}M44$37XZK%JSI&VG~W`* z!R^3+H2Vo|%}+zB-9H8P0sZ6>rO^2IoF$=LpRumT_BTC_A=V65oyr+|4zhF?lHR@F zmEBv6pDevAH-c=3eZOqLWy1Ylr_(r%8~(@YJYP@njq8oFwro&0@G-}?{8H+Pem{zG zww!9<{yNCXXe{|~r}x+{G}{UbOSO@Ow83tpXy)X}|9O4~Kd%RQZQjH#I=`PB;R_Ra ziA^XHRhI)-VZb3>=qigNX&4xKfQ9fg#>?$M-+fNUB(|=CUTuZFF}+7LLaF0LpgfW5 zGAKnjgNkdCR36%#S!iEj!!_6Kx`vYP9YObiDk9ag;s1mSAWU?2+z&Zq^AKWBa^>i^OuU2L+s4Mgo|x{OYs%uYk))Gzc-pMU;R zx$OT#>w%LHxz0)W9ZRVxux-U7H`Bs@E+-V{y5u4{*R8>LDy!i0zowhLTGXyQ7z~r7 zcd^+SXZ}g~ohPs5K0fWtE2{J+KJP!A20<79>Pe%xJo9>~37jwfC=Jx)bpx93@8D-F zUBrFbE*74sJh12H8%(<2<3(tge>5QAC(KiFlvAEa%?k$&@AsR%?pTgV{t9bM^1lD) zk_%;ph>gmw>vKnOqXu z^Tn=f!w;`Qqr3O@#P23SkKLTNzXlMq`0up(3l+S( zYo^TPn5C+QA@zIccYLhY%M*#OgxKg}f&H)L{z*ZIv))HPTz0vn1O&axe+DB%K3zL# z1ig4{Eq+9G6eH7+jHiwdXmQb=_edOmBEl<|b(P3R`)4*&X&t8orO@h%@geQj>|Y5J z;}cfEPY@Xf5<4LZCMG6U70UadYl0K`oJG^a$WLcSag}}_hnT}F_#Cy(AD@2wN{mr7 zq2}G6*nqwFQw&@w-m%9=%5)ZYf^Swm8vISpX#AgU^wlPSQSA9_KSTF38F6e7{jYa^ zYyV^#eMg_y0qtv&vEr;Kx6@>P3QKpt#wI)!P0gvGe~`&p-JD;g|BAd{=oPHsvhe3dLQi^$+ z*{)wC>if@(dnW34yE0V4II|Aowy1kd0QGPF-+RG`59J*86qgb%)IC$@8?IB0>dn}9 z8b7wa{QJ=c1PH^u1kV@`Tox8}&qyx#oN4~Ym181U$7~g1gniqpc^yw?W@9B*@wP6w z*n2ph^a`4&U;qr9h^f(e$HxnVX2?p;SI~j3V%6AXrQr7si^~K>^h==Xn}|@=C>an~ z8#>$JC9}6SzqlH5gbp6e#vM( z?uQE|*wN4GRl%dO=;q8WK-gP(1**N`f$x$F?u_nWW^B?SEvz}>;95d)ZxA#sIY>z8 zr$z>|W_+2^8h2$(kR{$>BO}te0q?ybB7l@v7gYeVlLZt5Xfheq>J_^Q?cwJFOZFGJ zD}ikmseCSR^nxFAX<6$1w$k(4lu$kGV98j(wB5dqCmPBpXE0IFjX$~f%Xq4?)B>Lm zGC0UiZG`iUX2GjszkoLeoq0){)}3teXbZk36QAprwQ-hsCr1L+nwGr-KJ#%_V%5=G zXkcXY7KwWNNM?NOfj3N@U zE&>RSMr0C*3C^}b2b@kGb)Q75bIn^UNu6tvR;N?(Xvm5+V0`B#euKik=_7-QSyb?^ zV#FXV$ADCH$kIo*ZB?&cBVkM}jlWKdxhH{;PFkzSZ3vt3-zt96w^1HAoxA?S4|2i| z8el-rkWB23#n87?EM%+suDvu)u~-5t-6$Vp6|4wSw^~RS5Dobntd9VGf7=VdlnqP- zrgp8Q)9BW3=Az4_86lLx(5mRl+=&pO24A3cY1XfkCv*?~J>tf&U`fs7MkR4|?2Uvj zGY0z`kJO}|t;1c{Nm^<&mV<&TK*pGk2GLsFmFq*)BwEOkG;~IgJ7GW!?eV(+{59>Y zr$YBIODTNMgP~t&+ocB#?JwDdEClq7S>18YH2siCHNSw;SVC#RPb(iGE8M4+pyyzZ zP5K882W3TWcsZCVjW2Cp3Bm}l(_aQ$5gB7fjxDA_=MJs$YHSH1=bO+JvDWnwU$^6IwxtT(PpL}A#NS=)z0>sR%t$O!la>i$M5%uj zrri@B_VasoY#AJwRa%wO|FEaiIMey*HLXhjBUiFE)!I5$24Jz&=&YEQT1A(D5?K^(D z=W6x!HiAA6>5uc@Pq)7gZhCw@q>&HY|6$*Tyv0msa+zB}Kal^^fasCiJkUsT=C8RA zDyC6#t#NxxbTmwaNuBd%3xD2-3MQBf5g?N@F~(vZOOk{SNMIO{+Xg=V+-gB+L4wc7 z{|SHXNMt2G(22@viN{Y8j9llUxW`g+csAUdHRPAt3Qc|c5TG=2PxY8Hoyqyu1=KAlk(LeA2tHGHUQ)BRn!QFxJUPhv0#%Y1w~3fN%GLl1`@3SpqJ5r|*_ zWz+DIfHA7QP{D(PGQZdA?CK5sKdY_oS@?f#)gBL}4uaOD;(p%Nb&x;?>(`r5@%i!l zq$A!0k?54(6d_J_1n!4E5wfKrf1=PS)q0LS!ebKv^~L{VSQ~@H`we|QB8j@cu}t-5 z@uCS09+~t$he?70VYBy$-q%5w1qXZ%Ol|<_L&(grKuaOyOnZxtlb$jlx?Nwr;BjZc zXxB#gD%dMBK&z6sWh%~;Cg5LoCLY*;JJ^zH;k9Hcigy{&lVPB^J*kNv+s6qndu)8W z2zq{#&W9MB5dUpdL;nmZU^vB5>Osz_k`YcjF684NU?lNmY^#n~@@s8TYO`39y@f>~ z_V34ll%%0-dr2G5sppnJcl{X&T5cVc=q|U^(5kdA8eu;V2}&q zB=u}19(UTN&VI~)8FngVK+1g~0Lx>DPG(>c$0CO(J9^&*iH~36WZ%p2pH#WoIHqz? z7%qXubZ*vghv#hyhvgZ76UVjqqa`9c7E7e@qA*NAN0KEqELNNd=A&}O4Hp$I}g?8p~|vdF_1XK*RebJh75a zsGZWL>}~x0yV{}0LBwz z)|#qisB#wkUiVt$f#up00c;T2Pq1C3FU9~aC|Gu->gCvV@h0cd!&awLdPbBl>2?37 zaP8OfeC+V#n$GO;m4EZoo7%D-NgLgTj&@9?b0dQle}0aT(f?1G(gS34l?S3mtaOfK zJrl7Mmvm?TZZ=@5kbwblA(Yf`y;T(8^8?edQUWJi!Ttk?_?L%xzz=)@NE#n1EK1cK z1EwrnHy6J2$pmMETrVDk-#EUhHK?ekh5cf_BMq*025rK^gBy@UDZwl1T>>2`Jj^+6 zI%%-tIr(u*PT*t&!eFREnODc(^pSwM8i)hNh_sz1drUES$QwIjzT9u;Wn+<@sDy-1 z+nybUOXa`U#r#E>Yrr#T;+gF5!dq`svKIFw3U?5kG6e7-4ywRI-S^b zNYWLrI*Hm9`W+#ysN_s*pazlSO#0NrPDe(4l);2E=-<=$RlmgYh`;-LKH*$*HDu&N zv^>oiaR6;-6))SZ%r1Ei17=KyYuB(gAr;Y(ZWTXZFw3T$mk)a(H&~+KGY?!iPQ?9x zYFg>}&;s=D&-kr<|G%P`mRxbS*P!>8g06ttFIOG0K*u0F5v_m_;J$n`yvx_&)yqmz zIEAlh_=&a+ij;6RuU@NjTA!y+W$`JnfUQ~VsjQYb{H{VH#(-DHfR>_CBzk|gzB z6u_aE#m=}bXhPP@+0jdX`|SkQ7!JTglg0tL8(hKZ& zzpSYdLgXcNC^9jO#yLy;wxd(j>#dhCh?)>+)I`C+D?Iaz!Z-|XmK?L-Un=blg=HKg%iU;+z$hckrTO8#^Enks&i2g%*T={u#apy z$e))dj0!Ivo*osk)JJIqy|{t|03TN?=04lexqPqa zx9|0z`dVZ^e|fHNZYTq~S#Qp2l|lI1>-)3I3FOy{MCmUKbA;E{6in!}uqdMw=KL3_ zpDYCbim=*qR5K%7TDx8;4UWC|dt_8(WYw6>-2m+Eui3$Js4@uHYG%rrZS`-jD+n+` zDN$8M|VX>(?$efU+xm?l=8x$<*(Hl4tLlKh4BP`^l7eli`82$ zQ$xa}V3sgdbOYTWMJ=kY@Hlp9vut*Ef?_n4d~ z>V>n-$b@R8wbQrGvHpq$zMH`8P1urRG-p-{YS|?atsb2 zqho>huicrdQJ9gDLk$_mdaH<0^=s!Bmo`V=WjY#~$xfhR}mhzbdwDp)`1 zKlVH6`rSLNI_cC$7Q>E|IS@W_7u(`1uBOwK{#Y057J z?9`~n0@I|k=_1IWXVfxnKI7!8XsoAFl^<~ef_$I%{z)hrwL78yWAMjB`x?oghh<{C zP;~9J*Szm%M{AF6mD;SeP;hwVe^FInsRiVVO2-=GHPS5G(5Q1%o$1pRZW(We5SWNq zNK4(3%M8>|3K#bOFBx1K1#mMYo0c0$?MQEt_fQaa@xXKCB&r?PfJH7z4fD=}wOMke zM9Pbeeey@=j(o$3%}~f;{V|M|>n;=@{E--tF60r+7*kf0dYx23N64bm6oYIUkTi+D z@>FM9EXi?)io!miefs|ywp^oNyZMLhboOfXz#-=cnxKfA5OgAjpHqa>naCMz$Pav9 z|4SS6_#!1+y$sDCn?nei8Ad^d;4nm0s^Jx;^=5qUDEi>AuxV-Q%9lYfn%h@Tu!qB3naS(Z1#0iQic1YOoZ z-7r-c+^L3`&3Nc~f`bnHmwLP{QIz;YRbLGnj<&NJTx-Ss94n!-RT znrUi~7v$KK#&xFCa<#X|fT4hjKnxgCEisewzWj3X zMFck);N`B)IcUCIWBczXnq`|AE-436k&#V7>MgqAcoAV)^k_@mpfh|i{E|P;An{E} z@`M#dwX0oqoHGSnhn$LjjRy)%m8ZkrAoB!J-r#niqpTe{LRJ`C}0znbhcdk^3#}SSD}{@tVQrtSRFXDSz z9nywwo%B}bJaQs|&oFDobH@@#z5V-<5o1*xzDj@slF?4T(pX*36n(GG%<7F5Cjw1)0HI*l)g)*@Ia0&O zG%O>b%tl{(8jIUnLuLHQ@aq;KY48c|SH)b83lxG=H+5*d{V5isrRhg6edMJ6F~%c0 zU_X0o`n!dBA$}6)gOIgM(x^fioStz^iiC0;NJS`%;K3>NB|2iL)9S~-Asg?)+1!P( z6~yx%L)`au;^AZ;e`}s=hxD2lR8>g4Cl^7m*5O70Pcm5pJl!+9*;iw+faZ?^%u_=2 z+Ff=ChD%cUU}$>U{0Idsdu#_o{mJi>{$BO>TaVACvvHiVQ5)9vvfB@Mfh3Pd={p;N z<}(GJOfqx;eiV*#J-fL)SM0(P%*S^>%F+fEFTJk*)upz2a^6CNRRKWUc$tq znf0=IuJ{X{<&wC_;L2$ZU$N3Vzxzo30ut%{S#B=({!%1nX1lW_pG#e%8k^kcK$t{V z*7IMfyH80HYUEOV_On0;23A${Y9Wex@PBfMa+OPRgu(wIgPpL;nFxU!wJZM=Q6@E{ zqHQ1RL6mr+ur~sDJec$&d8_>jp_NL^tJE!K-=OmP5x;!yDbW4rEcrUrZgNMeJD=Gg z)q5Xluv993aj7Hl0dQRWDULeFyfiCp)8Xtt&hgIL=_2j+++41^|27!*Y6&XcLs22T zWs=DoI6}*0F&S>a_(jDG{E$;%ESeKx!kTL5)tG)8b5mB3I>@qcHgZRKjze$x0gA&E+|nG2EUW9)f4vgK;0rx z>-uJm!}x13FnD(jkN|pnp*q{L4;bau|7+(i*b#IeOWX1S7S zB|b-3X)BM6W^0yZ7hvZ92|pCT>&u$i^$FgR@3w%Xgg`R<4aL$fBU%73}3Nt11D zTkPrEo4w_(j2&d)w0?v2?j7^V6Fqx$_rd5`PtAlylNAa3VHtU(IH6eYV3IDdGaAyx zu%UuOoue(;g>_hf&R|laL>@jD+=(2O17If@{1cN+qT)J@T+(BVuI~t^Yu9SMTc`cV zev{HBF5NL9Z9B;Kb2pRC&-{P?3zojQUh^eiR;flQndE?R&kyY$8Lm}HtANyDTeT_VcjCOs?|-)NM> zw*p-cb~)3crPN=A4e98vS1{>3jMY_X)-|r>;JL$RuNj)OvT@J&exnj`z4h+h+O48H zjhH3%Z(ij}72zUw=W|}SToc8l z&>2`A-e_y{@XgH{ZR!0jd%e|i?QCkYxp~v$At8rr*WTZ-?y?GBT{^J;%$6;wB|l%? zpuy5grEi`;=jOoab7#-ox_M(C#kqer&^{rWKn@yJYA3y$0{o8do3Twx8Hpjag)v28y~$c$G|KIsO1Ith)^3Q%?i zflE!8&d4W2V6n6nkjZ|Em~=@4`ynRlMUC${E~Q7@LXckZi|ch9)PK~}dsb_W7CqYz zncQ!p4qqLZpbKg>Qg7HJco5E1X3z-*@_)!&Uitp}!48AL%?Zb@Vp3PT^7;l1R)V@17XE`T1{bp` zOj0HeEJ5LLi^xSr)44i&Kqyl}oI+HL*w73H!0g0HVw|qN$0TP!va8a4XhPLCLt71+ z+J4yVs8I`g#4oPaDymdSi->-sd&Q@9k6Y4tMDl=1OM;vCU$b$C*g=XFDRS|`dFJT_ z>O5#pW&rd2xpPW=Rnm0mpgW%vzpRoG(J1qRN+;&ax3K6(I};M@M>umjJ*>`8wSvwE z2A(fd=2WF}sg+Bg-?tB6y?%k3p6AW@fPjQNZ=Tz|%ToiVnWunl@JKla0mO19XG=`2 zc4JPnKx?4|69(!q>8Wu&62Ta0J;{6O4FtsCn0=t2ap;0X(aP3zC_q3zFxzZPSW;m|M+C!^ba~PM8PM-p`g;31U(5Vk#mqTrAwVs29wT1%hgHCZr@dJ)3ZhY8EuCw=={B| z%gD6WgBOMOog3P7V)5FI7yYnCdUyD-N_<&-^QKL=Z{6Cmc{42U*s+~66#WNXYSAKJ zz?SMh;kB#cm=rn#EBf}D_THPb|L?UK<%(Q7d2(K*lE!xlATNUa`}O(73!dJa$=-f# z^neBLy*c%@fazsF1KXSZ)yTkU{Ra4ICypQO(W5)AFIwdD^z@Zrot`{?tYgP-#D>5# zPl-R-?CaA%odU}i{7+mtAwIrE^X72ayLWCM*uM{sBQZ(d?`EgFFNz{PLYM@_2qr5# z^vbD($N`uHY23bdaI4PkhU>bFS<`d;#-8K#oklJ$RD-FG`T*%a;}T;`5Cg^*+F)NxxxI{eZ@!^br|2y>Q_IT)A@P3LH3HwoIv{ z#Obn_ymR~3pg{ux$#4dyO__o#0sP~~$CoQp8qQnQ;Gi^}?iItNT#Qt((HZPjN9KbV zvRG&L{=2zp_By}7LOOmsXy)$k7aWS7{o|J*b*Icp@zlZHl&e>- zh+$Ij41n{NP_x2=fxQD&vog3F@ht*k`_sx`m zfJudN16=*&EA!7lPQ#?^5KDf!5A1@qg3KIP(2#n)p02Gk<~&S-RBiT6Sg#@DmYK>n zY8*dpu3ue|E@2X6--h)QKhM4FwSbKQ0f0%-}=`Ab_N7&`uyMV)yiy7S#;*mLF)LiV>@=N zj*FT50itkE>Pr#?>o+^{qK)AO~WSl){RKUY$$Ho%*wk#@%4Df`BOt zm{c8l^?ywI^A)KAlXvgjP973Hy-4mI|9bsEK!EvQuOBP+{-LVHO|?rOEnn!Te7TN* z3VkhL#i#!sQ#bh1*)x8A23|Qm>CDSYcw7O5*xLdNq*~Xxi_%{(`Q!=sPB~t#T)rfV zNx3tyIdw@)<)TXq|NBVZH&4-#p7Y>a1_YdV`~QCY^k3tOzc*)Kznhk8{(lB?oIfeo zdFFjCzN%i?d38#S zkElKOyP>C#9er8OfWKhULYeubjH=o@q;erP8y~TxKg&_FLrhsfso*ez?8W4hCpNIq za=cu=bWseGE}em!H?CizsEg;$-mqMI>CS+^VA5=2lQNQF9yCfRSGluDp{#h#06%*P z50lBnu%2U)oocJqdziHS{PV4wH*&mOpw5e7^3^#5u3{30#39wN7+&16U2%E`4*77* z*wM34Dx*uKn~(X3g$G4Zg$oy+FtiWPne-z}K7RD*y5(Aqm-FY&ied8AIRma@l5W^w zsD{%82{AnBPtrBCWcCh?Ql~aWq#@^oSf#rtg%E2WNx(RhxpL*ADS2#ug~>+`A6~n9 zCCAIzGpEHc`RbejcWqZ8rR7OX5@vBBf@2Fk8i2kxq!W}P2RUV;hx|C9bi<~D-}G4a zb=%Ypn-2kEqX#dB<>8~(z)QnoSH{F`4QVo~N$cb$Z5Fibn9`%)!iG&p+`IpC`>x5u zV^CFd?(lGZdb?c=g53Y1D&y81{&$MqkVTa~Zan9bXc z@7Z?|RyJs}u5yD_Ushe(to^ck_wRk(Zt=v#?MqjgJ}RxN(r8`pA*-?lliFM@29`Nq z{AjM0MCw8XMY@easo7Yd^qfzFwtp6!@o~Aux|GyLEf<7$SkwP|!^h>81U1=QsItCT z<)l7cqEmE+f`Mr*y6vbEwx&wh>bfm-fXQYZei$)vXXQrAOV(Tw)OZ~n>e$Ki*@8(j zdhF1_6DN-6V1X|vdlDW!dYFU7zh=U!D_&J1OIPi!9F|%oO!wWWm0veQ96C^q<$;w{>Ao(#5Yhu(`j^iJ(FY-Oi@E5bW1*|YyA zkPf|d6Q-}tE=+2oN%musVWYcu-@0eN`M8<#Sh;l_>d|q#zAb(_u62+KA$R{7vv2DiCX-7-8I$SF9%$E_y`OEg%OUw_QF81~IQjw>NHQjdN%$3Z; z=ZlB$hHXHKhUxhGXtWFj>C!`qJP0RJ!BY z;^9Bue{c_14o=whRpik!oz8z1d8GHG6Q$Z6E7OUB|J`GM#0{n{GL~#}0Cud^c@wTF z9=;W}0V&pOi?d5fxX*DsSV-+4GyPzug=v`7)G}(8O-_>n~#+G=3rot%VncZ^_zC8bjPFRx?d>QjjGhaj<t3uw;U-Gb*Mt$Yk@t_wjYsIHJ*9oZ0Sz>aeb$8NBd4YUarUK3VpAZ`ex6GGnZh4 zl9AiW_qklU)4t8SPv9`BaB|bXMo6V+<=|jV$CjJ@YVGp+Q04CG79V_4WWKhiA%0sSn z9J`B+$-rKRVSS~+mpjI1UcGs{Y>&M`!*5rLz7Ugqbmr<)6$YHHH0WaQ$yvLU?QVqa zL12NyRR<5STHk*+H@J}uL6(L|O>zb%Db~kb5syi$GqF11TE zR~h*e{q#|lk*8;_KE!NXd&GX&ruK|ywG)4>8h3f|=A%xS{LWN6@fUch(szg1oUV*B z2|CkkHmjKYBaKNoUz%v*ZGnr;smtHN#la?vGwGXo0F&sRnyD*|#~6dhoUJzgDoFJS z*Q$=a5E6TcgSwe(4hD@pS$*R5YVlWU4c~eH(LMI7t>U*=9e1%>{N+X?nVgEfn+pke z7-V`X?uxL~L=ztfi!sN{=Hf6IvPeKoOY3<6lXS?(PSUa8OwC$nnziBRto27$?>fPO z9LnmQ$KchOYY$zyd4uy3teLs?APDo)?8HgAoFC{1}U+cK4EEMDu#N>s4L+COd_d=*-pA+wt3F5qkS5j`z>0UFtT?2)bz+u2~6svLVb!!qu$%^!qy@lA9ADW^B6GbYO`4k$pt)sNkXe* zp;)9ldO@RE?J2M^6+CFi(ty;kGGG%PyE+&=VHegdiGim3ywC6TW4h}mj zk7?5Waf!ZVs&)vMBv`NpgU(>2#hlK@rv_0KlF+caIXY4Tlf>IJZt>aYfm^vrowG+% zBr+G6og~rOUbE4)rS2#?z&sq2Ik@{S8{368n_?(aD|a!8r$Du7jz%wh&n%`%=kEz1 z#GoraW+zVCW=9X%$%8bh(| zHKGYDYMn`pp4ht|SqB$z*(^$3Ha6eCN#D#$^>P=Jsx1xKlW3w(A~PKXA7q>sl`bHz zpwwbDYiMLFu^IKNNn;W~>+?{s9SbIuiNFe*?;o5= zxJjz2t+n3uKV`C1tZiZCvd>&U`e{psSW{xQ?3L8oZej4c9zCb zT#G{WsH^|mE|i_#Sx^|nX&ud}UMcMfX?kFIa%(3ObtF zpq`?RQRd{+%;j-mn>{u>iiz5ArJraVhu4}8UfkJX%b2qIe_9us7yIakPGvDnG1Y=B zA%CCvtx9`ybCVfAMus|R{(pLffv+vAt)Q)}t){K%)()bb+BP3C_l-u5mmTOJ!_ja%JWx zw#w?thEeMnB)!=nK=XJg(9-) z91mf(-!jbt)PlHm5VcIzZ@okYQ(s?S7Z(@j=hZPV67Y!~yu7^p{QT_g?JX@Wzv<^C zSxp;$@grgoQkrsd>&~tFP2C|BF|};&_LOnPjGN9W#YsX;T+GYg)zwu3F==r!c4Oft zw$pef%a>_WJ+>@~t=?20GKwxm;HW~EbX4lo#_33)oL|-%3mlqnh_)k!pLgTa` zFm~J=1%N-n1+g~G)Gy_6F4%TZV7`HGrRrQNk@*OD@M*CVfg|B;8Vs6mXJ`!0|7@yMkXdE#NTgVU5OP0M{uQ>w~p@) zim5A0+cT&3Gf!a-KBGoIdBlJ?=}{J`u#wHilhPU9xqf{E%ZK93zynst&Ep}x0}W5| zz)vlY?88qfJEUv8kxKZAqWlhj9Vg`&Vbik01}tSVT0AIO`#o_+ZIu z_~fwjD8=aLaAYM^e9{0QD*hCRszf^yA=Iy`u5M;-E+H=N?^V}zP#TjmCoL8~)T(Zs z*+3YPiFjxi+&v3FKf#9Okm-QX&GPngs7}gaj5HrFFO~kM9J^Y9X^h72EcoC9#>Vxz zjI4!WDrIma-f!hB6pYGKT+L36SA(xSF>nd1+p+q=FxDi1%3Q{RnVA`U>a%lOTYKpZ zS)_td?Xz(>G&FQFT6*Q>ONhYY;^M`nrO)_j?&64m@y6HAPBAGwUfkq~SCI}l z-(#+g+KJ*Et?wi8`1o%|MKq^mQ({b<{c3 zf8EYEYTT&L1*4)dnF2FIlA~;$Xp%cO^;4ZC@nb-rRgzj;RyJy3Dxr^wC_H zPC0f!lSKCShkNxV;e*-w(~JxB!#1E4hImhao539raD4* zOzA+~;^kdgk*~BsO;>1q$TdcpiZv`E@|J1}ni?l{@6juXIt(&r*Rt;vZ{pBVhtb%t zVD)O@7-0fMwiH;(3l4Q~DV+1#F* zk+$LLO&`ovp?v0vsijWd9*!;MKVau*-L&yakE&FTJXz{}me$siB@L4kIbx|gU2kb! z&4Fg2qCm?uq0q6hu?!xOk#PZY;w16#Q4fGjUO*am9VG}qmc!cryyi-L_H=P^5xq)I zO>};^84&FX3<)f~IKOa=)M#ndr$w1HFgD)V*kR-E_P11Vm72o&TIW>aS~jkP z3BdzAw(+=zdCVkrd0Rz!dFQY49E7$D)?jD?yL6O;=ljRcKPl6;Ndc_{mVd@$)A!9X zO5|9UhNmToczQ8oB(ijTCm)nK2Ne@$()C3OOh2o`sr8>`;R-f};dWP7Gi&3y)fT=M zDrct}OZz^IC*9p~L43wFe$g==PrJLZk&7AIOGu2NyOE0(=kVm3Po?$epY2&*YTkLCFwU zn0K6m!$f9n&QJt`yZbv$tz?=($y-!u|KYP_Y#hmu@BJmIN0vB;aJOyE^9`Qk4KfLM zLlWW3N0xi2NeuMEH1JSG#ogTvg+cw$bZ4O%4CtS|8pBq%FRo8Xu*iY>`UhucpZW5m zX~ob5gI;deLtF63@Zk2H;q%A|F)?uoBm3G%)NZKI>+!>0-aeEL zs6HrA(xa(+j5u#_w=@*ys`;gD&6u@&=M5pLiBg!}dNxe0l$i<7NTe{ow6&o){|t8y z{Ln8O?nopLjmUwo#k9_n3ke<2ry|jrWK+ZNJYrte2rDk3Mglq`84l4wDK|k)DrYSU zyX{60SB4zqXXGg1b=SwELlNWCIch0?!|m2xj?{u<7bZ#D?baeH!+Ok(F0E{UQnqox zJKVfYb@y~*WgM@do|vANw&5RBG%zqYS!NFUnsmx0mr%UU7~4vtEbG%*+Ty-%Va4(g z=pz+KX(Val_cYOzAbRsOHMeaIGWOnQ5{SbAX)J?Ci4h)P0N4tE>O`>foN}xD@B!)2 zPu|hVZps+E_QFjXHPfKIg*qq8v+FgBumK6W5DhYNa(p5zEK*!V=fZL-k@KUV0YOPT zQ6jRD(_802KmTqw*G88=0wqyjLSHmkocmzj{SeU7Vd}9iD(maxBM}p#W2Hqu9}R^+ z8O=wG9h-}Ca6px%m?HM^5TiX|eMaJn8=S0?M`fY~VmC;t#+4)|W24n4uFL9vMCXvg3(u=j)>25Su!xs-jl6 zmzUQis=Uv4@h=>|2CWF~f67C#T(Br>!nxu)w{>(>{h$R(!$A8XEc1%2Y}f8BoJ{XRX{^WmRQ{TPC_6e5HyUms7wZoS&ZaTkTI{UVXmGJk!Ez& zre!>}go5~oX-@w{F|~q{((N}-sWR!6VQ*D0Z(2G!^`t$f$1dlPSIyp+x@$Lu&-38~ z>3x0Y`HRWe(hn2D>}*|+)mCjLJsvBIN2Q;1DkOd`*o)*AOagPe>Rb6386$_R*MPcu zI2ki{_m6cyYL;*&?FITQCJq?eF>4D;2UR4*aB2xHxJb49ocRYJQwf9%ztzmeT6I@2??++R! z#3Ur&zkiQIAy5f3`w|z#CFn>(5s*MbL%MCGo%lGA8KhCq3N@V)PBp9FSA6~>03YV@!P85m;2;H zBW9)h#mHgijs2&~M_xv~VZh@vg(w@rDI?kzLm0|$O%>#)FMOm$)3N2{;dhOiZoIJj zRaG}|do8xh%rsby%JRO7xOxxgVssYm%JLN%oQCwvB?L0*TG_}Mkei@K1a*#78(HK7 z0o8pp2v2Br@AL3*ZfF`bw{!xY{rRO#9t#s2IvUc`^L^M*2=e%`>ptB;Z@-g@PTGJ6 zQD!PRfRS|ACS(eYIT*cin$~qbNIJeGGI2-(gDviz*VGEr1BLv1K(E-oSrOfcOh1lb zj14V(`hHe$GnFXl3pkUb5C--lPahitlJ`Oz6mpgsK84>^8nigdS&X5{z;^kKCE4V>v@q9tF6O67 znDC0)URfyWS7OshX||00E3F8c0*z>ST0~0Ro0;Exf(dvr{%=M3!iyN&^QkYD)phC{ zoWYBj*HTV>4KIgn^}CP9td0nM{1lgxMC^r5S{4?T(x0N)<5snS`kLAx3GMK%#qHQ8 zIa%4o>>L&2AL=A(?p9}I(j>sx)zANu&;an{(&CS$yeuUpC6$SmR`#x6`2sYCe9%Cg z0jEaScZA!Rjiwn2{(mpqcEjmaV5N&c>?A^NG5y{(;p1HhSwIW`0bxP7{D}sYaaok4 zJr)`oTH4i#Bf_J<(zdBA2RyBE*4D|>%go@RCGg*K%1Lp>mx*6r?cmti`1pjFkprl` zA7vfRE};2#b&I>$qn>ePcnIM@euGT7wgJM;5U2@~Bg9_%8u z%R1=@j=fCIV?XPPt;=dVYtmGXmlv)JUcX*iQS`b${!`&!(@EQ+M`>gcWU|@q_0Kvx zdvxcGdvt%9fjp$`i<+V<7gyfHpPI|SvyHD{S^P`XvDq0IHok7!!zUwBxZ2FM0caeR zHq=?eus?sys0M~%osFlU_7Z-t2Ntd7yN@_2|B!H92 ztMB4xPImI*_A*ZxQ+hOoBKEn@I^$VRlITxc9)HL-OpjR!gWpuS7v-8bIz?MRrB5Np8i*X62%B$Ra&);v`(8%Ab3BP=tm|CxFnDw;QA?8-Z{Ih^z5Rd=gYGu1m)^j&dl zQ=K>tB~7QCFm6CjPbNyH!)pGF1s{bji|5DY5H9sHS-%VmHYX7(aa_2Uv?NtfNT$Pb zmm4WS-yy=Gme3ApS@g0fWAqVQ9@IEw?;>ER8o~ZVIm%^aA7Wh&l|5t5q~PBqF>&$r zfU@wba<7z_UbOw(kflWc?8?z|tMXTK46Rrh=2C}jOm~(~wTU+67xXcIoxz+PxsyVM z%jgeL_Wf3DFXkBLwd$kzcB%=&tyP?&pIKan$y2A{RP13#{4k?Mak{cdnDDK=n)qsR z{PBw1T&-<hX05sL$6R)YBYY+o(y@@w_-Q7#gIm~E83AjG zGz*`u)rL#>ei$8PgiGDpv2;C?tI+skTfjKgZflOw2d~URj{RBT4yxu`~R)W1^qEn+N5`}4Ymn3 zm8n^2ojQgDEPfgG(M?_HWv3GMK~1i*^6#c5<)74A^hqUUM>2O{Hb{<}-wNr9yw8t( zbXP)Vp3S@cQM7f)GT-crDpj}zo80LE`L_FM-YDtqqu_$N3BTO4uqf+e6a#tJd!y2i z_V-2v3a|d%-RD}1GL^;iWf&Z@$-x0+%UdA|@0TLq^qs#mYbHs3B5bFlzcaR7w(9c9 zuRKn0&7EBYxd@)2Knlu{;;{TAy8b^~)-JYt^FBN6-?vvyC@dr;RS;ExXq#J6KniL~ zZmZzkqRPqk8}QxIY~Dx5Eqs!CP~Q$i_i9he(*VL11L%*{?#@b$Nh!?rM$_BzO3mef zqK(2t)0~L=N}BHWLjj2kZc9&Z{ZrhUNzcjoh%S5YdDy9LQk?j|mm!C?9oJ*Lj9u?B zvVh(l%fpHSKi4z5kKWtx-vjM``!0skt)9147%f;`wby=%U;5llFr@GJuf?#7%vsJ~ zc5jTxgmKKTvy}g-9}V|O-lJ2he|xQAS76gq;#L@{CldzNdmXJBIO5-U=5YY$0aoeX+WXT4qz@m5mknz6t4R)PWKJsQ*pI+Gk2cwjcr6*i^;*=)_*hc@N`# zSX5#+rQ@a{OYpoWjD4J5Qv37vy4fGyGOpk*N`xn~nm{i2ehy&Y3hb+?VHW>*z{x#G z4*dANkd^@1^Cc_6bD4ficU%DzkK0rG)hQ&0Wq(O_AC0;wv1B^ zC#Gj}VO4aIyraZq*c&t`?)z08hm0K)B81a?@AvKyJi3Mq^GhH5;^<=4Y>N^7BlOyO`BR8PBlr)kGY zD&iG1WrkCPUo^YxeoAK0YCno>OGye~XU7N)^$p7u|9eD(pAu}O5QVg!nUw@S>a@#J zhF@*sS~4Re6+e+Q5BPSB?75~j0R=obG_U<{%$1kZrnr}Wgc0PoWtzI_=m0Oa!DQe8 zvta(_fS(2eaGEFsw0};1|B%0922A4M96D5N2%zErc>cC{w%k#_O9qU!3Q60(U=iu@ z{AixiM_pGsgJ99n=wQfuMZO>Q-1*10*N~d9r)F?XO^vbt@o^1-iTlh-Ox5|rimu5v z#KY%A{x17o_r#b~N5jK^3L-}Aup2{12JlfAxVx9}^7q{Hkrw2z|9S3n?p$H-iPTCo zdUS93z2oz{)9#0!-!UG<57(BKhCH(4mP{{w@<1-;R{N%5A^~5DPReCP5^o6$+)*I_ zj@z8rz{MFjR(o&$mLLvswM{+xT-WnvFqZ~VR~IkJCx9gMuJq_^j0{+BdPLXAdo9oN z==eCBqSw^&@{Zt!PS_pT3UyHza2kQn+bQM!QS~9HU(7$VWSAxJ+V^_qWm*Iwj;!MDFs7C>trzgKI$z0VeXoo&C~9@Jn^h*8_5m=g@@0)b{IPi%Uw zq9JK%x~5KEkp1PaUlKojywv%HxMJPk&40>r$%emS!S~JINVJvI&q}vXk5_&*l=uz*!O67QlvcUtL7kR z%k|zpWAfPIu#e2sZS9FM9KIQS?Z!D!7Wh5+5H&A?Et+c4f`f>P&Qs&;A_~ zFi#iQgSpk@>WZbzoPq!!@qUI8DD9=vhHik##q~-XFG-jti<19>0KdOJe}gD$ARDeg z8~!*@6nv%igoaaylm{ZN0twJ>)!FUmqoJdK6NJo!lj0d`9Ab;lPXWjw@H1j}wsEd$RT@6(LbbTZ+q*Ni^HNLCX50I&O* zHd7f!YzqQ#1tx5(#qoGj$(l(ZY!uG#NQ%BlWM8wj{%E4}wf&6TtI8?x?|*+VYL&ru z@Q=_B^sy=yW2#zobi8;~FlaQ?G#4V(eR5HuR665-aCXF=3fSoR(%CT&z6osmIpx(h z^!FM#sm$D&qqLrC+tKmyv5EJYvel+BrDnymLr|Jio!&+gFWxWs^O9(WDn9&xM1RST z+F~w#eqT27lX)h)LCRdLQ54`gG4YSylg&7afNy`%_zwsSZcv6k0ql$e;06`-Wly|M zKR!DZ#m3Qa)TRX&?e`hJ=!RbW(mWs-NDQcLn>gC(4G8>qSU||aH%o|(A8v%0V1iu{ z1r4WFSzJvFkM2QapQ~qVWMssP8u1iAOT#vni28;RIsm5>u^!bUhcng4*c87$pWzfVAtVaJ_z7N*gQ`Z-w1rrJhKWRjAnNWNt^6+W{_s#uwqtM zR!`>73(P6!eszCfPlT1km*9ZdQSKwA!?#ZBp4BZ*m`oTo3Z1@j%1>tmzSKKpO=$h z^fCir99DGxN+=C;cvF~|Sem-LxM&Vv;%!<;!TE7K*!T0>ecd_$Gdd!vaKl*N#l;LH zJ*=ECIXycZHph66g{m7Y#av@|f9E;_d$i%QU$U$JJ zj9-PRb?y)eR$ty^4(=!qI=UoN?<&?>SXf!8y13AEXIe?rECaLR3}+6F+=3GgYPIN2j6|CFj#bHqjICCloEX^nwluaJSbg zvYa$M0NsCKlO)45XO8X=!o%U^Mqd%$ZUE_%IGUOz<_8VrL^w?^*?~U-j$6u}cM!}_ zR;g-3Wo7d}Dn4w|CQ_ET{b9c!VyE24C<*Ii?>YbV2;I0@U?BMS_XC|wG<>}44c-}1 zFid-)>ilY8v{&qs)X0b`HtkN)(8%i>YTSdOZr|{S=Z+?fmUPUPu6*r9B85B6zEway}rJfYoc9ALOA$THgzp`bo%ip zFlCX;Ia*ca8G*vz_5RY6T=)jBo6D62UHaYaoBM`$hdhHS86kdrvkFOXjlur@VZ=?` zH~K|;@3YyY6HKP%BN!KlDRapurzYea`5(#Yz>JHzqJ>v zVX-IBIFiGb9A@_hOO5^Xd2L3|kf+bE8)*{jZf>ORYCvQX$;wQt9@ zzi6qt4U3W>m0Rz(tQ=NZO9+^p^Vjtp4WcBAdj%)Lots?)R5u=kZ6;f0abeXzfuR(W z1X#CB=dJG#|0v2jx@=H(ZK`7#M5`>vZ z9Qh0qY`OR7BUCrGo>-c@O52OXZE$rFCUZ6Z{)$RFHzlrKZj-mM@86jpELQ1>Sx8jW z`izc#qhI#C{h{vdAhmn5eoXN3J2K~svgSF($8H#tUdOs7$T;i~t-voH7Itv3%Xc|` z248WUlsG4%#_j&;@&c$#A_idZYwkqfhaIHecLO0(Mi%5D%bc~Up(%wT;{DrV6_*{Q zw>*IcJU;Tk$BkGy2je02=`g1=6#~5xXl<_TwZ16`r#7mlbt|E@0C>|^$}$EUR2jM9 z*x={Jsr5*M@G_aN+56!7{uK1U3}ZI-GlU$&d_=ar^}wMLRL8AyX`#w0#x-Lat0~D@ zr0?(J`s?}8y$rnt7dD=e@m%p=>ppTZuQE_#Ew(RlLM=Ggr>tGfPg=&)ntxOc#e zOh3%R&HCV6l7<$!?5}PF32owdUD);VWYY^$lU7gj;8PVGHWAkOdz(S4%qYF}30|5G zEMVH2K4TWkIkZ1onN~-_Tn}NH`8u`PvxO;D0?UsCB_>ObSvM& zokzw09$)K)mDy{M2)b*?#8m_)TDXOe|70lCDQf;WAJ3Car?!}RMo#Ni4j8ANpKEDv}o_-e$a*z>5J^fzUQMOpx|SeVTqeq{Mt8*C?*aaAD=Ek zm|GL}SAzWJ>U?PWKD^@}U%ra)2>qK2{3G65YP4CliWZBzU#zS)hpj@PNSZ@+qkMC`93zd*Vc=l~CMbK|EPw)6Y2J=;JyZgzYLxmaZGfYV4-1={Z5 zNx1YFjbDJCdw@cfHxc}Rt@Q@y+w>rYh0P-*M!2(25Nz)|; z@n8XDiSnxowCSELtpGY?9fJtsWDncgYN-UgTFha>p%HjJ>v+64Sw2D2by9%z9^zNm z6>&CNYm{7?o8#|qj=>u+?ais)ER-1I!+)~|alz5L46MJ2eksk&er$=e>)nrb)?Wo^ zqv~a}>J%sqsT~RRc|SCOPO}x%giub*%IZ*Pcp$zODsQM6_QT6WkN$0 zD@H?%*>7!~d$`FiPhU^9C(TDaEUKQ>sA{CnYYjx*ygB0^M|M{e=cE5}-06+L#Pe5P z$_n-{vg?QP%6=s`A1BC+Qz5dWp_@&#!g=#1>{v%H3=fflJV(+3JOjJjx&rF%SgJNO zNSyLEg`(z%vZmhBktG(^;b1FvNpqEf>O=Im zRuZL)z>LER(wH>d{Z{&o(|AD2brzE!?I`e5b8|a^Q*;EMK^uP_T!XC55|5W}{4Ad3 zFl(Gj@J7EgsoYIo{VUoj;sm$W8I?`3R=vNCWU%|*EF3bxAW!}d5IuPe%rNQlJLY~% z$YXS}bbmyxa9h+8n4piYjubrF-(3-|z911kA?57ID^pD<>t!s8NRYG7z0Yoz(4Reg`GROEO&xafyRJ*hTr=5C z5t*3;K&;2h+U--`kOe40vAkGCS0q(nXse51(7Cf>4mLzX+Tz8h)BK9%V26+&lHzxk zxrY*rB9pG#(z^7uzaF*$CPba*_RV}IFT+H+n+o&O3DQ^Bono1F(BB!735xC@_jk$9 zNINLQ+F9rSl)us5ANsz2LohPT5WSJtS2dgKM~qEv+&r;!>iqY8jt@?jq3bn*|JL%d zQ$wMmQ(EvpCc)9fjGtPI70`+8gyR59i*=7$n2PMpgCj7M_-F0c!}grQ>l`LObon;r6#O@Y)j1|8=kJ_2B0MXj*2wr@tST@6 zAqzMwohfqA?P=e;#SF=BrD~FuzZPirF5mS3kzt5UT>o-NM$!`ScA1W~Y1w6H&!$QF zmay+xAji=sMRUHGACDOY5)DOs?nIb5xG8~V$a4Kzi{V*_{5wk?wygvsDr%+isH6^c7-qi9{ye7$@zu&HO>W@tbJUR<}UaeutCjEbW`2 z5uBWAiigDrsAaIeR^P+Y1I`gzk{mtOB|#ot0Wey>mpZsDRPG(h>mVuY1dek3$v0x{@tKv5B#QTWedToc&&L_Mn|lSrqVQ0Z?rzxYP@ff?t}F{NsXUtLx>Gl z)KjD_^y;#$qsnFG$k3)tml08|V_fWFa$_AhiKqV?R*XT2t8ri$l;fs|E6PvMCLUrv zxtYu_O5yK^Yj%H0HYWB_`eBQe@l@rSB&hpNUTe_X+oyCouYx%{$^-vQezDuYHmfKH z8;v^Z5=0X2KWsOoch9H$`5x(If~VdrwZ@#?PRDzLLSOFE;j=tECT+1hMo*~ne|`m}FdjY&jqWf~M^ZydyVJ3Paqm#KkV>Ogi34qY z4MOQrnc~T0h55sjBV)TfZf@TofC?~0&dsZEG(`1-;v7aAG~tp!pIuPN969PJ3(M9Mfahz5))fKqs-7x!%ozzsNWQIa45u-vbag+Q)Gg_D zxl8bGbH^PK*}u#@IFlaati=wAF4}oSSd&Ks1{EbfbGI_^MK5I;$xvTjVDs7GA@d5u zXib%Vm&XHQlh8Fqe)fa00X4_wgOx8mZ$h=i6Z814OI(=rA{lg&vxN&B71X1NuF0U~ zQB1M9ta^w?O~;aqb5N%a~U zODk+`MEx240sZVJg-e&~P=(3IV}b7bWwM><(7~rXeGa;AI`M{%@wSfTnyST`s>zb7 z%>W0qxhTi0@pM!KU{qVgsTE>#*5FmvBx~D7SLSzl^ipj(9(ik@7d3_$i(iVQ=yZ)O z8Y;-NqcxG2$t%s`Rgehj0tVFv;=P^g3iv|yn%Y{vg|YZE@!aAxTZ_kJMW#DX3A6%o zO~z#$d(PQ@l*mYHgJCjjdu3gb;A+Ues|Ye-yU@|cPWz(8#!OX+7-?8dt5 zh_nVvQXW%6kvle>OQ*Pts5eL1sA$5K?kGmRBU(+0EYG-xsp_K8AtAHktuk+F|Kgrv zlyj|2nQPZl`%ghX2P_i;c{?h%4mST@M>7sJfbS737#m9?=MOn+P?^!-di1^4Z zVZFvK1cg#)H~d)7W1fTtJ8Tc@K3ConA)o_jyr{+32wBv_8C~2|ouR)A4+)pzCzm8? z)T{9)w*SpwhR4#lY2N(rrgUin#D3$clDjAA^WrO8_XE*4vhB&ROW>t@I* zD7w*4|6;oq)I27BOzH`s?U&wyw(`E=J1W_PTt3arW~8UaoQP^;1bOV9jDvBZuO2D9ix(YH0>0VcIY+1f1C4>4<| zO_@>%5=9M?pCS3Pq z<*W4=Jc_UVb=xVWYn?5FfFvD4xn#?*i^n6+i0NrpSTctLw?vO9)2ECbLg>lwjhV_2 z;z@CK2p#}l=?Cxk5oF*itNHN*D!#8hK1zJ1N`!g2@&IqKIpQZ6Flc*qHQWTcAOSf9 zwI~y!rubI{`b*>-I67XQHZg7&0z*yoffNUK5&rjX^G01pFlSW(T z=b+Pq1P%uA(pLEFUdK9p#`K4cm_~SSYX>RylRUNlfWu!+h(TUgluIhZmK>8)Q+8-d zuC9&6JwMk2Anap^(&YH$ z9yT?v-MEgFu|UL&r)MMGfQCzpnpHAq+p>8iV963QB1x`FRYgZ5`NvlZr zT~%&{d)6%mANkjJ1KE-VmYwZ`dg7(e&# zM-K~;zFo=$!dl4#*zNIGSKHk9>u+NogcusAweO+0@b}(b_R!sr7hh(Jos*sl9G$7ShTqH|6iGhR>3$T03>(zndyvM+x`23Xw>%! z);Kh@J#?9Lz1F5oQ>$ba*rhC1sV3^FNV3a~r&h;!kp6ULNCl5#Z=Ah7=kk5Pxp^E| zlC)sQc$sCdf3@eUy>yw>iNO$ZW=IYk>3U>oZ!bB29(4>{2ErU|Rdap)tSoKiVMDkC z^gUP9r#!1#%mKGxX!9)+E|0)SGZ>oVYQV?my%5%Zv4Isf zio^Jcxm|WIXmWHFc&i3sp&Z=G1#Z8>u2$K9!c8<iAu}8H<3rIyx#O3NkWqHF;yK;5a<<3iZIB z`FhY=a0aFoP;7xhAAWn`7u2w))YL<#tU%_?=;2MifnXckI*M71sbj~wOQb_+hHAYo2a z>ALr~DvPJzpFgL3LgChm_`}YBtTHw!QM`P1?|dl&FEnUJsSV53M~T0pcG!H;B~vc=eXP z?fQ&R;MBWc-<5SKs@E-qQ=3W9$VUF zbXXcf?Ddq2t!`QAZj0m@*GbLrY;8Ntl0wttA$Vd&O<>>lvCETg=={<3M4Y59z{eMk z3^^jVP#bHK>80$35D(!%;DyaQ2$hDHhlem+71e;lELP(CklckKDgwH}=O`CXj9Zo1 zCae~isDY>2Y30II0h0Y7D7X_*v2nFbDs9@zCwk9dd#WUDU7h`>EYIh?*#o|;ZFP=k z6M~PWwY9OM@N*}Bxv{GF{E5ck;f#zljih;-W?`p3hpko$aKO!)$%oq^+M~O>Hf2;}1CF7SE> zN09qCA4lVPdv}DgT=zX+StLN1)Ypw|tG!W!Qmwpx#df<%;MiKdK1D8S*i+Sh{bZ5~6jDwKeRQs0L9IL|_2VPOvJdnwMT zsd3>;Pfw@DnDI^8k4?oT&`Rolz7n1je`d)Tq7U5l_WfJK{-cPRiJ4_Mk9Q2&QT_Ah zU>C)Z?=b8=N7znI*Ch2O2mXUo0(9+&EVVFe2psW$|56M44^FRk6-sM?MtTK)}v?u2kKO1$urZE-2!*Y^F}tbuUy z?%%F^5adwRIJEhS)V)6Zr!-_u2XFp#Z#nkJ?8WPls`|zp7w_wgiZ)g!SCv9!o*3^uQpIsN^7E^E$L7$x|P`~V(^c@LBNyAtS zybVVYB1K>+wOOt#AF0dt)5iqD1N(MGAH_m>Np&@_QUrk7$K2Wo3=wxu+ocOc_73{P*6~t z);OO$!`Mf`+gor^52W63)cG4C)vc5_RT%iiL6I2Z6CDwJd%Wj(wcB>R`_W0bqG#4j zX12Jrs6TSxh6GSI&{}DRHHfV5`QUB3B%x&GeK8oB` zLR3Ck!1l6L@b506@BQ*%>&pA)*quZYyYayZHp^UQYomx`0rczRA>4kTE~xkABIOH( z@7`rbLB~u(_uZhDGCY|oW+`x)P$<7kyW&$VquHVL7b`{DI#iz+@XZm=1~pcUhVkRy z&-!Q@ZN{N2K4)^E_}G313{=Qb#cNsDRW}YD4YkNc`N#Ild$kUx?MtKY6&5mFPmgH* zSi+El-s446gE@qc(pL$Jgf7ExL3w1QrbeB!)z+<^DXWn4oapoM~vuO-y#o_`b4AykAygMAsXH02kLR`hN5My7w(U;BcNP zOUqI+MP+=scmWC^bF$&}azmxTsRW+ln7T+FU?6lx`AS;&&U$;9fl{>|H+HJ_|eVsYQ$)GP4~+r9uXo8a1!yv}a9`F3{sfcjl@uX^;yc^fjv`2kcUVsB4R z#{j??wbE)dTy1PQJGtPk&zFb?%Wum{39SB)VGN$v>rY2go;xo0$q3gSR#R&fIJIAe zSj^x~9J{(38VVt*Blxi%aIoh6a^FoPEbLRt`qZUg}lq#K5z8>CCR zyF)^{8;5SBySuxQ?yh}5@3;21*4}^Mo;6pWN1bTq7Jh1%V?OJrXJ?!PzJwWA9$yIp zkWdWimw(>Cfm74ckZ&MiVuATfwDR!ajb9|1o<%HxMqUyr8{RUAuKSY&Q%+!{)wHEP z46&|;Mi@e}v5~em(_y9*PnR=Huxv8iOzG?=y1{$hb2q_bNYfVX3>8I^5xJC<6niOE zF98~;@hjjSRi%3*;;IXmhRH-jQ4sgnih*MrOi+RR8a81oDVDrFUi8|&kvZ?^AlbxR z^#55h*e7cV78q>tNQ~++7o&uw6I$@VcH;%o;(`u<*kf z`mjx_#o~#2g-#t8KfCT?r#d6xrH>da_E~9bURuh|rou|o(b6%I)8-i-pKoX@kB-c# z<=e@coKlWWl32+N4i2^~*Nt=tQ+7S!R?mx!LrMZH0nc!}6|J`xSq9{fe>eIcogm^E z?$CFo8d_RpnNgvEd=iba8L%MLv~miN%HODKh`EI*X2^0KAi{JLr1;cY6w)8T+9Kja zm-176+wb}DsN$E?I|+pEknnJ{!Og~z`^HAtZ?*(c-~W`B=JpMwV#JF-+o=IBRJm$g zidZQgq{k=PPhWgSk+x}(VG3hoakeEFnCKxRh7(b=7k5{dRui<{p6`TC8Ex>Lz8zUw zTCFI0q1#J@Oj%Vjl{(7F$?5A;g(0-jCDY)cRbn-7X1X^v#YuX~rx%w(10#%;na1CN z>BMUK!tGU9*gsocV|L0@pVal}`@s-f^$)43zjpJnjSzKw5MbE0G7!(-Dse}uMwj_R zdZ5$%RsX|3W91qVSZFc?q$vYAI5frpxBg#)R612A^H9(K-yO9pd;P3I6Cp`AH0Ga_ zF*!4%2xub8_xgfsXH@yF;^x3)toZEze%=cKN;%}(=QR`R&Q9YW{E~P7hW^uh5|ar( z+|ycp)$iCzOqzebYq#}znNsv(0BqG&RfVQp%Jfb3)$+@uVQ~9>c z>7I02ca5gr25%L@&M<`Ma%&bIOXY9HDWMTC_q&_+zn=v# z;54q4Gw~?oPAY%W($eDLZ=2T^R!_y{cEROx5OnUDu86sQ;L_wYoG50H@FeRKGiS(W zowKv}j=?$*k4PBp`n{5`Sp79PBxDdJ>DM4GO?Pm)p7LMsrxRXP?f`eC_^3Qlx&;}` z@!1N6W=UfbO0sD!JoldKZp1uYvV7g6Zt9}vLMb_n{j8r8^gN_fQYhllT^xq*4mmc1 zGZSP`&2|G4@?G#jG2!8lau3lX!zYYQsr&G z5~d@BzY@ttDs!%ouFy9!G@8uOeC4qFo0djue}o(3M~}bjM}VCW@7#oeq$K9*D(XM0 zj7veZq^QRbU!RgR=!gwc{Hyw}?iI6m|>lWaA<&^w*B?-j}*^qvyX7* zvvG0SuV1;Grr`!NR=gY}Gm;7l>oMO7zgPRLx7F6Fp06UQ3}AMRcICnGj&)(K)*$n0 zzVY-i7RvSx^~Ba=#Ujo~%F3?%b*Zrb&$S= z(c8RQt?rG_``kUa)3=)EUX54ElHi*|a0qR*&81M$YO}Jk($dJh4YqzK{&wIeUonZ` zWG=QIuhloXCA#7#8Y$AKyhrlwy@O$Du`oWcKA6@Sp=qeRzTF~KLf;77+ucP(M$Y21 zr{VjEA|xZ{R0m=Dz6Z%1(m#~cFy{7*9U79pX9P@hnVFfeu&~)`CgMT9<7YIZT6^*D zWZyIXIh);cuz%vf;GJ2u`)2QGA7P(22r9vkP%=n{&_9K3CR9P7Te-P22?Uh3XWRWT z+r&q9cgYyRwPkV!U@B@RApatirv1rcy!*}%g~wMEO2|=(7;(|z6T#dT{hrUPThm<4 zaoFO}piJBGMa^;z;&Z)W+FlutVemJ*rsDU!eEiJ}V{k-4H)HN5)mkhC$ieMz0U1Aa zlbFHY?x6|CQg1pF0!v0lX#W2^YmEUP70##Z`BKEX*UYsGGrD_+w8ZB&IKJpHQTKiN zVG8;au}CiSCD@r}hisx_+W`>{)+YS>8Qoza;!p{3EH|lfqbxl<=>ikc086~|%{D;b zE%UOYb}Onk$RJA2AlEYGw+XwX06@4+W~=o$|Mr)QgYj>Eru|1Q z&n&$j`K(4gd7{_UxyA~JaYy)woj}>3oh5T^yDyW9M8U-ap3lX0CM?mmhe(<9klCP}pm@4mn zf;_AV$2WOQtF(bCmGV&n1&Q{cp$Dn#f41S1R0%5Y`&1*s#LYrN2~f91{W*+WndF*z zf;r~o2{}PQif=pd!_9xW_XWYBXkY3zEr(33+{kpXjsG&6MvoAYK}voRO@1Z~lFo#z zl}!8e*|q3Qv#MbygdZ9vljJiDv^M|rQQ93dBn?Wub(V-N=niChPn4!}ecHfWE$bK2 zr`woLuV+QONAN>dgOk|>if~$of-CcpU2Y%4TohAy(z5!dhx{X{j7Zf0fx{D&EBmLc zfpiFSp<)#MG@Hq$K@bW(5(eh7(61M`7#uR$oM`|I&3H#0k!9C2Et7MA2<|dcDP8_y zp6-bT>ihEb-03DRKFv%#xYytBbD5*@dCW)#?G-;u;`ErkdDVr91N+5)Lt1z>nWErq zZk|bu?j6sqpBjl??zbnKwzVch(JX(!Ep!X9P=Voi!=ffvKq+{cAKHP zq7S_kzsB(n`k%%8 z&~)Y|^KAs_nJ{hE4^5~TL7*ui0X&F(Fa@`F5^#;|_6$Au0*|cnJWm<`rycCze;OrP zhU7SLp)KvCwo&UBl&LJ!*bZ*(Y4(Lr_ETqkSSWG(Sn6p(EC|ld)J629>F8CGYvLq5 zvo+!)KhPJln{gUFvMqBndI?r&A7d#${6{gcMerGLr{II84GR+gLG^pbHkmmIM}7n3 zeo{)J>0icqQgEZrD0$9(^DxWHk@%bLZ3m#CE^}GGYxj9`5mCjf$m>P})0>jI{MIkp z`v9gN+CM`9O=H=n)GZo%w6t?WKRUe+(T?^xzc4G!^lRI+U+s{*>wvgg4P7RV4>e0{ zr!3M#CjYa@;DfI4K}cDWr@-}kXQs~AW3~BuFaO-E<{MFwRUux}iv;HJd^XpX{;WrX z489yLwM^6G@}g~E)icKgSqFhF9|NUIJ?CcR41?wzEion;R)Z%oAy@jr{0>L-jmlwkzlvi}Sjp8|? z%g!4AXL6(e?wnLYwILgEvj%^L-*^l^0E%>e(k71}A8oWa@c3Dj$&|gL_xVI+2*R|0Q$5;*c19DOq zKPh8-WQ?qAz9-Whlfk=gnxOmN8VF~GMn+*pM_-SF8au3DvB(YUW1UxgzfdNst9DqW z5P@`mc>@EBE%b1?L{>pKq!vXU2KBgZwRILe?`rnyEE!(ApQ!W2?;&$A>;;^tRt6t< zSmX=9@GR&HR(}7xZ^?73r)ZWT=3p)tM{k6IQYG`-=6lC{pNCnqFQON*St4l8h~ zJr9Bd0nB1csWzCPrM0#I7Ml)fts)+3TH%Hk>d&nx5I48&!ld>Uud}6!Htf-Ll@1rf zAj6?t@d^PadJ_f>c>{4rp32L8;dEg(J`_t6Pb-2{TT>Y?oy7S^UV!VNnI0OItPX^y z|77=8MR&v(@oOlzhy&pQ=|k=Lwzp_(_wc1)Jl7W_21(3Jk#;jEPT}*lN{)(|vU42bhj@ZX&Okzd3B&Cinkta#@9*D4 ze$VeWKvJoptP=CujA$jE5B6lU)DL=G>LapGp(VL*`rVdf{22 z%ap0!bR~isY&abxZ2IEH6KBMrK*o0dB+kD$)v_Kg7_yVZlQjW^*$6PO+H+8i_>@Ga zX*pg$)(&ZXMSUAxT`O&E3r#$BGg%7(cDrV$&v4nOj~EAgC@f5-@h_(681bSpWsSKw zXRCc^LRA9q+Si1Uv+haSzpi=sx87~=PMA^doLxnw7m3X!L zv8hvuk{EY{VPb_F4RA{ZN zuDBbBw_WWQvm!nEV?bkoJoFi>%Hcwo6A$}xs$1%N*EC4_cZ`B4W9 zqo85+=~|g5N)CZO!6Z#oDqBJAjC=R`^Y!6gOHHkS*DW^>9uD51y(8C8S_aKkyIGonf*fh_7Y=?HKZ9W7Pj;wb=M^Dyh*>+oS#kB!$SFE9CY9jNEc zEHN3c=Z2l-2R62iuzt$F$JH`#Pq*$@kJ-wfY#TQpLnz(P$-9OC^fAXjLkp5Kw6D=M z1b7!Eg&`e=HCO0pp%VD``0z&ojp98>zlL`)K9@C@GJTUVE{oGTvD)zz)> z*w!aL%gsI0dv|?H^XZZhUW|;47`cty$sU-7krC-{g5*4MYb=z9>f=F+hM1t$IC8$P z)C}H`=AVmG9=}JIG*fWF3ru@X^SU)&%Q3Zk^jK+$3CTDl6waw?njQNYSm3#~hrWvT zV}Vh}6r&HTl^t-^p^4+dPuIP~y2;$&?-CLj5Pq{t!xc5z{4;ePuFkj^hi(c+W>CN{ zLvlP1R9_KFb>4=+9t3?vEgWc7i4+}?(W+r#tu!5q3Pd&VoBkvW*VoQY2rDB?hx75t zf!c^+A3r`2@(7;sN8tNf-tOJzJ~|vx@wi`Tn_26Z_+8VKh;@`2!ZFy2d+h!VIGi7bKrHQ7(E^?=3h!u*`fsg4fcbwe&34OS zfqPbM=N$0h-gK4j7qf+6zYml{*!~AN@9QQ~<>VjN1m46)sNP#}z^H1g-UGaqj?*q7 z@5_GQ>R%TPfutAtiOvAzkt$_nDCeB@9`O z+qcU|$jCk~PH#Z+QuNGP%};Yr&-9DUer@ecz*m3ikwmRftI51Lrq4zJkcryIfS3Ay zx?HEes@ml&^FPyY-MqFJ;K4OEGxKV9yDCU}e%ecWb-dGYad&s;tKuMoCSyyIkiFlz zc&OB{W3S|kbg^$-NwaB29mBOES)gSz+xUWu(JB6!1RoO{g~qPL_6H71(5E`m4vz7{ z=rQ>ou!b6H=kJ&#%cAr_9G%iJT-dVI5G7Q+4TMDF<}TDvV?S#`34Lr^ z>*BPuGu2dq3(qwaUryL6vitj_=XTV5E=?EN;ZdaRb9e}47biJvY8ok}$Isny;Wsg7 z#QP_1ZXSI^XUylq!&2$eQsK9in*6x!8U20QAl-&P}`$jNCtwF(wlnt-? znRIl)5D=f;fsTvKS_`Te=o1r+29Vp7nq~Rj8ng$2#iH=s@SZsz7?31`xLtYE05@5d9t)=(CQUcL&3B;oME(ab4IH@vLN&b}A0;xXC{7}6@0f`mwN{S-!lJ0v4cMrzB3A{Ed!3jqcH`8vJO<~ZDZpEo@9k>E?THaBH`|T z0RaDT&^zmInoV23pd&x6Mn(o;I9#={Q1J=goey+lr&2y$t0+7?xT>gqp`#0h0#s3s zZi>1`ASxy%{ZR-oNH@FOJe6>kCKrk7=Wg27Uh~oKfko#3&?Ju!YJaY_-H(0`sftPr z40Np?+jU_5dpwnsLtXuale2q^noR@`AFsF@{qm}WKC@xIq8m+G=$K&0!{5ae9%RMi zG3Yc8;HUaJ4rfWj&Nz69vrPS7?0ZkA6*V*W0wRl;Sn&43VQMPy*xUwU z?P^L|pdXo;F=26pU2(H;kS$Tx@T05$eW5mu0;7ut1%+HSUYiTcsQ#r9iR*P9BSbgDBiJu~;$^vycnq%>^H4<(hKGSxgkLvq9{ZQS=yZtOt% z9|IAPb=(g==wsqy*g`1jl9w;Gi@l2%ikU@Xj;f6IPU!ibPX*2@SL?S<_a3{@$Co># z?G&@^>2!4|u!YWjGpxCs4H1KIuw($@vKCTOUP^O>68Sk&*?Mgc50?U~2#M^MUbclR zB-w2mW9;%jD7!v0gwi|oGf{M;H3JHA*RrPPY~s;;nWHAiF({EGh>z`KtSkE>KmT+i z^|#O4O7%BRYJ6fomYf{#uIZPrRwX;Wx^lmbtm`@Hb2O%j3NMdS ziKnz;6-n5LEyihW9hB$?ht`%~Q~ zVlMmz!d?aTUTT_N(6mPZ`}@Nwy>04=%Hq6~p@#7} z<5>qF*(xU#fik_Mq_e45<+;V9;R_HSxw?8T8`kQ0R_#dm-*zhACjryZXsZ6?Do2?8 zTb*nCeYDuENyB)7<(U}rvF4v3QlxY^`UX2S(!ELtCKIUwD{IDk>*g=5Xbbe`0nk?b zou(5n;K}J>^VH$^giNt4Y`0#8>#%(BYdJawzQyJ8psH3Ui~Hp|Xn%><+wCd9GzCFk zQBuZS5w*K6$G#9!QVLyYGV+kJ$F;C^QrmrWvh8G}9yNFU>2r0XE=wQ4Cmj}c(Zlc0 zEzi@X<1cQ63*sZ68wJ8wdN?e#swhExIAM#`CY<0{QB~1*Of`LIE10f0c5dgj{X>6YNd*t{!=B^W`Rq>W*N2g&!2By-@kvaw|Uvjcx#J_0Q^%Qj5F0`08x!?jh9~F5C539bHvZsRX zRZ*)eXYT&nRu64%AgzC3-m@$5SATTt`62|6)8F>6r^{_h^>z)h?fyzq{jx^lDgaxV zwMg_G|4==D(X_iK^ZHT&FhVVl1;ps+mxJYx%}c<2hxkTG-|aPg7L+I-1!@BBTgAe4 zk29TT%^oyW9i1B>DrFls`Uj)u2nq-X?`kGq0pQBVqj)0w#AXeQ zSi3VbvjH^%A|jKs;Mqde`+N}!?~C63`?@GXrt9Co87O=(-0mMOE4&-b<1wMWj39NW z?-#_@T-(?wfv>=-Yglzhw}~B|%-bjZuT*d8mbR8d#6ZxNGE~M3kxRox)Cb4zLG*Wz z<;!5I6~BnJoSa=axAd=KwT`b|2)R5Vue_(Mbn)z1YWmgWNNcn&ah*Xq?c!w>nOK^| zTl8{&GB>}53i=_r6}o|NVyh1iV*3)B9&nctaJNqlF1{=g;)GDwu!#U@Lv23HX-r!N z6Nl&b=t$`@q0N&rO70Hh!u-PY8!4^gi^g=y^$bL?^qtMzNeLshN~Wg-fC3Pi(j)Y> z+sDi=vFqjUR5bb8?aiq*vOoO7XLvB5*njyP9IRDc<~iKrfgw_KWmo7R5qKKv0I(_7 zP+M~;w6YilxsQQvWX2;^#pFZK+w;InqFyV%0JMkvZt3mM_?^&!MPhjWE)IVc5XDXn zmfXze=FK3n3`(7o zwX&f@dz#%4AH*#Gqst9QwEbIX>aRUCrF?@1>p zbY4VP9*Ipu2obUZ9r2=;g99YCfNY{cSB0nWETtU4#Bz6w?a5K=26H?y@Nj*iak3cF#k-k0eE zP1#D0g^DO4hkqUTpm)o>z~R6|V}|N%p=%g4yE21AL`3r|bQ}&W--?zqeR2PM86Mt~ zPp2om-jUrA&(5A7&j%C%3001jR#Ns~3pO*VLI(y&q9QUDsT$(^V<$EeW(a;kfIHBS z=Iiewin6!ORT6`sZc|)-nM#!_#e6%`RI5a@Zq(9}D!wD-KVqVLU4HloZ5`?S4m$&b z$v+mT>bMDsUg&f01Nky@w3cQ^t)oqA6B~x}a`Q-JO&z4ezt~bN^-23=(NFCToc4of zUeDX`LGsLWP!Tg^ke!_&#?1M}O;84$Wl;C7MeWkXg+2ZR1_lv!DMsU9#l2(rhuvjhX@!bJe-i{nC2v_0a0)`65czhuimmtGehM z{xAHt*cpDog;HEzT-<}ou|UuMscxx{>tntSQ3CMp;@>=ZkMj;?K5S$SA>hNO&kmIe zUx*>F(i^G2k`k@PoArgk?oYL{ES7K63csz~=P96-8nXYJ0E+NWSr&;7-cD@YNPhE1 z$>_?$>v$vhzDRSJiiVT6af7?>Gcp#$O}*g^qu&ctkP@ZFrHU4o_Oh&w6qRxl)6jLF zJU_o|;_#z^nsu3N1~%t)BRubJ4k~kvYu<^C;T_Mk^bCnTq`N@aaYJDyX696l_jh1; zdr*f)hDysz{HzVFOP5#*@H}vlBsz;bm5RnmVg7WpIW5S(K+UVBR`b+c{$RJhy6^n8 zP1k}jGBWC(S7*S>BE8&Y2L%&GWV4UJC4JrN`Tij$G?H#ET;yZmdv<*;=HiQ~a>dGp zUKyp0*^kE*bf^fLT*$b(o-ig)0u&VE1FlF?@da|9f-d}v@Gw$hClu9rc{v(*D#hhGg%^qngO{n<*tm!xlmBWh zGUoP=Y`@I6DP2svfGb5AQW<1PNXa4R)!&I`YP*rKpD7$*x)PVZ5tVvovO{IlU0EvO zC_`$S6rBS*oeX(-4$NX@C1tZsVONzBma20h!c;fn}}+uPXw!G9=z%N zfQZO>sFTSEPFU5fu;r!n^s*kzq}<~KD=q>IE+8u)YWXZc&kL=sSJ88kuj1h1T9{w1*(mstq}4riMu#j(zYstO{Ff_;p9dU6XyhI5YiLNT zJyMXrytS40 zO+DuN_r}I?1t`Y#U0yJg>tMe1T&1A`mM?8?a{RI0ZJ3wm=eYP86O#;nerqMAt$!aA zAwRK9y3Wu+Gb;^Blar3uJQMm^r1Ti_u)`*W)7BO);7tvM^IH!$i@Kf>t=UL0CE#DmR$_F!P%Z6c?fFS-+zqZ0k^a?KlkqcnJcq?e*L?5vYb|L z{pZEbXfZV@A#Uy=>;DW~sjALC4_H(*z>HV=B=SL{C@fLb-i2J^rc7e$35&XTcDJZcp0J(HcLt(uxqTWkuvqoW8=mN6M+J26Q|6pG7lXX0oS9iF1zdRh9 zSJmsmWVfG^sI5|ZHOJ1-gu(#v6!m~1QvjMkko7yu=@BBebpW^UWx&}TJ?e+9enEvo z9(XjvnB&2#;?0w<^dsA!)45p>veXnu_s^CV@wJ-0;fdrpfEw(}opJYX=DKyyl;&d= z_aDWY1zcDYV+KX59xj`Y4dJ|2AKl+xx*BvVR<2Jpnm#{q0@-bfyH_iHXvYM?K5vUr z$~AhZVW{LR>TfAF6^r*8SiRDmlm7kT+H0>HOIa>YW?~8U*#W;08(JH;(>u^m;$ELQ z)%3h|A0Et5?^alt7Fy)3!k8kZ%=LG?>#Sz_L7yRp%o!7#fo~>ZCzZF z9t)&~{-|JIVeKU=VLxl!^D41xT$me`oRauygi}Ee0Dl@gfE+g(8m4P5&J_wku$ehB z^7BfT54o~y`)W`>b{cv5&bhtj%XxQ} zzwkOw!6_rbNle-DYi~u)Q@4XR>S(a{wQP&i&DhG8bo{&LmzVM;cYDYW$-mlt1~qN3 zhY`2a^EN~2dd=4S}90pTb*r- zHn6V>#s%Zs4-W?Z+DK{Fd^X*Eu~+y)__jm)$%L42VSJoegnx5)aNpj5DWC2`xsT^IG<3f8LpwwS>D(i4=y& zAm%*l#r03#wfhy$anF}sVq>q}a0x-d6=jvU3LphDJKI--p-zpdI-F33AE)Lq?PYsW~w^-zRvZx=_8m1d-kT zlHm$pRFHg5$u0~Yum|8tX50ikP>%}qS ziFM5<`b$^>-Iu8dW7>Ne9$lZ8I6%Hx|7}vf{-`SMmuV*IJ`8wFtG2kb6m2g2`yWJY zeRP1t@@L(d*L>+IxBoWri~9W{9^w1NPrpXYJ(wdF6Vl^2{x;?(zK=`^ zJ88{}I4F@<*9VjE08m*%r}@#L2o){7nu^T-rl4- zNRwo!x?BQPd|cXvnMkv3d7V}esH*x?v%QH-`Mgy#u7CN{`J+R1HA)VJPSY0Pa)4{M z`^^BT*E{@P&|yJ#{-u@{0Bz$4a5l_3%?Z7x*taDDIA&)1_dBB0!O#ujwfNT><9Nu$ z=gryaWq~3HBP-|=GpL)cJ%h(a=)8k0P4DUUyqWp* z?Bx8x&bA|5_J*-j1~Pc9~Q)$WLSx8WYiU9p{r~6^VlV z7kFh3e6Do3+4@7_x*?SBJa-T{(5u-K-b;Soe8!XH^D=z*IDCmko*e48hWD~!R7-eQ zb=n-^^mH;he2US0#{)KJTwES9pnQ4Cx@FP)xpC%WNVKD({+}q=MBy^i<92Z(R=*kx zO80?2*2#reU6BwJg!oWtxx-O%p#&f;8@P4spE&Rq#pnEhtW4D_*-B&p4N=X6g%;0R zEEtI)fMk9%@!#~F-}7x$+i!nQrWQe(`$~ckx^#(I|Bu0fl#=otdC%wn3a(9&14hpL zqXV@@57$_!O*}mOJp|CV&wN70%8mGIYn+cWE%>1Ny3_cKyVl!>rP1;;tEsj79wLjj(eTXKt2KrjGTkE>^?V0HZ`=5HkB`A=@PFuElCIIO@aS$On-!BY3%KNVn zevk9{qM&+-ks`JdE+wv}0)<6lDiIhGf zEEQu&pVoVNYO3QsLU4+aUG_{qS*Yx1sT4MP?aCG}_XF(o%2bRqlJDXY`04yr9Da!u z?=g<2C;yPO`TUED(S5jFNLznsc``bGTK=ZDzSHpNIBq7qdA5{9q87e6S|)kgn!Ry{m0NW7jYobG429FKiy z>HA(`!b|+3Hcr6uvy)PO#Z_EJs>=Sp(YEdKp4X+Et%f?DO++p77TC8 z(mzCcP9|>VsEeV01U3((fYq|HLSB7(wB71{Bl92e(SA4tGM?huE`~Ab!FCxGeCd#O z*DQ6TPlt!VzIV8~go(b6jC2m#0(iFxA6h3*^5cm#=Iw0&tp>vN&=@Ch(h%Q?>LW}C z0NzY3{nEfdr1`m7YM4Vo^X!x#c{z>x3*59su|!z_Y#m3TUjSf`xsmuFtWLCAi`AmL z_S;h}VTATKm1BvgIN_zF>#OewCTZ66UN>H| zhRsz{X!0A#|8A~(?=w66{WqRNw$8hT$O1ztf|NAe33QGD2y!p{ zPLm`ZLo$#rE)%XX77Hc$;MfQinZcQ7`-1?U0$@l2n?3^aDZvL=0^hup?*W+piO1>W zst_E6p3d-!EY@>BG*95f!hH*Ml?@c&(|#*m-k^#WWh3|3FSoH-fP+CqMDA7xYQfN= zwj9_HS)S<;Unai5%K@ChiONh4NoFZvpa$M3GO!A+u5T7VXI~!IW7?gV?iOb*K@Znyf6>F%$&kVP6ijcmW*Aw}61b+cO?74}elCcfU=EA`|YYNGd{JEbo_= z=h3*km@^83PN$|x9o+!Llbp3N$G@|dm)p3ba@dyB_BSCPVZG{?H4{5@|h3~|@^YO*O~VO`x!4Wf>WG808a}#B(&!f)R`>|I4iiSHq;!>*@!yWn z+P}SK#YYf5=svc$QQ9V}t9!{C24SdweVKV8-Er~ME&%6E)l1U_V&%Ue{uD8xU;Va# zc)CMO5F5o8JL6m`5ax^IGsv{q^ZZqxBQq)+*+avMho5$L=G~peHtRD794nKrjFAdH zv?wYV@vrIU3DX=y@uDMOLkozVgx?}(YW>~W?02T%J1ypW{~^cM78AtdK=tE@D8`ai z^0TcHkY2+$uUmQ7xm@$|@-LC_7-%Avo}Rp__2unq{Q2!ktcM@?R4Yj{eD1cs zMdKqo=*Sf*H#)R-*ko_FDS5Eu(P|LyeW_J6!AJ=m zzrPB?<#?kFiw7HUtM=cTM@MK>LJm&BwZp_M2%ed0P}wB-lCOSRXRSiVspuB37e%Ju zx|uB}X@Jqhd~@{f+j?u*?|Zwte~jR%`jE|! zpB+4ld`&Rqz{v@0|3_!NZqXKMO1)OrZ!4g46jhE)G$0XS{zsjl5T%6QR~Q`oLjBSa zLuJ1I5L`bx8iU%S3!MtVr{QnBkHSB<2N_P!-H+$IEWz&H?M(&cx8y_r{c?%96rfXZ z%PW$h&ODF_HpU`IuPSPA@#i+m1E~FTmuq;fzJ3F=U*0rL+Vb3UiWEdc(g1a0l>)T> zddJw8Wnf)6iSy11P8%k51PaZZPIj@$F)SdHN*pF?xfd+whK_thUZN`#e}Zmu8Vojai=kE*WmVuofDLQOtRuZ5(Q+?`$w0)Jz{XW1CTyIfcBu#A#HNqUC?RS zi5M2jlkZ4&;24DD;Ym#utT{QXj3`8sM``Qd9rIpT9%(hp+C%I;0XaDyDcVs95S6Y~ zboB2ykOYwx8?5FjDk=#nPHKp0h{>w(5fM{Wb&%Gy(Y5(%uO#JWt`#yR4;|PAep z1%GOdMF1V56wdQ4J29}q2JG zrNoTs+&l(6J57(@k-f>~%{rCLh*kx5ZmG`A5dK!n-`{DF&O_UDznBR7i#txx*!yo-zV8Lr=+=q{HPd4k`S6g*5kIQ0$ zJDIHmtrQe99-|N{a3l*51Jl?koIfc6EyY$Go(pyipkY~HtOV=VoV$89iB7Ig3w-84 zYmAIO;1>KW#C5y8@z3>bpfOuc7d(v33R`!X z9|{XtH?UW+8&_-D^~{czwLn9=3ojb4ADcfx&GwCZdi#Tdk211pC1Oa@v*QO#4w?{y zcDbDf2Jr5AsTd~{&}^ho+FVH5LK}1L<>yJaw&t;2u7aaw0Lxy z=^sQwLe4Sjk5O30r%N1m6O;!u>ux4)##Ydfn00oAvyStBQ2#A@`U;&@5cmhwK>Xv5 z!2g+{oLCq7N+3%f7!3}Z;XXZ>{4V(uz%iJco7Wua_hB-Y6o*Pn`r+@K5^Iic zX!M{}&;8#y0CE)ikriRS4*Z^N&0ZK1eaB)ZNF*Xsi=Fi#0HyCaGQeVcz z4{0c1lpj?0a&@RO)ChgffD{2@v=#tAfAABAAPYxAkg8F1u5IqH*dCsj!j2zW z6Gk}Wll%#RB;mh|r%FVM3AYF$14J6@W>pmyHdrW5!p~usmxJf4*80{6Lbz0*cLRC* zy$p3QidF}yVz5@5%vktwe3CDtG4bqYqsVt2+NIeW8|K1){(S@(3k+a4rM^K*pdXED zNX$q;cGj8?9wP$5BJsTu#46U!mD;IC^>KuTWjJ<CEc|)lK2=7%8zAX&@So%u(0|l#69=D%V@0zpsW1<-n2;(~t<1Rny2n8JX2nhGH zbE<#@wsuD_`mpSm&5_C}gLPGcew;6(FM3Q9ItzBK=_u)xf5b@#8rFaF)8YS)6oK6r z9fK&D*_gHyE!Iy5MWmidr+KEwn|ic0P&%#6o7@?H0B-JI?;2*RJ}n%COz7M9iR+uY zEE4F%KY+D#!h(RPWE^FNfs@KI-o?7>(+gyI;;%z13k!BTZnpWLTS>+XiB&A@3ANEs z?4OYFKGPlOc$ zX8ovQoA;1!B;5Bw)B&Hd8xZG^Aute}RDJY7Lc{zGVlk=#DrP>ISbngi z7KTX~l_}H+k!(=HKeWo|_%q?79=ri!HuV)JfOM0=UztV{MzMoygUXueyc;P0FvRpZ z{njkn=H6(No*}d{1g~Zc9RvHU>Nr1&R7GgmI^}`$2pHmOU3vIcw+pC z@Khkx<>H+;iQO9YYJZWYhrlC;FQCV^T{RwJUddC2BT^y$gNf*)p`z+~PZj2>oDZA4 zfl9_mIh1O5_{*C#nkEbuVe?~3Oe(AfK#5LNoE%N#Kyj*JK&dJKqP8$Qs`TT?WN8a* zU05lQo_D2x+dMV9$VI^aEl$yNWE6!RQTW87A)(U zmcOD?84QQ=I|u#?UrwQG0g-L3*RM^}#21)aoIp$;`|-S){f$=X=aP5cP_;92cDbFT zpt!69_^0G=S^E(#4gdD8@jiN~Pga%52gH+(tyxyz716KWy5C^W>ZE}P))R(skj$T& z#cp+)4AoVzqq{zr2O+E}t2Q-=TQONZq!jXV5_6Qbjc$2o$*0}Qd!VJe_noi zOs#$6A$}dO#P}^^p-Ng%sC90W7)D?(mt}Qb@`NNQvy)pZe(kfTusmH$_%7T@;$VI7z!Grqb$;|lO=P`* zbA`l)YJH)Y8%F=UY(qgYdoJ4noNIGQ50XuO%%TK0KLgLTv`lGR|M~rva zr)S3IcVWWXMRZ-Q_aB=rv?zRB!gi8|ZsEBzLMmHzoobb~r}BH5^M0lm(sohT?@FCr zAge4i6WF-0wEvYYE+aPAT&iU%31*lNCDd$1p2_)zYgr(ZrkE+-Ol3`HyGQOpp^Jq{m->PsAcep7h{v{GfwZ!YT&--Gr4IPuh32oOZOwlbiS9Uj1E#tzU&H z(eFm0B(}Vc2*nIWP8FUx?o21Ji0xK~v|39|20-hN(Z2L1`Zb>##lJBImIsNFtSY`! zG5hw5Nk{OnKw@6dlM2nuT|f6B*7%_L$U2DXOu)iUa!QM0Ut8{@XjlLoY_6V(i=eNy)UElZ!`P z!wZeRl?{`oUfiRo`@e4cH5rxMc{8))F~&>KJ~5#~Z%~0mkLil+#R4AKEaAu0Wfv#Pth48`ypa zO>xxZp@h}F46pk;d;iG45`7pSsNi&a&u@09yD%3G-w&tV0*sXw?^(=8!RQ_dZpkoZ zzEp)6iTBdwU1g{rw}ZeDw1-Q%q{i%{;f=q?5Q6cfjY~u0Acw~FNYa}>tWlyBh!oOz z2NMbhg{a!JL8(^I0zsK+nGGYcCv_Xl`;77CDIfX3XUEa;OJegci1RcE7 zm4!^lUGdq$PjrO9*m6!PT8_0 z(SkB)N@0zp!gRXIp4&kZ*Xa(+}cx^k#*&Qgowq98(=Wwngv%(hQs4%%4oW z&BQ`oBmNzhw{_T)c|d4vB+~P=yJc+j5Zv-*{-8lGjn_MC$6@5QtkY}d^5XZk1uvaY zlagq{_`Ihx+lH%QiI0tu7>Pjr_v8gCDVw;$x>_@~&((|4(){FVEb5rM7hDgXWtfC$ zHzFlC=0{pL&SfCvY%u?@IL?2XZM9pzp|-mQL|WqZ<5}+RzHxTR*2;m7aCD;2$En@dNYV~ytSCVDfOFWLaF)5YP7N;zxA>J zrRnJ#|6PZ!N5a)nCgr?WGETsCW8r=?l>tPU~%evjiX{qXc2jJ z*n(V*jag5Vo}^UC@Id)Y;nWh_dlEV*7$Aue(%=rD_T5|{@i9UQI$yj`5rgV zxV*5OnuKyjS}iZDQIt!Q6fk7PY(*Kbi&8mXSs_qW3e`}RNCQ=iwH5^nrJ+^RB+=5^ zv<9ZAd2%pCJJGM)VQQ*OVC7t~R$SfOMn*>+h!yj;o6@o}#IOI5^JLqNv0x$@n>-~U zcNPU+U6*t}t9Xs5;k{1b)?W1yV}k|iHrOm0?Fo&RB8wl$($*2w+8HeD2o)$oc_!6vR3${ZDKmy~psL(Ax=U4wvKHd4o`jWld>{oj9_ef7=*JR*ITMNyUw!DMcO; zslRxI9}SH2kIz1tQ2LK9cAWYwJZ zI({cqFX)0Agvv&ds!6Pdnp-sx4dAdOSSav%=u$0Hs`q^iOgS}HMQW*41*VGfoUm4U zl+qL4-l+2oCezw9D5t5^v?MYm0#0PR?S>k<(%-98$*LsH0WAgJR7b7P_rIYimUc zR8#$ZRpqAA!cU;Qv~}5uucgPln0E5H#Nd^&eh)xcNeX`g!ba>u0E8^adWRf>V-Z7f z{(&l&ZRLBn*eQDMQ5z{VNiEWe6XCom5wAc+xqEBtV)*Scu~K`Q`o$xN)vjyPjfpkG z_%#t-swt<7(#xBovr2tVrXBJL_l!t#3yp{3^Q#abFd@6NnT2=?vUVU7jFXMLVhS2h zKZOp_fp0&`{PCM38YLxRqg}adFyCmZmd-S-!U+`?HehCo+9*Dvn-O zlm0BTdV_%Rom{dXWKpTTyHvg%O22l!Po#H?WStIBo5F8K;p+}>soA~BPO)RdDN|`@r~PjJGVO4?Zgk%g~m1UWQYeTLBfoa8t;8)TbFv)33H&>T*1#TzC7*i zIQi*@T`#Ul34W5KzlJU#;z(6r{kS(MCM_|iEVrVmw1HholXUQ=*O+5W23_X{E(&G5OQIOgH%tksO_ zkHoy66&)ThUOGUTGcI|DN5fBF+ON|*9j)ez{Fuj5d~d7F+b$BW1NimzWm{8J?T4NM-9uwC`_)}KOSQ&MtE z3Mv~a8kx0pUIR-=<4G7o8M{>haU>lOx2=;W?c&Rn0=Y`qu7*SkjYYvcY3-P3Ntx*P zne6(Zm_oTfod!GQoWV+URT&wyqVEry;g?wLbh3PWeGz{Pot~bNF{3XWniwql5x0FB z>J_1$-c!T*^+RuUTB)R#+ZftZvacd_Lr&-;*@1VIB|h0$w2oQ(X)Euiu1+_#p^5hl zs5NgdZP#aZiW|JuAt%L^=7ipz9e8hZ=>b_=v)b_H)U>K?qQ@j>>IU_*jvsON=~8x? z1ZZm&HPY!t<&}vUc|nme$4>+taPh!OLRwB?c`dKC{gCSkL$KO{s0WPGgKV$P`o_*= z%(a5HY)SlZ-L&`OmuH@3)(2FkJx?xy#^Mo46sm8w>T>}rdLzZ~*~5Uq zK)5P%@Bu?6nZg$E0>WnT;naz@gp9`W_xEe3kfmpWECeoOXg^jo-CAjDEvF&0F7JoZ zs0~^Ecjt!QU6JxkQ{h_z`eu3SevQU^?!-9w6>aCIb}IkJk5k;ADNlT&_~gTdQTHRA zS^hVtd0khP_B^ZRlNlvd+38x7^%4#*jCu~&u2i-mA8hDkVtNCe+cP*09ryBA$Yr*} z6R~)Lng&`_d@@9~;9NMbaZ2zP8|{hh4CwHhm+F4}*!EpOfIUw?)zGjHC4^i8g_Smi z>f0?lB4UyL${7a zP0*z46i`Ta@)V{Rxj$gatgxa|@wgnKGlP}*go!~b$BrML>7W&R-wephL;P^HtW9A% zQ&(t$EPWadpY0nI;^N`u$qCw0kunh zfv^SJ$u9gNB*NpU+N>851L8q!h%>|c)_tpa$%+t4BrRyCUhdg|P zI@)CxgH0uxE8G^gfGv>l#K4LI5=lXp4zaAWRo*3OSAs;+4lqSchM!myMHBfX zO^n-UjhRBQqCO|E5)>Tl*g-2M0#k@JgcE7xGrk;)uFwQo`ZV7DfjqH9+czNZQp=TU z{3?`MUH?!si2qDS|Lq0gE6B#O==ZP=A!KvO9vnpuM?8oPads+LUY~#A`}&LY zogdq&=yzU?-m{w|Oej?}wlGYRiumjEzsE_Do|^P$g^X`CYIoAnZd;tXk4j?hN1Aa_ zh(x89Qqh)FRQQKR_6?7B4~*a!*(H~VEasJMsnl&qmC1Rr1<9U8Yz|kZ&k1Djw71~k zb5HY{8*0)&;x~UuE}@0RC6aXlE88Mt6EpLQ%Bq1CMl+L3=Lk4LU_~mDf+-5I3{2^0 zm3Kzrw{K!u2eF6{s zMsjR3ogtPfEJoX{Q}dgGOQN65^0}rg=00xShZ@x}GMvYTXbeMhsDL zi2H6SDFO%d6`eXbiB1$rW>%6o*t4sk&URC3PGdvuQ)yz+NHJ#0Ip758dKF~SHU^WKnwB&dr||Lh)oQh; zgDrEiOm;a-y?y;h#(u+H_X`T~4Gi`%=!mO(YF18Na|?_Y0+$p1LCOy4nJ>+y6%~6P zf0ViNLpzo6@tY?;|BPZsX;tmCr{FgQRj0m_<$ZN^>SJQ&*WDVoY59UMEyb_bray&a zyR-`APtV{;3!8@(Pu~DVn{cL=i3A`v^Aq7LEu}x7b*5-Eoj*Oga@x#K+3aUfuZmWwyt=L?F)amr5lQ7RVZx$g zIDB?*pZ3hl`?F8{UR}l9xzSF!z4$^!TqHRb{vC?0e!aiDe`sJ}WR$3XXsCCf&*Vgc z=D?EZ$FqE{YRG=JUAT?B=wMM=b)+G-F&t%xa16gV92s>oA)$rYLe5C-vVDP!^1W3_ z59>6p<|pcu?#Tm(LTm3MEpMI*Fn9K~hDhtfNcbsRW4>N-#xj!VXiE7#d|Q0>vUz&L&ne zv$7&0Bhxc7;NzU|_Kt`QPfkh6FDR(3so=0_poz{fc{PjKG^c}B+N9Eu&``u*T~mXz zpH5W6MqNS#E)7l1>6zKUN@{v$b8|E4ejb=?cIofeM#jVlTHA)lehZI|;R!hXhKAlV zFZUOpFZt#z_RbCVsCv_X5niY5BF8p1H>5>kAn3NnoXHt%a z$rLp#%7But$q}^H93P4)Sk4+&^dHR_9{$a+bnA37xtzsjle(Rll!U4d--rCdNFjq( z($dm^p%z+`u2(NM!uZv!=CHKVTJ*p!EI(KUl*1cbsy8dkCJW;@#FV=js-gcg+zyET` zH(w$!@~SB0Mk@VmHixLQOS$R0?@z|W<>nPsRo6Dt=u8%e!{rM_t*sKNOs;_B?Hxqz zicUpGS4XF^vr7ea8AhF$%LF2}j1a6Ws< zSheDk68z#ocMp$%z(CNaw4?xB(8z_e?szUQD=M$3ATJrhL~;nKD6*uaCDR$qv9aI0 zynVX6q$aJoP}IqjzzWP6Ur6A`N-q>=RDTGSz205?K=MY%h=ni)hVUb_{BQH zAY^4_`PR!XQA$PnE$iPzc=JQ3FxpNj4ymoJu1$QPI%+*spSnKV>)+*3xA&@#kXOuY z2qAIyvz7Ba9_z?cE2qu#pjV&COo~8wIWM(3{$48aoXMpN*2*MSs%z?KEesZ$%jF5o zv0}`WY4Ks1CMHIT<`huQCRX71$mMcWX8!gwBjvP6vZ*ZAx zbD=bUumYhzmwxr8on*=0_$q2Lb!=^YBPauu5UdOj4-O9YlWI>G(cjxI=kUM+?A>$g z&14tW0R{Zc*T11Yj^6|K-U9$(BZUmm1S|M!eh3u?DFaXT%zm&XXtJ~` z6bOJ|@d*j!SbIQ&Ff)D0(0+lJx}M(g-+ns`tPD)LNTf~Ipvb5wQL6;piHrtT*e1|9 z!>X&R-na5W?vD5E6atr&6p~|Mi?z^OM#q4ainPf_5Fi8K zh>eRYEGlZI(O~#k^rT?&;6&gQzyxCiM}b!4RRB0vBg6e;qk}k{%;Zv9R)#Hl4e1=! z#{{DcG2`aO#mB)dn)txbWScG&5Ez7;IzIjzR&dvcjnH#euam2WBEMh@X+w#2bO2L92^}P90pb@%YMAjxC7gD=C zS3Q%uWBcgH@R)%th~eP9OQ`@5RuB~x8zLu%kS$T4e2AEJUDDAJ$*^H=Eo&O=K4xPt zE$4}R?`y>MJ0;BlYFSl(kMay;IFh~Rkq4&D>iBif=X<-8Lz*()m9~6LC7a7(6RCie z?>5KA#^nPmHMK1*bT*sA{mn4|1df6+LaYw~p9xC@h7aOF z1r7)dgqZ`k2tE{YjKIQp0eT2%5N3RQ97n-aiHV5;-E#BtfR)<1I^-}|GZ%eKQ_9oe zgna$|v!r2ku0M#a|O zt0ad|Til*o>9PKG96Q`!rJrVawo;0JV3(|JNPDOv`ql#fD@sD|tciY$mh&;UVz-3m zua;N!Duj;B?V7mo;8&lTHlL3?^@xOCM-Hiqc|yVcno2Wet$+{TZQdLc8=IG(UsGG# zLZ@RZkIxskwxTEkDF&)^m{CP-po(UC8Bu21#Q|6u?C~uPy$M!;{awpszH5M>LoT-S zsM3-G(zY5A8ELU?6}iS@Ho>j|To5yHf&d-VYFvm`s|8AoDYJO-;=la)kLN8~WGt}8 zQCOk6#`P};OzmT$_~LTx`0&Um(slUok+W{95J?#gKRb?#_wB zhlUT`e{aUo1E;K&OD`rS3D)=A@}J|ct&aTU!vo9iE-5acc6qFNCS}KVSZ(-40L0+2 z?)9?LA`(Dvtz3HXgtcPWg2>4s)E1-}D`kGZ!+llynT8!Ct?VbI=m&BAyX@lCP3bGE zPTpA*aCMI7KP#dhX-IvGT?)I#A9jtdi#gb*o?ULo{j@#fz17oZvdgL`s6)!a?p3#K zrxMAT>B%NGyG=O-PZ%e%PQvAAA{O#}BBZQ3Ky^S1bur~CIJ`aa<hB*nT_z5JEs0+QFm5;-x4ev5**L~3+FTlXU310?@_hwIeZs{=WI}aO-MW^X z$Lo`p_GKIB}G+5g+;YR>3egbXmU<_}iDyFCxz$J+X~on&!;c0~u53zQ%O!46!kF;J#N zuICGZ!WgwNA~Kxtmr$NGz2moAr=$4N)7vYT%VDk(`WV4w%p~z-ZgW)f;=mipH|kLN z$p`A@h%!Xo@?C^AoRXIs(;mcm!5YIS93BR4IAuEsYkHJSYA^(fBC>AU>^=DCgDS~H zTfu-%lJ9+~@p%_GR`e8?SGl|Gx2wsvv5{&ev9qj4MMuJ{a7>rYkPdagpkdVfrAwE> zT0sUo7Ar9^F}CM(fH^~2i$F|xO!EgT`t~ok<3Velv=b|hZhQLFYIvzcxGsPR&tVy} z!8AXI`>_$$5W-U({J^fhTM%1@hha|QFCaUh)8QA`0Ak2c1H=qNhKkuF9vk0{BA-tv z4ZS1Zd-2$)q(>P?zJv)1n12k@;ey?SbK#7jTXoDGur-hjV%q2pz3Hq%*fPgfJ#KtC zh~)Q$m684|R@t6uT_M9$I$@@B)542!*z^d){dnlXRze|0N?^IHS+nML{!XFJjULZ1^dXX z1s!Lpjb7W5^AY)IRH6TMFlyk)pt@4u-ZlRurei^a$ngu0$sQlm*aK?k#vzdl6)P+v zQ>?%h8m@vX1QWnUc~vPp$xF#7QVEH1_K%=**f%|f`(b07G}aR7Q|=(Fz(_$Yf`xT5 zCfZ^E82^<0H5b!Uoo?GuOt|N&r|q;>2n9MO8?8|DJ=eX4jaUa7U{-)QKm!>@gpde; zjrbifcsf|YMw|{|NV^P)@Zhq$V4=Vy*?Y^4Bz_VzR*dn4hZ8nJ#K5uEf)1+?=h6?k zG@ApEcqrt{P^j(H$Dx3m`lRLL`%u$<5_1-Q)3kYe^vQ>dgKw#hc^s({)Eoj08F^1n z#mYowRI8N>7AyMZTP@qiYpw0J*rP5k#|1DuP~$I8T$ZFm@I zKnA0FgK@AOP>3CZl^&&(G{+)NSS8lxbV113IM5w=mmJAkzCoh-H z$o1}4G{9*xe5P)*#ZO`q(`I(8kp)k0tq}TjaK9q0HZWD%XdnC@u>vt_B11xhZBO_{ zT5aKm*g5ZH3!P4SRX`R8)ESgKgK&+cONda0lxc^|j~g>NKd!~**nwUputEk0!)%$K zSb-Jr>ys<&#|r8{D1dd;c2fEA7ge1=oIr%af&PVwRZrs(>>}ZTo{4U_Nvr@Fhwono z|A_ia#Z1LRPJyHXD}*l=@1VYaz%ZN|Md&nLPU@4GVvXmBlQ(9VRV?=CwpbkTOkOdYJKNBr8)eP3vBv|F1#S5q zVTHqH5gB6z9JzY&MN6)`c&PWdfh^ z&5cb>7hG@wm~zChELpNdrSFf>59&WnR|x5>s;ZhCNzAv{pxY*uA^=t_ex7xn2=y7R zknPT96Rc3y2WmbnHDW`^kQ&+Njqfg4pEcw^9-C(FNjZCb2|>b6dc6{<2WG^~IlKkr&~-g)P(TycFXf6u8# z7Ig;5Vxzgl%g}HOSa^DQ;h|*yHXtfwNN6Y=CDH}~650Q1vOT8Ck6WHzvu4e};2;@- zg#rgt83hwrewt;Ig~mvj7%U zw!`BX6dS@>o-l`Xw|{pGHq|vg2f7G0;Yg0 z6L!j}{$@R)Gt;cTF61BJPo5ztVR+`?xsBV82Mhco@^T=m9QBaF&DM7YIquft8R+ie zVd#bFCmtDm?-n{8J`qe9xm5wjYF(qW+_L z!$P_09~VxJ;y>L%ad<={D$tO}=WEQ~awNEfWkg)0bAl}PkB);-ZODs4Kr=B|$mrWX z{ZQs{8v|sm4$rmk^$r-h6vSCK@9Qn9~jV!BIuSVs>M&m{n_=nTJO^3UM0*5+AbY>poD59wOuyRrJ;r;{!1|sF89oJFA zZAqcjTirDknORw|!*MS70^~u88|&)kmYkB}*o#8NJN^DF^R`?24VH;Fn~hpqfrG>M zF9S6wtl$=dHSn0I0kYKaTnL_5q1$n4FfJhu0^<;CSRsw&$j_58F>ta#5YliCe-j(Q z6c`<+*<6C6ayp)oogNU>)@0h~mhXKjYMC)phIGfN%DB+5LhO{Ecdf!RUvHHdE_lgx z7q4c6KMlf}lVQXyiXakC4SO|yOggq^8kc^+g?#o5e2Gs;I8B>{G<;4B^BWz- zlUXDdJ8F^#%BcMH$@^z!uuR1Y92{e;pn#Fre8|`nq~Bx^Ew&>V8SnxfTy_^+Ct}Ok zPAaNBI6~g!M6_53(?NDeJ*!Cn5g^qjmCmTK*8U8%@Ks;0i@*_MtkfnfUAS23FTxeV zRQYw^OVk(^m@Pc!t|@OoD1B}8H=jkJ*uhAFseH`%)QA<*4Q%WLA=&!3dt$<2 z*I)~ILD{_jnDj%Fz9fK#G|OF39=1i6e0lv$BfUOIw>kRW&f}vT&A75 zN43`)+46Ll{Kge(Kzk_H>*9rr67COE-3+4 zcznLYfd~rN-{J^^1vWpy3UB~?5KK_WAaC(tJD`ItFk38PWsDl*kMMA#V6z~kjW%5} z_b6#54+;(@Q)Iz@nTy_HGP;Vzq&v2;mgoJA`OA{~e*XS{`STy)@FSkE@Ni%S4{AK09X{m(0Aatu0vs4SK}afW4GI~gVMPy`m~eZr zLcNq|qpJvaigf3Z?qtFgWXqfmWrlfc`>lQb7peSx?eC;FZ?9z(=-)Op@qjw`X$u-F zda~5%yOWjshjcD24co3>e9_u#KJ5E7nLW_-yo9XB{qy%*a`3!3XC?PLTk>+PI2a>yohhv9b z4D>3e9nNoYTtD@`4}}b@kkPT=3$a0LV1>-3pdZD%?lpbeiWRJYBV^9ws;VmZwiEp? zb#)FN^kk{S!M4qU#8-dyhMib3Z?7#6U#1dIc|`~w&`_^*;b4W$ViHZvykAox;4}5j zvHe`qd<&F70`_kFjg8s>G86(Ca zlWCJ_AMnt_lNOaBA~JlM`Fag6E*V-G#z?;#0V2`~KZsC<`ZA=a;bTmWor=ZI2k+zn zT%!?gQ-{n>o#1xN_RO_prdt~w%zkBJ83nqgu7Fu##U;5EByFn%cyqIEwK@IKaj1>nM}dApPo^nlm;_mNm1;o)^JwwAaGffy*;xQiZy8aElf)R>)Hk4^IH) z-FM#wK}f5LpZB^Y*If+6m@tKMh2~>_^Q1#05{pq$gMx$q^5;KN?SDKzzfCGdH3nbE zyUq@K{#TWe78YteE)wAC=4LwKkiOGrjKk$34S*eN6Y~DSO*h>{p68_Dmh@o(JwCp^ zJX6?1+<7*A`Ir%SStmt3L5{r9!d&h>W+JyVz{=ifrMhbq(=#?Rz zi{6PaxJ6Wdv%ZmKz78rS?Vzv%3@;6hos5Y=Va2ULjlJN43s4OlH8TshTM>dJp$o%H(v90V~S3+k$&vw0ZK6dAjL8x)_AFzLPx zJ7lUV`$xx_8!Y;-#|edC^t3|C?Q9!@IZ0kj1x%?qk^!$XJfUYZjb<{@hy6p~tTn8d zkFPBbT->9iFC?re*(?ST!3xU#$(U%E@vdIS0TD0-77FSfY!=url_i^qLxdF&$M{9> zdD`D>L>5#|aD;q}6%T*CLv-aoz#39@9ZMEtj{xgr)_yV}OIljmQ?XbxScox579=k-D+{Tj zgujMY93~b(-2S<~jAvA_(bzGk28*#_h}touRuvdE=5gkGUurV1C23eOU}d5*E;Otd z&wY&yg&6{x03YNPA=oUS%+I@4Aum?1UacPyH?_ZcKEDWqs)&jThhnI|pD=}dULF=Z z@jD##Bt9FaJ`gBlQVWvv-3}z5*TIVGVzOb z($j2lUcR{&OcmqKX4ltVvS-F7+iw_}j97u?0{i9nf|UskX!Kn|dilTm?z`mWAYcXS zFkz-(rO&meGk88@g&NXACSie%O`fi#V~D){>_D3ZjtsFZa3Vkib_%gR$dKQOL58EF zEp$58Ni!{g0$y0_nIf;}A(1FP2oC^}Hx&?@LnZ?!FC5!xu)qq5kmCsWnIav>k62Wv?GF`nKqH^@41g$zwR<5s_#W@w8+cw*O_@b>=8pZ`cGSyfeK zm{7(6Jy|pQP#I;gAnQmMK9cw2mZyDueNBXoB;^n=I&FhDd=1j{OZBG1{W)8&hO?M- z+{yu6=bne}vu75#DGe+1I!mttxmawk{as>3Z?^ceXgfd_sy_(7RQEGcq5pMf>!~uM z(ecDIz*7{a3Ta0q-7(lg42OxjJ=}jfZc%+*6`79`)m|o-v{^Hp5`?GZ-v9J|D&fgo}bPLe`Ci zjo3xjn`yLZXIdfOrHzP;q)@_P(;PbvaHa!T?0*BuM#G9>_NEmkZ!J>|E5A#u7zXtt zBYxfYQY(KCEEF=jf8~nn0U95#wUcj5g{OO5XM2|Hd1ABRR*-5y*(~}OSVu>(9-k0L z3;}(|^XTa57%vf#;lvdp<9$i5Gft76lSAJ9(R&B_Z~fWvY1iR?($cJNg1Pb^WQ-gV z4;oSfh)=(5s5Z{t50#M*HseCTHLYR6HG&%2$pxt;E7*u#WPRFK1j$#fO+Mr{yLnX{ zHR2X$2W{=|RJPW&TpD_p$>3W2g6{4CdNJWLsLT0ohEgfW11^k_?uCr=znfN&LFc~Qelu&K4WUvf2lF041G7yq9 ztdJ4IaOKFfRfzmFC)S)@XLI>iuerYV640#CYBdI|z|cSniO5E*U?X;szqNIBwwp-F z7nfi{Qhh)Jy~^sEnzL0-fVP=-DidWVPx)e$N>WrBdY#FbFBmU^nnOko*FdGz9q=!s}IyCy{YfL=*PvJy*c+_zQI(n z)9W$*JD`(H83L2_p80fe#es{v6%D_4tWd6yzSp_;xQM%(jFh9CAj%bj$2k98Ak$dtO&8-uZNaew*qAIUI$YEt{zxmTbeDk@^bgPlxy zNt&N6PAEIqoz09bZv!9XH3ib)jpZhzg=`^jNI>9)jr0Eh_Ra)8iu(Nj>fhQnQlnR$KL*JRmjW@qR7p3if9pNHw|oRx)7bSOBO zMG5ug7_2MAfS|IbV+HKcTnE(K;d;{Zw>MLD*kXktODg5R{pOw#u!gv6nzl%GjHV*)@UUOZUy22GOZRkq@B-LtdRke+m|_=yv!P{PLYg@T zSjNP~gaws(brMtxCD*H>DOIS(%Gs(AUND=+aK(x`-c0V>3$=W;(tF931WSVVc+JIH zmO03uin$63sIyv_8U@&gxv}5R4{#A0XHgRh<_VhxO95b7hLrwvtHQ2q zBo0btd%~m=C|FpmQ0AM~4gW>2(S;QpHk2LPu$@JTnU@zDT;qy+qIecil8^0~lBJqh zVRBe5H_2GhaI$n&^<23W>ow-43M_f+9-cV*hfPjwyxH=Z0=*5@P1Xd{UXv+v9YS27 zmRHzk>fj2*L2n1%;Y3sv#b{{B4CWR7goNt@w1%;9!#95QuvilyDLEOsN!_|M3|s@6 zVew($89d@gbsxwudIpvto}nIt)L5gqs6e4m;5?I8h5Y3|6;dlHGEsd=AyP$PP?;Ae z;ub@IW-pDKsmYXf)q0*|MaBy6wicP-J(^AyXo9%}tUUJU81lJ*G8P_h#>EYx&UShw zQ;1#e%$9w++TV{Ed($PZ0}v_6@vukmZlz^qOqQK`K_l}HO!m4-3UuRE&t~*u0<%kg z&66E>JTelB3xG;y-rnS&46h?&+loIa(vas=H zOKwh#TRkmoqb0?sYM7Guxa~t|P!B^qc}Ate3!u!I9K%htY}(8Ofrkz_FwKY1L$5awycRb;g%#shvnnGYDl@u_3P@gT~rC6w8GuJr+qlQhx$5G)t zH@KcLqfFnho)}9 z?9G!U3#~uKl%NVDjMJp21N-ElX;pU!9u#$k$qAehC>dw7L@^Xmjmo&CSm@>tMr3AX zF+JED8(A%*H5CQ&1TVudb!~mA%%45WoM8j|V55-Pt zDU>N^YioNvG7=(%D!GCwZtm{puVcnFg%T1IaU`c|Yq?2fMwVg`-ffFr+5D5=N1e|` zIr!KIpmBa6`fxG=Qd(9fyhR3-3+}hHdYD`Tn7a6+zzQW?ki%tgOG99avb8BOQMn#a zvz21m{(|FAReLpZPcVC_ehMH`SX6Xf$kf*imO?SW{2@_>3kkZ6XBKHqQQ;=+b1NZe z#Y%QorWIhNmRn6oUPd zp;Q{yJA%g)b&BdGflQ&iehTZT!$5tsbcp)h;0(kIsZ&g_NKaEWsXw<%2 z*gkBrH~?jw?I^CRCCz^_zCt5|sbq2BQB>%f>0Gg62l-QoDZ=lNRMZ-_50ws~%J{H` zU~y1-3vRsX^0Zx+;5nl8jnGh#b&{1|3>Hy}g$cWQdvWc@!K%X~(ENc#Bg*`_UWxK% z3y2p^?(Nt%U2C7}N-*E5%hr=Agnjb1 zaMw2*8E=L{p$HBRL*V*4uzYSjYkbzI)wW_>>vPzI>zG}V3a;7&YmQ- zl%&E6E;P2ssF?)|FW5YY#b|{LQN`cD6gJ_Imxil{DHH%=>r3jvN_Ljg+OTrAskXc< z4`=O2csPTMZLR}?2G6jdVM^DiMashyN88xgu%-}i7+>m%rj5W=Fu10{0$RpqI8!)* zXDXyf?iH$gg2$8b@!-oKVx_OU^}@M^&bD*x`DyRyF#MD1l6ztrm2rd3z{()HV|8LH{YCD7)opTSuW-l~UVs@lS8gG=;bjDAiTQ~H&DAa?PU1_>uQcSX(`8}ODnw(J z+cI6LQd5N#1i;mdsa0H3LWkGT*tm7uHe-!>b)z5f1*d|T!qz>3O*B}p$MTZgx0fv1 zwbPBT!nOpIIICT7b{zHnU&Sv(Vy$_#G5Uki}7n&rtoUkQm*;A z8pVKEu}&jM$Ty(}G*n-n$kN#of}x8n_%CBjA#Pp!L%~5>2?ch9hJ~;tJRmV}oKhJD zl)QhV6Zm886tZ zR4S?BG&=@ezlbt_7#r}0FRwgtB`>s03-@9BXy)t-kt)_~1R3_-u%N-D#RYvLDk@s> zy_>sxtj8!lc|k^+Li!5=;F87kJ{}oKA_ccf9GHaVNQf-daUJ3XR~wW3q9zM@oyuYYxM`H+(0Ak8GK?PPFv9P5dITA^4nJD?>iD+Vq3EjBJrZ1UUn zw8QunCp~TX%L~;yCD!8}Ud5)~4%Z|f+g6oQ2v)4Y2*T@2ONwb2l@6zoD@k`DoXV4) z_iA0iNv04}>WQ-qM5=-Slo3m-+A!^5m|)JEI zuFDOdqQ?}9cAsi+jybO&z~K;>T}`k@4*2`i>R4LNi>YOv%!q?QffV@$>?C(;=$syH z=Z=JhR8&;-_V#vn<8$|%J$n}4Z_uGbUw!qJi9UFekEihLIBu|6SN)XovJx{dEEI13 zz}TS`2{@SK}ikn`;=$Ct;N_g^|IstXIlnKWQhKv;f37X#H^_W6L7UK&7 zzyP5#LagCaT2>~x;WMy-a3jMtDLFYbEX3qR?Jk{#Y{vKK8>x8eqh;g%IscL6Z-2ed z8_&eX#(wd|`cu^;OSD-z~qOz?`e7V3&z9e-hjoH-C!Xq{`&k zblvj3NFJl0V%Yx`nEz(QSSg}mI;^P6j*rr13SmIe@S3WK8r&jGG^NS#fo3tkM-XBj z`iKM+5M$CpGnZPLPR2!NWMw}3+3Uaf?5{sx|5rTzV*Ou#{@Gv0e*EIG=o29!hd){K z-=@YYQ~Y%yrC7MZ<`b@)bd|jBzTBd}e~{$k+Zm1mt2B}M1G$mpGXdrd>w@LI=5&!a zTg;3d7iw2Qzld5XO5@_i3o=#)9@Xg^NIL2gEcnihNtc2uDJ&{ty|Zra?xyf52w%qt zbBr)2VR2brkgL2T@7r&`j*gC=_{rbz+%;u{x5G_i&*ahT{uUM%_T`sfuKM_+wu>go za5XI0tXZBGUzs^;9vfi(;J(<9x9F#Cgf}}@^4&M$1+7hMxmUH?eLITV21v$=^aDnz z9#iOQmlCFsF*6WkIaOOrHQc04Ej7jz0_G#%K#_n+^)Ki!y&8vLHjwRxH2C3%kkrrUr)YQ+Y4YzfxJoigbw;&9(YXA;?Hx1t76C zjUf%lQ+p^l7+3rtVukBVJ`x_Tr;CNC(R1Zua$?M~f4%$COE1lyGuzG0?N7@V!Ri^Y z*8xoVpR#-JsTp-o_@w8(JQ>n^$(2`gKO^WtBh4QfD~1n%L-PyZt`*7-nva`_G49)d zK!3J+`(X8~VuRceO_Wvx@gj*S1f2q=Ok=jEr%#^_1LuF=nfD8|T7gIbQ||5f`ESnr zior_2BftFGam3O&Uw!q}TW`Jf^wUq{cb;{0JQ*7)?Z-S3ma_iY-xh1gl0Ka zQ)bSuNtF46&vnw%mbao!(*nYW;@vFC{1F)ak+C9;DWvHwm(UafR;Zjc?tuq`0tdbL z_R08oYE&-C+MMpoEhl3X|9t1|nKNg;{PN3d)~p%tyx=FSRNnKOGk4un`|Dq)*}@3= z#W#-ky*GE=x^+-1FTC)=^D}3D_2uWaHKme&Oi}%G*kCihATvAEGnJ{r&7YmKX8L_` zx2Cve@?~!pJ4{Na#*#we(H|Kr()b$nn?ju}A;y?OWPOk+6c89VxE25LVOb{&_#%}T z`FwNz2OoR@4T8s6E0+D7jG!1hh!g5wX<@Z$>3x3h zU$Awb^F8?BoU+~1%lm$!jp<;J^uPtpj){r^r{KaGzqp7V_7g`jf=bkAQ7PbIQzVj zE9};0|06jyRm;F2=5lpz4y$d>^z|m=INuE&OVoYD$hr`A;0V~+s~N;!M7Dw|IPcDV z$Rv7rWR6TqpiLV$G{;}|{I~N;9 zPpyLaHXvQ9v9Qh%BN%J2gN`n>uNF(oDm@}1N+aFGn_}v}7a<{Wm-_YV{_V+Pf#2`W zO3iZd>tQ@6j?98|T(6P1x>7f7ZF5}#O&|D03S8|d9Gh1gIoy7Hqtf%V8VzQj>hvGF z0tf848tPV=<|8XBghbcJ1wp@ecl%R`x_w)}y}z9)>8XJh)N?L&yA{&ozm#E*o5!() zpAq0?7DK`|x}%JDRA%x~FhD_PnY838d+BY9aZ~wDb*3#3?DOzD>n)!ab91eknB+{m z<}DF7#)t<%I7tWlmHMnwgRrgxyST1gVw{RjQFhi>^jwE!cZq_lKO}06#!f;!>aR%x zbO6Hr{*-mzq`-LzeBqpv^gu#A+k7iPxB4f{_}v^-lOb<;TvKnP)hPc z*~(*k;wOfqBmsBA*X;=drV?Q!kESX){HMU;Y(sJ@t@~Eyaj>( zK9%d{O~$w(E^Oif(QOQhz1o)R3C7F>#YT?dze^zSkWEt@rD|9hVp46sPETWs(d#F`MPP z7*i~4rDj9Eo`>Yc05n6Yk68HZ>a2m2^o*5tDz*x&d}dpHdRC>XouR9n&bOj%A{J!u z_B4b~YsymxGA`-&844;bE!Rv!>M>Cmy{fGGNmnHGaW0qiwrg=vxe;2-)4Q>xHK7jl z7P1pNjB5$a6s5}2E;Cdf5$);*S%cNhb0RvVH-w6`+BGELj%>46dU|>&B`ZGaITAeL z_uQK5>ag&~t062J6FpsyIJ(Pk+c2oLl#GQ?o>=z$@XO?k+VNOHvc%^PhxVVWBO@c! zsSyXGTsnyH1Ad+M6p9#Y8f*LhJ zdwo0QSkH`1x!)*Rrb>@j$JuF2YnXl@X?>mNeQF~W1zs51OPmryq1e-Tvo~xF%{2Mu zGfAkWRgywTF*gh|2(3x0tPW4K+j*^l!WlY7M<>P5@MRp}2B7^i4&4>3oHG(zYtDz0 zRzHygY=4WttSv4sZq`(ZY8RDHPQV5QVPRpd*<@=^A&cNEj8+P)!H^$lQ4EyoF|XOH zn6xrx=!4H|C;k@9=pV$I-X zO7x>9=vHb$n)`V`YSb!M2S83p5Op{{oN~XzQv(o{_?m4IXDPyw`(mYc6ew8b;aZ!I zAq4#q(TX87dpg>tUT2-n9te-Z9v-biQ^0XWW8FHS*+=|G_0N7;Q2D4%?{*#O?eYGj zNxmN%TI~A9Mo3!Q%F1Fn8L+)~PEZgTYYZyRRob(hPvq!2sYc$>?Jp{Ab0LH(psam~ zDr2$ZIXisqwe2G6aUQ`EPT^P>!yn{GzZLcC(YD1FDCtOd&ns6WN-pX&7@qId-n7dY zY(D9e$VOlyBE*vkVs;Efyyp1H!GJ&Jd5m>Mk*UWwXJk`Ud7P|3n9j}DGUcCis6Hpt27{UxI%bM$V) zyV1(V;t=%vjn7*dqw*W!e%{4d7z_M0sMYm;%WEh!Siwi<+aAJ$US`ER-ID9}6Vr&b zts|}AZ=2){c<~zYp%vvw?x+pXRA1y|kSi~~FRbS#<>#iQMQG}pnEdYbST&_G zdCu4{e)Ekz-f_G>itpP#eazTsS9uxgQC;;nQPV?~+qM75y134nM^Idg?4!jt%JUSH z%^_Onz6_}#qm$rwq~D|ufS?F+5+b?nAd`vE-yGs--nPrFptEa+)0lD-v~{-d``YNS z>nwfEBjEg~#rg}si4KP77ZG%1U;vg%mY2)2gr%e$HX7E%%kv$kB@sH6zebFnls`Ad z5F)M`!sF!HT4qZNA92`D;LBFa+oSVnZ8tJ4uhWkyvfJml4bS&4cYV74yUDq^x%v6| ziHT}PnP11klo{bLBYn}$#*(`Wb2vm3RzVBF12J^x!4zr?T>e~ZY#UA=#bkdIOj_cN z;~NV^7|Yf#>tzza#6C{u&IomyS&& z{}jx38I0AJRN=OUGKhi$>c6N$%xMFs)mX6}uyz^kYpR6;#Q?~Gm7Ck;kJm=o7k%M< z!uqWB;9^)J&as_3z0RL~P$hTw+w1)p#H~7VPc!;Y%oaKj{dMd#3i)2cFtUb*P*CXM ztf*sqV`GQ`>=HnGkcwtcIN{&p4C0S-%*^u)V+I=E#Dor^fQbWC9^fQ}NvL)<7Nzgs z>|S0@3FdGuB7A911tKy^iEld6`T4y!FsJL|mxPmLC1m{gb_l;W0cd-oK%O6z*_agmYm z)ubJxClkUZgZsJdFR)g>!zyuYJ7>c5_MZ;W2`0eNVrv405tTKpxRw}Vbz}dDz0mA4 z!6@me`p1t(4t8)>|ETpaqr&Z7Jgm|&vkIwW^R=TrgwbvYAUT3j06IkDjM@ZMR#v6r zg6p^_o_yNh>+-U1XH~cP7fL3G8(~XE z?nhyL3+3&(kO`X1o{CvK{y*v9@t3c^*Iz^Fu)_oo{_@)D)*|WP_Q<1I#&C=&0nJI} zckM`iow~oCq11`20N4TasXGDpTU^oNZ_jSEKJ6NrkTzcxnvk0`#Bdb*g41l$7}x<% zGOo@#0I<{2zMjn!z5l%xJEluzT02!xyo!GlcwK}g7|=a2Ha51n*j&OKMPwg&i-v(l zmx20Kf7&p7fv0)DLZJ03J5a^+m`o{rnhNHyIZKn;e>8!708gpG)R&X{EFgR*y+uR^ zhEc&U(Y4LIvqCVbvG~#gjtc08IxfOE9Zne{8aOAxtSV+m7wVnCCs-Q@t6ETM|zKZ^Cb!(AkR+@7$NSXVcz` z{@C{4Uo#qOd|p>2dYX6P+8s7}-(H^zfnoU{O6MmR;dx(>o$KN$>I>>+4eO@Y6ywsH zLnu4-VA(Mo_b`#80ef7A&3&c0OaIglrZLoM=Lrh%5D}QmGq7hR=we;l8J;Ti#)XB; z5>@nI#JkFf!DtP}s%@PM^}8LY~QeWAuual2&3!@|W6{qC>NI0$MccoAc(mL3~$F z&(Yyv{)406vo#WY@3R8m*+mq}b|S!*j>Oc@<*Tj+c9{)Ddc9pwOq-s=vlObCbieGL zd4;n*k1UJ`#CZLVbEm|)`yI_J_WEZQPuV_U--r6>QbP20(&2S(=jRagFrzzt`q6uT zlvDiWcJUqXt-ogRrTGD?}r^iB2+ty@u63RAz~&{ zL-|sp3V);uOvafKh`WZ>7P2?W{4c2c`YrF>f&4(6H z!K!Uj;CwsRb)nkKyShwLq)GrRDLf}(-FQ3QXY~0&8;mzIrs2|4)4dlk<2T>ODW(YP z^t(MT#;AhMJMECR({mn2Z3yiq{8Io;>6c4F)$jJ@HTu!`#G*1JJ#TACK#6j!L(Tko zKeC0$C2NH zK^{PW#f6sFjht$;oIvl`21Tn+ueV$=@L(dyTlVec)zjId!%QvXsT-hAXbBxL=t9t1 zZAO}c_m;j`rZrsGg?2AY#l`0r7Y_FJHZE>1iYO`5#fJGMrHtkx6j%3iGgB6J$ZFb3 zQZBhm9%>A}JpcI5;7EmG2H31{YkKat{Vw<4p6|CNx_6#OlDzC`Gj=?1&tYU%UKpK* z0%Xa844)6*(xK3|X1_nH&#Ehvrsdy)bU~JA=*689qAd@N7jG|fRfm_~j-TszKXeC4 z1`~YjMIk8`sdnuiICJ%cp~Y9|1-d1pAA^{BCcrsR(e)O9YuHwK~((B#%d4O;$_%8a_MLe^!Ysp2j#huxs)yU<`8)?d`6-@gP#Q^1}}GwUV&* zR+H76L6i4H6L4?KYESoaljL!`{zE9K8nfx9?(NK5`rB2v?J9Sx;n+%NLhX~8%1rxL zu)wx1`ljUg^V9F)ply=YtBk$s*Tt#=dP$FA~6SK zpPtb7^VEeXom$kP{X{pl&e9*&zUUBdW5vlcFGi$W)l}?DX<^m&aSCw!R?;OBcDy4N zZOLuv)g{G1POMz?mH>3Y^T8e3znLq6l(~4@r6Nf;;|Tw zdkO44&=4+DL-!?vnUME4<`Sh>0j^X!b{0`(icurq@~{KPQ(VMyUgw%%p88|B_5HJp z4gv6D@DG<&%|YzxKn*4n?^zBWu6gAFYZ z$0PI7x>CtO_rRO2!PFyLa-AHtO?f;0piBul1|++wA7kHUxCG70&>`#(9VPnT#E6xY-bFw)i^&s-CxPGyqlw z;OcLG*LS>XgnwPDG`4R&2BYMe1$Ft}7Eyf)f44?;JI2vqeSDd-NrComHLB-s#gMb_ z_WN`eFA=**cs&euWp%xmvvaT`l2NgRxk}?tvj)Ti6fE$hYiW3>Y+pLf4^V>h1tXnu zdrOrqf3*q(Cu6L8T#s8=hglReNK{DZ;m@2zeN9#9EYf@EdFD7nr9`~!OmvH81&eD#Gh zHOb>$J4y1kLDc`Rp6ox(_>rfdhXf?ofmvS_~gTjT(jFN$Sjf&B6x z*i#N5SaYiHWI6lWY?p(=Fez+p>`N+0GR-4-dG+8Jzv$Hby5G_e>0`DX77;yB^$P(_ zqNf)MMqAH{*MU<+bS!H&wV2FetHEjVr=u&2Zr_-CKG$2kd}1pb|8?2vs=lnXf7bj| znqlJquop$;bC5Jf833$K_TL<%Slamdsj9TKw?}#Knhk~{QF=o|eTTmM{Tmee=*BG6@$K7- zZBUTA3i>Q!t%ndfu6eBn!tQ(+!Aq#DmPa6|8)HI{_fMM{(~#aZVY@PVwAfY=9gXMF z9F7(?i^H-Tu*cn9KkCMhIs8%w46w?N#P;!E?JJ16^zq!v+trJps@OGr35w;E4B-3# zKCiH_FvUCH)J)g@or06RwXr4Kk0M}w-Qt-15`vk9tpyu@5edyB&lQ!1Fp+|rDih6Q zan2c2$XHCJ^ps8lf%-KRyQHCF{KpTH#giW&3tF3-HFaZn3uXm_Yb?0sViaXXnkU%IknE_=2pZ#?pfb@%6}k-+LV{Q?O5%j;}pP=_@ZiN(|$Fr zeeC|u%M&R0Z9Z#0qc;H=PdA%R5A>B z>Fu@`M*v@I6jMI~>?4Zm5elF{t+qW3vIR#_pMATF(rEH>=lCx)WOFbX*9+mA?! zDGOM!U&PrwD8dC$FfamMACEu(=dv^DbUg%msxCiY=)<@k4w-!A{oAHUtW>o8Igc!=v-6Vj=-lfOZ0Ao|V~yrBda$IaN~Mq@Z1ElEv$odO z5#5BRzsus&%xIy6px>(W7*j7guVR11i&&a}2!k zH`FFB1f3jsg2L(9iAMl?mQ0tY^noYD4&;Ms+<6*TRo8Mc2D$Xd1xgbNRvXtA4quNe zIH#cA9~~VIG>jdOFj0tF)8~&Njh&S8;0(0zTvdwt4_Mg27NK83n-~By48>+7_`Nnv*T@KC)3h^pspfpO5RFXd z-);rO5ozMWTz_Mq1Oxw_mIJ#ug*}yIcs52b3$-$E1+(RISNjCN-FpR`%pf*$u-J}+ zXLKL1!7qMLEz9mZ<~$3QDmliF(6O8;4Kelo_XegXa!LTD;+vxGxm1rjT>BUpmg4sJ*O>Dzbn45V-I z&%+B~l6a9QF~m_rWiJw6aU!?lZ`-Iye9TLKFb@V87B>w3b8KE^U$jIeP%Jtg$as?9 zl6_=ZD!u~3JDY?98Xq1SVT!(&Y$v@bGgei66;(b5OCyGz*&Ld74gjo(Rlfo5W+GYQ)HX~}j@FS4yGMMU?o-i%%o{{rH z=Rmwsp6BQ1)GCl6VnOzW;eV%GSsb5at67Gp1TC^aCsKQq+1qPKO75+%dx_)f-fr42 zA4F!%kr~;M1ml|QX{Zz$$c0^HR6WV9skIO_-QEn|RFkN=XCv^`)%|+DE%z-vk)AHE{T#KXg+g6VvpY-lVTq^hi%AsdsV zH*l~@dK==csF?7!e$67FO2rJTGkz7q5?%zZj?h96TvZqVDGu%%ciVpRY1)mP%BEh~ zDPj^$AkX@HIs=egmIKBw3f|tI6c>WI^KP0ztzYj+Tc?tOS!`E1`(d*sODyT@$%Vd} zeltLl!BUOtH<3JWe{Ew*M0gk~qe;-fO&;*+tl zU&N7s2TmAHc%Crc7?z$y2umh)2U*XGI(c*IeLn583y)4X^w50DXSxx{eV$%kX8=fs zvoyaJ-HBAVYoTr(rBsRv?0H%X3Hge~;mxp}qO=WMZ1L)S8$p_v= zfejl90Im~hXL~`aI?I_0<|l`$Dil)!#Ly7zocO@@fJ0?VY~7(qW-P9cSHi!60bg{k zhaECaPAtV01VC}YR3W&VG%W@yqwZm=ojqK?%IIn~4$%LMEh(o*ZJ{3h%FHY|u$+EI z-}>PKRLE4A$P8GHj>Z)AFjbVSu#H~pUf%qWB3Q~sl_UYCuI?EsPwYD7x>Z8mQ>>g^ zy(Y<(P-@HdmbO-p`&$?)A=bk1&ku3rQu+8R;>lU&=sM10i$zcel0hZ( z6M7RS?bFZ4ZhA|GAtjd0`)iH^lF|mYwBAw|AqqMF0hJ<1cb*zv+-16EK@zWMp73#K zW!Xsv$>dk}s*3NevdpUpjcj+=4^T283<}&Pu7MF?y(|BZ$|UR;jX-bX3xH_K&2bg#o z`RL~ZMNUsT58aGX(l?mpSnICG9)JEIPDyqX1sQ$wWDkdw5aI|`fj$dC>+;llH#it% zP}LwVRxk)eV8Jz&(1>&+K!_NeV=tti$N#i{r81O(3g+_v-ts6@Xw*UC@@d_HqJTCG?*~ zhsCHbPO}C*qG?~T$>-;`}oq(@#1bF5MN|t>EPBwaLcdz9E0h!)F z2_1}$gtU>zM;F@QSaaTDro|zasQm7Mj{n5+@23uh!s;|M|Jx@qVw(C9bGSwE9{)mI z+we)VRKX`&)ga`<0xQ*(7^lx$_*t1z#$)E&E^-|zs>D8yN|8t_Th4d|4geO~n=$2E zj6nP97%MfsN>2F}6AzaOf^sMcZU=hwzOyu3Qyz@~1 zQ#f72#ubrHr@`&3a`|5v$PmWxN#_^=3YI*`>w4Tln#EXLILojhI2DHvgQ~fqLc!*R z)n_w~pjJiEuvM*E`W_Iv)rb<3!at=}G`Xy&%(K`NM^97fxJO?whu?n&R-$%2zgX|g zK=V}XMp1wNi3$z-`}C+@?MW`wX&qk)GBnn-aGD#&YnE1CoLuR1yQ2qBKw=^alM_BFI zmO$$FrZ;K;OaV*{_-OU+i_k~AS`y&xc>4J?Mgku3k#vVTl9LHJJEXE*Nk4fcnV4Gk z=5X}yFmr1^e(b3dv*|foohb%}KwOZ0c!biCzMWj`Xrl%F_7XS*2#-+byvg$#_wkBv;^HcQ^R~U`nX0WJ&5|W* z_Q+3wvVgc}S-KxP?(0BPb(9UUf{-SVvx_vx|0s=O%AmrdA#%W}gy$S(WpU{AT;Ev@b3qXO<_=SW7qh*=h`^*kOiv{ zm4W^sbvsPQ-)un2uD>?6kqdIiXzbm9ZQi%6$s`9`J{Z{xJ5{-r*SQYfzNm4M4q|>l z)Pl2qxJ!ukl|@a2Pl8CoCIeUrwPAR9qhyg}c`zGS5cE5q=G8a`x;EpSfb1XZGcvJ_ zkoVK8wy%lbhJ5AB^YX5ItDZ-V;bp9214UDHm|{=`sFuj=kG!rD8)=1g=mq`VJDoz3 z0p-h@OdOKev;qt$6qt8UKMrgi0U{h)1Wq-kbCapr2>=pWJ=R|MhME*tc-jPrCneE2 z&Zq>0%?j?02<1ew%Q>g#DSptr_WYQd^4Z5!Dvpq`vf$JDgMmj$zIPOdY(G!a(8(&e zmYbek@pZ+rHX=MEQun5p1+gPBJ^ur3gb}+pqGjiH|GBm1FRk|^UfO&gC~NY8U_!hl zC|*;OyP5pmb_v#8m1ySj#{gZsB~qVavdtq>I*aP z+k71{e1#Y!GUYGKY+|con z{1YXrk-RTOs6Jp_D-wOLkI3D{W+BU?iA+WlreVOl`pU*Pv$H>FrfRzeOjZ<$V-m9r`#7XftkneNAAEPslp3kq(otE%_{+FMMLaLxF_ zX0l85yzMfq=xl7N3Hr@5%9(2NfpEVfYgL==nsZg*kd;GKFsid2imUS}g1Q^B#eQiB35=;34Bhc|@{JJ?R2R$m0 zQ5K0cqhZcr)R-R;1X1S0>2bIJ({I3~oA2GFXEgE*H@d!yIwU3VftN9GTFRqKa((yD z;%6w^yF3%18o3UY+kB@NjZRwJa@YwI1>8$F1b;B1-|6QdxVstDt~?7)8%Bddd>BUW zt>?UUkcXnGz%UHn!+xV8qGrcVc=oNA+_#84*BBBUtJ6Tow@(KQgb0Pc+d9 zso58sW*)}|k&J_8LSdV;*$pfE@MBj5=y-J4Kww>w+453Gp2)bOOPG|Xsz<8$=y6W2 zvqAFy&=Y=-p&^KhnE{1oM_`jJW7>Kd{3d+?%1{=Y*jNlV2LU8_?bnyONklc~&E+{6 z5@Xpmp8yHV8a)mjYHsuWpK;uVr?y}~Sg(2QptxQhLjm~rK6NI>W9%{7 znW)O_%0);N$YZw}N@VGo=6;VBUF%2fj8h1^KxOQOP&;6~jVfotLb&2J6{|g=*GOG+ ze*M=L@RBtBIRT(=R=EksBmDn<{^8Vi#*L3iGks3s7;MxtRWBdG>C)@y(oHs_=05tY zaT^f5Be450V3eviN>;fah#n;ap+4nW&riZUqg0(1x2l5M&Yt(1kq;`4%*lvcNi^z=tw$Uh0CFw%B4k&n9=9r>G z@xZ4*6>L#ipxR@9f9?Bk#La;p_jKllAFYUwr7fPKs;_8jXb>^SM=xngNbw6nnDQK1 zY+5fzE}5a>1|X$Xu{b{_!r!cLw;Lx&uT&`=bb0a*kR3PR*AK=$4Mq0k!Wab_9P1(j z_Al>5A}~>##%k%}15A>8SJEvE;J6!fI(Xy<>88>DMnOWh!!N>|oPihf&yY?7 z#nI%om)^vZ1T1J~RUS3A3oUK`+Fd@jy)6zb?n|fED3^j;LWN%iM!~lBg}uZ?z%*xG zOT(Jh|6nJdP}mAdt31nWILu7yDU}{#(*HoDvw1YTLOQu@<$JZlpk#w;{HgA}t6AvG z5>5Dx7fXn&YQqoQ>@GuL9U$ni)~e@aY!Tpp^jaV>P(We8U6b{{6wQz^&<1kmZ)EL( zYw(f5H*#S#Hh4TfT^qeYn*%y2EUCK^D#?cc9Pb%^^_}TJV*y=R*#=cAV0R6Qk<^#I zPOm|MSlhdC!6`&(*se1g^QrMaR~jL`03vwx-;=YsZ+GUP3y?3JVE`N7hyYWVLea1h z2z49#=u5wEsxKc}v#sKm4M9@`WY%EJSs{IK7*#eEfpIBRT$E^fSvy^JaKEI-uWdfk^!EC9sb5L2yE&FKvXF|l~ z``+~(&Y75x{QArhR7A{i)Gx|WHRAdo@W9!2sdw_J9^A49n$a#zHJL~WYA+6sCq}Fq zW9;LSJR;(}mAlgZpv0*jiz?VAxe1(B8ToEN% z+k)c|gu`2lk<*kzZ#|rDIepzqCe2JpsdydrxX^|=y-Ec&90RR-sj>+>0wwvOb>(*p z2EfX~z%bAwr-n6=0yw|b$i3k;B?(`WsQvE`vJdOL)7#k43?W6`GZKRSO48fKuoLDZ z6Rt)UNO;=bGClzBYcHe!mdGahdlW)rORXB4Gl0Jf2WPqG;|xT%VYl*^G+vcCHB(+P zsT@J4vUOykU_fen(;Q4>Kqr+(Ae*Q~4J@N}8fk`a-ET>XXaV%|ErSYUuxKnmJ8zZ7 z!v37o(-3YjZTpUcU@;5A8j=(5)c^FM0hK8j`kG#R_YL zP-IscwZ+F{Jb<=fdnMhu;&$~UReCk`VTH}nVqvzAaTEsSvCi*++(^Cd;!|U zKTw1!Kqo`fR~URcxM$ej+$>6p^$?uprh=27m`O}Z*t9Wq7TgyEu-`2B&$NGM z)YA`-l8z)gg{-@}CCgRK zT?3NcKoKAv)+FL>tE>AD7$()cQ*SdF3E!op}#rXZ%_GHJk=mYnW!e?Lx#EvD1rfAbdqS*(uN@9OK zjCXW-M}~&r+OV#qOmJCkl_y$j9UT7t{R{L&s1czncb33mZ>_4`U1(2`fo*Zb=JOPu zg!qY1ecjFa=0ajS^NPiinpZ2i(GeThpIXP1=EUeG9~GKtw$nTXg2ziLkBr0?XphNg zjHTYEiu%w;xSIY+82w>$gaY(FWW8WC7#V2`I@}_Vrr$z%#8X^1z>RGUs}~$SD|alt<~cgUQXV9*Tv(ph0Py*MUZ%@ zx$8R!G}I(R|NZ_qTNgY#fS90kHS0GBs= zhant>j2D-i2g!6D^&0t0L>_Mme2MwABLf2lnFIpX`7abke!flNGdnTU*;7IeE*>M{ zeczW`0iDCU^1|lYN3HkzjvsU&kn<$EqK8^LTSLTCI0)Q2d>i_&;5pR!3Pl$TRD_#w zh(zkC9KVjElAlFx$7=Y&x#8?Ddlu#E9IcFINo4&S0!>xlbxfKt&5*@%Bh>3}TKrcS*wRz?N4|+(3csIcDAHl=M`uvbr|fvtaT{9Y!B0a#2#2kB9g&{J5gEMo~U?CFLvb zfKRiqplSoP`DmI7tI4`XGEEJGo}35yyU!!f;nE*u&;o#rU`+;7%MT6}$+S6c$oaz8 z9~%f-KB=|keEaq;g-FvBEA`gX!&r5W>YvKM+Tn?Qt%-_SeusvFcQkr2{N**_nQiBH zBXG|-*>pjRHX5wbr!3kc7Why&u$l=7Lxhw^hrVCwZMhDdA*xW@K-Mxrl2+AHgYLSY z332J)2?B5dt|UwZqyffID|p-irLOfY)D_9vU^>9;U#K&l0usQ{hao-J{QfRwuh>z{ z8bNa44pmCNxQk zd{K||)Q-7G07%o9)(9#my_+CBtbYl~_kX?p;F>OGd_KLVfKxuA(V&gw7rC%aQiI4N zy0xIM{V*z@y|{67==ma=q@P2SYo{{AbA)C8^i7|1v>MdRS4k+PAW?H^0|!isG4DNN zDqZ{Cb$VBJq5@1J1lQyN#cSdG4YYJynapSFWKXKla-+vp7t0D!6<4Do!h8-eow5y6 z4{qZ9*67Ty{`qLQ$Su2G@gGv{^P7M*2<<6P_i3K+! zr|i`J2Fra~lI&SIOJN@#CM6YT*IW!a50z8acQ4nFN&^<-Ee96=O~CY!V!6ODsa_OB zp3CUg>$9D=m>X=?;>$^lsS4qWE>laMf;9R5?%DUWRsS0~1pf{+BNjU18vs82so0W9 zEc*EP=$+crN{x>64-7O0ZT+eKFSHDrSuy+dz3Nh^rj>w`TFJHN)xR?(zYS!}{*9t8 zHBOYdj!>P0pf-DC$udc)L95>QwjHIdWGYx)0k95GWDln{n&a!#nS6^8cI)4vii)Fp zda6?h{D+S!3!G~?vh@5g%Psq$1`Fhq=-Q1oz<0n@7#s&^)UM*LAnc%t!8$*IW-0o~ zQb+)DOY&j(8RL{-1mHVuqMQ@2sXHANPdEQr*~-NX+ruk2N+u>gcE~LH z*Ng2>HUeCo2R4tKT>h`D$xI`Pj;J19eCmycI$vPGo2Y62Sg}+7Bw$4Ih?7wNvs$Xa zhdluN7Ir(5yvV8O6md(ryc}aA2yCY+xf6d6Y$^bPT#!$oN&5syPLLr8dd?o;(}h6` z`C!z8j<|45v5$v~xA=V#3lU+DY+wN3Ltw33-BQbKCrd!_&Kan{Wg=a2UW#)#Bfg12 zJ;E~jSiUp%(ERH%lntZ}%ht1-TMUSq;tN+M4e|!eedMNqf ze{$@LyXyg6#IFQLmIk)wpHP~+Aoi8cz{h4aK{yEcLVWvGr%9pXXdd6Ph*ovO)VXAy zCn5xjtv+EmqaE$dU55(zCW)mibKFgO?VX$eLdy(w2UX5R9afmd8E>@a3cGoLlKaz( z0SGnW6j_r#z(L5{{o4H zoy1s-?nKBzH^Ca2PC&j`>;9Iw_D6Bfj|ME@w2f}E>n(Wz)IsZ$vi1F;joFIK_;jOw z-|yO~%Bji`R;aCng`M%Lu*&g9?Ah576Byi9-U`XIst*tJ8f)t=Do%6`HyHJNd45yb zjZK znGFPyP{DB|=x6V~GbpSs?Y{6+2=gEDA154tNB$jnQ2;jrb4%%T380Fsav?)y2L7A8 zW!2LS6$Y$=hwx1Gmd66AM4!^PmDeXgvWeC_POD2vseMEju-yk76mJEeb6HC=igaSgjj1SgX~s@;nndK-JSQS`^c(- zvDLNj{+o!fOGLK;&|IYWGpc1vEV^Gu+L08{ih?Ps4%D3IfswYZA_O67fAv%YM`JDR ztnAxDwI#^OvHan^d6<#G1mvWOn8`>`k;5d8^9H_6T+D^f41tjbkZBX-54Uxb{MP&N z^VGJCwZRIZWAVEPDe+ro*I-nO`;dRw-;hShb}xlA3seMH1WqAs%}AEXnAGh$jxrT5 zUlEVAw9^~hG)^zg8HeY!(j|s$g{5G&{&*mw6q4zw3~RgKcw#VIrPk3CPx>6QS{#Y! zzI_s+|IRp!FT(51=r$r&JK}3q6Wx!hiL}F}2diWgvAvgw{emgGI8h2%xnUT9*`1%T zP~JI($&~G(RE@bXP9XYEb-9%g_Y+=xaDD4B@P@4+$6er<14R~Pa>!&X8D3^t$NAHyvo;5WpfrB=VZNFt_%1#=apV)q1p z7OebMl*sjfGm2o%x$oEI{#l1oVf%(e@EPa=Q0bNBZj^wvJR@AGssmyMDU9#HMsM@ zWTC4p3qJN>z}l{^{d-WW{9r^x*QmIuDXU1-#HZO!fkX%%GoXb+!^A8@NPLI^FkvA7 zsC$^;vdpv5t6X6=eEvoBK6P9-tLuY8FS$u@^_vUQ1|SP=$OpDc$yik)yH|4Qvt}4| zY9NeeOGe?CObjCU%4ZCX;KR+EEoh$ZvtIR%u+OVjhwGKu5U{D~0fhZgtx>nrYTk9X(hwTp=WYNfhku5Jbw5( zX7?SS1$DTGMJioUvioP@(!JV z;8se&2AE&*4wr04P)nkJ|(2U4(E0Jx?VXxkt%^eUSj^`xKpT@}wzhyCJpw2xH4D2SNH=j$sW2W%r#vh(4yh1RC zC&a6vf9W+8$S4Q~Ss`KDwUS_QxTQCmOZ5_D5&+A`l1l=g`5ZY~YtfjO2KCPZ%6e*c zp=#8xL{VP-JEnGyXd6Nf?HL)BbIB{Ep9=whO_MW5F_i#!Jba^DV_;f7t{Z^D5d6{@ zc%P$N2Wtb25xhsuyA%MUoKJTYyuXTTHT{X<1c!6U$m2ga0v%azfxsyex&X&Q0zMi= zMYQhLO2Th5s_UAzD&Q#WFXs0COf48YmwS|R^j&5bPEeytIT%*d23RZ<2Lzutp!0i1 z)>kL(gZ!!e(Ba54T?)~VJ@bo++ZWLA`vI&p!R7?EQ1}d&NW_f6A_d~Wl_SU#T!<5l zqA7$VdD4AgvrNp;O+i!Skobn;p&u}07Fs8mq}DL5*BH03TIzWEOn2A=SWEXT%x9@E z&2HYql9;2$k@EOtig|r~vjHTChqYXy)|EH4oqXJMJ`Bjo6ns=qA3l5lI8Aycv~fsC z5rFNB>Bj#5gM{?Lpmm96(?82#b8=Pc4XgDSLE9Rw3BOq1gZe}D=t?NwnM}OM{$gaE z$Ff2y`){uNsVN!9*nBm))*(a!)(Is*PWhdm{}LDo%rnSf0C;eAb{5)NDnY?7ONuVB zYOHjld+j>lwg!?0v1lTDhm&-HMO423=D=TuV_m%`5It?PY^pNCV7{Oe8XW~qw#w~7 zFdRfBc@BOa?>l;2@JYsj1)5M5I7$NfcDLE>8*esR*_E%c0BIs>@2|^!SldK>nOLkW z4y#iWE8^54$W*o&`KMP7XF7`!(gsi&R7dl&2&j3sS8*tW*u>+Yk(sRkfOTW3a=yH| zE%A^(ic4rlXz%PrIfpC5<}{Mv2o4PxClveQ6YT@uEFCBOd*W+=qY zv~ijZYZ-u9Wv*m0<<V=e4@N1T{`m`_ z{s)bq1b+Z+-=NCRrFd8XJW)?9m6gE>CzJoYymFD$Y>E=IB!oH*yeW-{W#(Dv4GT>h zu$qyd)va$=Y8ENJn4g8MzIz zCRue}en#M$ND?af;jC4ohmJfzo8t(06SgsJh25JCPQG%?2BXzh4k@w&!n&t-yfxe| zSStgka-n`aoTUhYw!vY!9rG+5W66QA`lCM*%&Q!{I#3lAHjeqiZhrq*SZ`TrC~T@k zVq`pU=G^TaO{F@&EKm1VoS0B+lAPw2^>$21?y>h3<5VfD|6&I;^C8Q% zSHAoIXDv~fPd`{oSX*0F=ESrtXymml4gV*laj!@btAUw^7tPDyT;%oE8aiCZc7X5| z@`j$~*ifi3AGD67hQ;^*UKVOGT_||}Bna;7JhoNW)D#C!67qOMcW|@n#y&rXc8693Fgc~_@S4?syDLkYI zev^w)Bg(T$^2*}%%JDUAqM0|eO#h~`e~;jQZJlLURb9BX>6Y%4j!mN=(xr5FZn_ar z38lNFySt=2Wp6qKBt@h{LK>wx)A#%JUFYzZi^Yn$=9+UnW8BY>@-8N+F$t)*f%1QW z3a=}iT8j+}2#CyNogk&&cs-NDrP6WtefEiIJ|;!o88QWgGzO^nu808FEGGQo1(YsG z@Fh#wMD0*}e{-Q`#YvD>b91_MKs4nmu}J1dfp1CHRr)mqC@$QYjO*Xg}BCg?%bFGNC4F zwFa9?J446j4=5I>j3Rd8i>D)%886--@W~@$C+IS5^srgSYJP3|^x}{31XQ}%28Tnb zlRv4azd(rs_)7HL5$J8e#Lev~;>2LGLPT%%{mX?lxCxOY?-QxeiaBjSi&cnz9Wq?H z`#F@UkH5FQUkB+64qd5Tw;U0BK8U2b4t~*>zy^M^HSFAi?XOMG2qyozk-zOeHTtGI z9;&_GO$A;`!m>i066I5>NwKS3)mpG09OT4W_h2FF=7d@MU1<1IK!QJ!)S!pd`__PL zOBf+0#>FlcX#@cV>0)2qhZCj@X`qt0ac4YD(8&_bEZOoRihPwA=lp8wnc+K`R9pyeAs1(WZ#E-_+jVYE$0kqDH{IjFqoQpJBR&H;&1R7yvj zhtg>gPg-UVHk+6c{T5GYO_%Q_7VjHIg7+Hnl&ZfjVw+Vr(MrlmLT&(i&4MF0z>SD| z6*8uu@HE7yxreSd1ZCKThi8dCC&(9uN=BMX50|E=zr&LA=IzYa8XiIVf`X2YM^ptH zw;whtRtWsFBWkEzzGnb@hRTh&XI9z2F9)XZIlH2`x~9(XU$O#i2T^y%8ZGUnjZ!@~ zcDHjtCBc5KxVng_ng>!_4*cvscj{42I+tGdlW>QdxuzF8x4kf$rftJ(JY*G6S91<=W3jXUmH3ys}4%&!4bs=-g#&UpHKNRz`lrv1J+2 ziB4a#1IrE6RLeTJemXx`K;Gc#GDzS-N_jW>6Xmq(rd}*8qcCy{y~bo@d&Y^g6PMK~ zQvYf&N=jGFj0}yFOhlwpY-Oc-0rC@_K-S602}V#Va}XyX-2=e6-1)Ab#}nA!fsH&p z-*i;HX6_XqPf@Zot@s znJQ`850NEA1R8$NXdMV>k6Z2p6Lsw3|?swf=NY&eo@kzl-(r@QVDC*oxIn|PnNrSfEDa6K*1o!OJOx5ukM>sKR zOKzo02hu=8V;G(G=LRLP^w_HA-ZF`1H-7;8Ua7`sp#I{k%2oRgQk4kO*}XrTX2ngQ zB)2^Cla#h1Q5V?npO%icEz%3m%Z*%f~IgQxTC~!)wZ5!5ZJ>*OBf@iE*N#Mx=qz3G0?XEF(bDk${tRqvR~V+AYVgZj^*qRcZ2=m;n#Jc z1Zbb_9kz^S;e8*eN~KcM!*ff?e2JQ2AjrbtmuINmG)QFBNppSgCpPX9<}9tS>**Ub zg^PK=X}opjOGhetwKn&U(%j(UwdpkAJ`Mw02J-fGt!HE?>IBC8v5P6PLd!4zW^xBX zRQc^VdjwfQKhw+Ij`3BYH>n`PC<$Z^tRLYy*LCXo>d;h~EGRk)-sKU2(H8_3ya4xC zB^K4RymFPVB2pz(KP;UkzUC`2zs0BxjL=JUthxzYLCpGg$J(b4mfC%=-!u|uA6R=& z0KMVOshB3q*hKyBj}t6d7q^ZR@n(>eV~^n&X6W@DowMigq^}+Yoy&b&&n)6C0XX!R>;?4QYSTahspL}2q z;*9Ao>LWltF{i{!{an&m!Z|XOYWtm!IcNMMxdm!;?qS<{GzkJISO#b8W^2xYpgZ`e z$gc>M>F8kh4Ru(GbznU|<$vv_M!H=Juz<5IfJ|fJtl(YLY|pjh;k}`F zj;-)MZLXrrh4uyEIgcV?uIFb75tkPhsFDQ{IO0(if&AIwOXTZ+59EQt6)MPO{Y3*M ze0Pl4k97!=VXOoHG;fYya;3uGD5Xu=6Qsv{Tn5mA3k^h;SbHO}RHpjEWtFR&zXzBi z%NMX3CF?hq=i%>V&I+mKtPetMXG#H!wL8$uT(5o}e1LL^^d18=U+mdCs!F(kRg~!& zpSqnDrqOwEJ@0on)X{&J{Lf(rf2~W3xUHzC2jWvW#50&5S3Ve@&0=|0@Qy$!>#gK5 z``!@^7k&;2+6nlH5{z-jA1sC9bFxp*OCgrqL(_k(+LnYGB;Q`w}!~BW7AsMpW4~Q&{DC4`C1uz6OZ|}Ii8Npe|i=z!G{ub!f4z zZ9SAEf>_A&FFdBbB`euaU!0{{QyxfCWi7nPA>(Uq-E&}TqIG6%bhXBZlsrhk%9Wbs60CeR^FnI z9gzmHYat0kj*b4L)3I#InmTTSzKLqod~&dxyFp6ZXjnx|N}_Q`PC7-Zw~(BS;@yj< zSWpT$x}lx+&t)XyNy6IgL$BQEjY6noOXIm<%*P189AKbIB9hDAU(CYo<-OWvFipo} zf$rCz)OGmc^8glwxc%N9P{RRnES0s(J1T4~_3P!V1qYT|Er;4&x=>O93-7rKWh^3# zEOSc$Qr&RnjF7y$b{FYvQ{|6_Wh#FW{qNzIu1sG*ntzB zcMG1bDi@2wPZrx?CDR#d0ht>#CPF(l6!@nFEee z6iWK46I9c$@6C7Y=Ga9i8s5*pQ(USutaAF^q2u+4=uIxhMMcy#^j0h%j0^ImbPLCa zr=Qs*xDRd+A8;BYn`#VOU?soFs6PLh$dXI6I_+im*gTGW@FFO%y8V`xm(9DA!v5!i z(e%})QBb@GrIoGk;`%#ru0qb&nW0@%c;Cf?Q`+d~n2T@=n~z6y9_D zT|U&L@6x5DxX6W=4RXZBAwOg@O_o0yXF~m|x?0~+W=PZpLvQhQRXHnr|BnNx0T) zb~qHokSLHA2tHBM!(F&T)C&1;02Wr3r(X*M5!9)N)M^yRP?-p7{M{z~C9?7|A3y+t zHKB6(W28tSK9IFxuGZzC*Z3$Bi9MuoRw%!-grd)k!3IafkK$a;h(c!`~0urV8_>n+_)3RCd4&a!XJ`u0l187K?vAn zj^pS&pr-vH@o%FrBp1|(4s-%hWiM~=K)o>U7oHKG0O4Lt%|uC9S1%dAZp_#iCkJ2>BI`oYY2sq2S@hw*Gi{O=aeQl(LN)EfU09ys zm@di!pdZjEKbc2$z_vr^FW`u2zlt7=Y*lQr8SVTQz+sF6Daq16nzw3Aqwyjac%jOf z2l!?XiDS?fbb3ohQ*Kl)jL6fY+T=W2lJ%uHaT-F!=+SV-jM1ODT1drD))-9g-PC}K zcuu%s2{f93l(VR)b$rai2>yw1Yz@Z{0aTtHzHJN3!ny2JFn0xvftDK<8PWy=wxf69 z8)Xk3VMR4q?%+fyZ7CgFju#QD%C##^a&zZtF!!D8Ep$opR@qY7@t=1^zAIS4Qu3Gf zL`bt(FRWp=%-ax(TOTOSN+8A%IqPGED6)z6l9uTPH)#nB4QIGeE_>r(5Ev?Og zoUEIh)eQ|2JD3NWe2XgBrNpneD8wtofW?xq%Ndvyf{A-azNt5!0!|mYK|nD*{Xy)T z2NvkjxOr4c+hTdOiwi!nQ=Il;SxFfZH`&+E#iOQs0OSU3w=mf*I@x1u*6!Z7Z8Yaj zIphsDA0Q6yZhv&k@B^j)HT|CsE3}E|FQ*f$3yxx0RN(xab(+S>Z$Z+)N(5*;9cE7R8tGTSFso}PaT z*4BW_j5C#bn?q8BfW^(%VCH+No=A+I9&VfVPyH=qVFd$utjA77r9=1S!&e59IroBz3S z+s^BSABvrRBbyMHo!9Fqs#kKFzSur8TSlB0J%VDltwlp$8W~w-`uoLCikDIJVMCMy zcJ_68vHp5LMH>wriT&N#^{Uuey}OZ#kNCgmiF}qV7p97OG``SUu)0RpP^6=r}maFaQ3tGI)B`3Vhu;t zRUI6%-rZVLx)fQnb?`^ef@zv+xG{ za+$z*_)l>XrVeln9|p5;O%22?+BW0Rf{b)4e(Z7Pt#Qqm&%uzQoxVKi!tzB=$kS87 z-Qm(q280PuTlpIs2sgs_XGbyI(RFcLXAJKX>5xJ9i2UbAZ4)p`LU-FjCNk4J)D6bL+(5gm8jf-I$1DjcyHs)7UqSCLP>w$NgRr~i3@ z%9VzxU+v*|xQu;a?vt$kw?=k%m)T18S)X();-dty$A&furmdimCzUFzExH>yH^>%s6AUb zP*J@i(qQ4i!t6ue!F!-qrYDz`;lb^I*37>@LhjHG`wi~K#^{Fbvaw4+%G6Wv9Cd;c zJ{zM(KtZ?MbKVFr5F>ez5E#P8^2770sUc;RN_gQRQ@goH$oRD=>s)V`j<`qUEr8KUBeyu!NyKBezKhD~&*=zkss;2O<3 zgs*V{@`M>MU~Fe2A8mNsm})La4tuNzpH1{qfb(;0$E(JgBn3xn6^D2$0Ph$?DG zxJ=MqEaGO%Zj?PLJqoK`P>+tDnMBA3wDr24k1qQo+qIDvSST&4WS*NEED=m@*2oTR zu6oiE!rOnzok|}kT{^k+yk|JD^woB%5*@eoPgf2;wI(TwuR-7@Y+TRXfTRy3LzmZp zBFq0R$=o!@zABKiu%GpEqbQ=FH(Bg!bnKV&2DZ`wsm@&$I`^-P0z)YZmFe=W9xci0 zqY_z(O7@&hk_l9PrHC2|^F&&Zg$t{Cn<{x`x*B5_#1~I}o_YgMEHe|!2DS0;<2QCr z<2-EgimKw21cAF!8DPGe6<}i)_LDch9};w)ts*b?58+NnEgfE)5;*xyMWjc18h*!O zUU&VVw=V>G0(|&WJx{5T{K6Kk0f2MAJ{P(^JcqYNkI<1(S(B^e+ko~^lB21qDfaVf zYc4OCwaGX{SZ(FEdK0pAiM9Ck#`|iA4-!>q^hno-bXuLQwcN3bQth~i7|- z!%{(mJOGhMAyG!w^66A8(bUces`*Mf6;jDO^?|{o^V^&bXse@K5YSCTO-)VH$5g4U ztFvB(uT_^55!K*R6`$4OGaE6Y~MafE#`m$Oo zw!R0E&#AoG5#m>h8s@q9nen2C>TS=iPbn>rCFfmM>yiJ`bs?mE4L$!fr^DPXJQ{LE zZu|;FLr#o9+Sp4hg0TzZ2+D_AD5EXNmEHjMzF|luIJgtjtDf72{*!YnxLW-ws5isV zC)VM@cGYK(0CMr~tI|4-gh>kgQ_yW$?c(_6vFfzU^YaGWaFkA z`+a01aCiRt&~WmLpBtWoy%Y6*25mgY8)n_Ej9Bp?+}r`Q0nmkTU}|H{4`%$P=?Fvw`HgUr}kxbAY~L&i`vJ#oBFH{MN_yF9JKS zsE4=H`?r0Tpz6Mdoyr1)N`z4$3q2XW2=|p;r&m%nR9LJxsLE=&+C()I5oZohdjBL{ z0%S^(6}J!0ccjRcCH8AT7n$nMwc%doT#V;6WL$w#{h!2U7_&Cxd;@lXIv%Q_1bsH$ z%V0lZ(X3zai+@M>i%B6GZcAq;RldEWV-bxCc+LQD%9E1SFE(mHMa9xT6u_=cs{TUj z-xPnSNnzKt$&ajyK#T*@L3uwtvXYXtyyZ}H>5^rFq)@*1J`Z)3SJsQq{KOmdZ)UWG zk_K|B^O1l1V*~Vs>`L|`&cws)Y#^}z6cZz+wP6po4G8F?r=Q|^9UDSQwoRTWowEp(4MW-Zp|Vk%>uL69O3&^&83KbN$zHXaoaU70m)3uG&y6 z1#({0DiF1b8g&_Su!dHFzE2fbv3;>2d<4`&Lgeg=-**o>7s4Z#pldxb#h5}>@^Gg< z#Xnha73`eEx{K%ERUr4jegnIkvony5K`JnvEe0%Yofv!%6R z&=pG(Cuw1IJ#)?Zhx!`3r8Pno3d)U^BVPo8gRqCrPjU>79plCem!PIuSu7iX`!+Nz zA>kcxK5|0lzpxgn#vj63L=>Tn1EoNzRFUJhpMY=bF$HXEj`zO7ej%a_u#*CvuZtj( z8O8j>@FkO_9G!0YBAwuQzcpaDsdL(=m6gGU6PRs(C1tgAx0|Ac`~4_uu6}|ah~nkD z(giUwNhhVHrL7B)?&~G5u<-D&o}XUH9!d<2(p&n}vkRg_#-(-44`BH=sy`i>0=g7w zS1tuK`63U(^I_oOkdm9IM#NnM0Dob+^!Fy2Zr@DrZ#t6r5()$huS9 zyH~He<2E;zgnTxTB=v|Ccq7?J{^p56?HguciX>{GC+A)-L1^3nT0)X;6J|YQgVH`W zORioinfos&PQdoP%UfU==?A*U6rj~28K6xYd{%1wr~2O*G~qhrL9Gs6Ky)GS^j)K$ z%j26}iyh8X>6#{5$nhiJoBE_HjSs>Z{;f&?)zMAM&D}l7JrD|(c}a?g5Jqc*-}jDrS`lB~f~B>g>?ffyM2DFn%g_Z3dre}LPx5C>ahGoXiVn-7>rV?;)Qg|X@4-KFn9As$uTl$jEu zKAi1SRtAlr(qNQuo%=He-}TOiMzdAV3w<;{<4*eVqxCt+0~Lmt&u(oV%`Gfw^={=l z#kG>&b2}sj=(E)HO|dq3T0T zzNgI}YUA)KW`Vaj#zn{I6)B+}t?LQ@_Otq+gbkrDAQT%zl4Q9Z$m$X6&1n(SSAYA^ z)c8j`c&0ahItj`DHvaGNpv2>pmPCx-)>L}rYPK?mnrbyHsg_Ilp?@{#;mWp082irX zYhKp<;L015B4k|lN6*6Jp9; zui9P^Wxc{prE|b1`gsVu$viTB(dm|Y-<6_0GICVR4`Tln0t5t zV+rTH)9HzkGDhj4n<G)vYCrn zm*cT^-73u@PA6Zh(c=WEsY8Bv0F!FlR|GO+6ZGI9;30N;cy5EP*AB-1+9kgh&e!il zi<`eyJc$8466h18w0gAhoS;s00Yxq1wv__fKD`xCN3KuKi#!BKh)fkeu5Bkvbdle} zlp_7JbFN`yBmUVG>G7sVS2h=PyH?+O4532j`cmf1w?==}@LD`aKdzM&mmDOXm|c-{ zp>00&gdstuUcj)J_*{(N!GY(S?`;GhyywUVt(0~L2Nyixr#vo5a= z+_+b*AMIUsWX_cyekHg4kO=jI2p3%i>J`#un%%+1Sh18;ORQ4W(uv|0pm*y7uEq*L z?kEVi7sEI*0be4o!EDf3k;8o_>w6amSn-^g$P@q@f|dfxQkXi99n6Z>+=Gu_{+$T}X`<+V;N)_(RQ3GeHqpO?mU@Oh)D_v4k!n=R3amuS(2FsKDk2d}A!MS)d6hFF8++ zzWq~sNf#Fr@*?ya`ODhpH*Xvp$K@o^nAS9*@_4%CenAg9U;9uY?4uhJ-L#LtI_HvB z*nQusaNgeS*ZR_Z5V)WAbY$UT_t{<@V*QeGAMtjZ@v$ns>!jn@171m#w89O}_A8l4 zjowXD-bbX`A($#iEFKwZ^R=FzZ6D|)Ojtv;(6^^h8)Y*jz>P^DRri+rm#T1KU4(x^ zv8sn%j=oCu1<>;anN$eZ>MuN;CuCLBnpR?6OnvM39k0_;zqAYP4~9<~b;F%E3bjA@ zt%p_N3GRxYxzzXFHsf_?sOxKfTDY@+h#X2?P&Q6>7k#*?96(=<7(C#sYXp4+m#fe| zA2%RY3&Wmfu!qHZoaIOrLt%$McMrxQh%Z$JDjUMFMcH|a!#QvCgz?C#+K$vdT$p*Q zRf*nT^+3pKY-bfgO%Ieq(vvq79+k^>$^27j361{z`bK>hF|<~G*OaYzcWjl`QItiZozZkGF1->RLT;;=x^Eb5$E@G(x}NPD<&OW@_!!G5no8;1O;Aa}k4= zo_eS~`jo5Z!7U9*^s&D(^RJMA?)a*N63j98?qYn*Otc+Nu0knF?EU(*?eSq z`Rf%WICy3!|CFI(N`;g+7KPrBXpcm%AnV<3;dR1=Xab=($;` zthlm;{C6R}Tk-3?in5-Li~jwy?)40>ZM|E~(yD5|o3UuEo)>?2pV*#WEk?b&dud_{ zN?2C}V07cI1WLzDi35N9&Pb2|bUiNn+Gc!-?i&j*M8&s)tuL_sjdX8pZH0WcD<4X! z#Z_1Ns@rp$D*d%9@&3?CJ4aY7r2gCN>QsYUhX?OhP3^DItbli^Ix?kn=7|hBO*sRtc zQYG$+GL39L>WCjKMLuTUt&W35Y}*oOiNc50VsK=LPgCenkz8YlfiuEQVp)fjslI@F zg!Qz+dm_6UPk{7~o{6uoo5BcyDfoaDV-7wY=%!=wQq9Mu-?1(XjR{+bw_kNI>}H8R zCgtWv>!zO2g9&H#wZZT9-Gu>(OYgneS_i(|hmNC3Gi`A|T*1brJPM!5yLP`A|m2*T)i)Pf6$sF^|H88r5OyTK6-fDQ}X~&TgcO>a24gWSo*QV8;}m%V^Dl; z9IeqGc7>R3tGZ=<7MbI!gZq`E;K_}Sy3^`HFTD=Wp5~G2Rg#NNoakz|5wn5@V6_>w zOvs7~e!s7hmouikDuNuTm#o0VhtDp!|3ri%Wt8v+Vy3e&qoNSY%J>V=6BY zx#cR4v4Vb#MzoNf7K`{q&2ut~J4=GF1jcvyjt%t)Y;8eGmP@A<#yo~F1kaarFhI?O zDLObhT3!?4lSk|!WLS>py)fAlgF-PLqwUixlvg`F)jU$&F)W!SB+|l3<_}R&W70U< z0)RzD-gW~;Qj_2Q&5nCTgi1N{`RlMCCVg{Cj%Bx7Fv{%ND?sBh7riOY)QtLG;sb7u zUY#hTHAH_2eTBAafM%RF4yXp6JNKTvT0U{I_fWeAKe)62fff7kBu%gW$i#?6Jp-Qi zu;mmw=TlJ&3qv;DUvKinKBm@bnwXfFnwn~B7i)7^T3F!Hylnb#xgQ@7y^c-}7v&6L zu|hO|bmbKg<5S?%;WOuR=JVzY#e}5KavGo!K`t*ZId%E@`3sAR5PRSB+Yl7WGNV%^@}-ao&8 zgcq58x$DjkMil;@L4HQY2@XJNV24YKgX4AF{g;=I51fHQHFU}1j8K<1hYJHz^#*jb z#YQudmFW!69Zy}GB-cJ zF|4Sh^!DxBq@<*x!a`{onfv?uQ^XdRpEuS2*I!24M~_6ai(b9LPe6U`x-*_JI5^nR z(NS5+p>G!&8XB*2dU`q(M~8=riHU=Aae4U@^5%@?4QZV@zlEZOg~bPpM2kv`UJG#I z7Fd=HmM@8P(S%`Dnib$|&=a5vN=kiFc`w{HeoYr$;lROPp4lK$u`rf0v9SERX%HP= zZ*aT6k|wkUZ3c+VPfbY%g;KWy{`216o`QlxeSQ7(^fc?1%F5ORlA!V{Wd&trWi@3@ z_xVI^IPst?ElSl|b#sTFz6iz+g%W81FSuu~^AZyiIsgB&Y*2W9ikY zYf`6i;_IQJqQ1BRbO288Bq8s{fG0@=T7*PIGf88_iXj3$+U=MSLsaZE&bhfc-bL4C zYiny}X6Awabq#6kh9zL%8}xUMSwDhv%c{WZ9P|iuaBv9dQ~dA1=}-ROVHrjN7lm)Y zNi#H##Ka2m543phbF1N{hvEvjK2Dn=0|fzMEgh}0bgfFzoP`!HZACAEeKpi8_uucG z`!4#KnEkQFh|iT$vg!)MIk(`<-`x$npO0pN2Rq>O45Cn6CvIq>%!jUf5(m-uvYJlj z>L>f}6v{vCl$Oe`^ACt>ZnIQj&|{PF{dXPoSvxp$lf+t<+mFc9bGOE^xg~xG;737D KRklVN8uUNef7lTK literal 0 HcmV?d00001 diff --git a/src/location/doc/images/mapsdemo-verybasic.png b/src/location/doc/images/mapsdemo-verybasic.png new file mode 100644 index 0000000000000000000000000000000000000000..9d92a74c955769a8e39e93668be046e47b0e2c48 GIT binary patch literal 63947 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 T@#BjT1n?&<1(K{1*9ZR(sUrwt literal 0 HcmV?d00001 diff --git a/src/location/doc/qtlocation.qdocconf b/src/location/doc/qtlocation.qdocconf new file mode 100644 index 0000000..695860a --- /dev/null +++ b/src/location/doc/qtlocation.qdocconf @@ -0,0 +1,54 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) + +project = QtLocation +description = Qt Location Reference Documentation +version = $QT_VERSION + + + +qhp.projects = QtLocation + +qhp.QtLocation.file = qtlocation.qhp +qhp.QtLocation.namespace = org.qt-project.qtlocation.$QT_VERSION_TAG +qhp.QtLocation.virtualFolder = qtlocation +qhp.QtLocation.indexTitle = Qt Location +qhp.QtLocation.indexRoot = + +qhp.QtLocation.filterAttributes = qtlocation $QT_VERSION qtrefdoc +qhp.QtLocation.customFilters.Qt.name = QtLocation $QT_VERSION +qhp.QtLocation.customFilters.Qt.filterAttributes = qtLocation $QT_VERSION +qhp.QtLocation.subprojects = classes qml examples +qhp.QtLocation.subprojects.classes.title = C++ Classes +qhp.QtLocation.subprojects.classes.indexTitle = Qt Location C++ Classes +qhp.QtLocation.subprojects.classes.selectors = class fake:headerfile +qhp.QtLocation.subprojects.classes.sortPages = true +qhp.QtLocation.subprojects.qml.title = QML Types +qhp.QtLocation.subprojects.qml.indexTitle = Qt Location QML Types +qhp.QtLocation.subprojects.qml.selectors = qmlclass +qhp.QtLocation.subprojects.qml.sortPages = true +qhp.QtLocation.subprojects.examples.title = Qt Location Examples +qhp.QtLocation.subprojects.examples.indexTitle = Qt Location Examples +qhp.QtLocation.subprojects.examples.selectors = fake:example + +tagfile = ../../../doc/qtlocation/qtlocation.tags + +depends += qtcore qtdoc qtquick qtqml qtnetwork qtpositioning qtquickcontrols + +headerdirs += .. \ + ../../imports/location + +sourcedirs += .. \ + ../../imports/location \ + ../../plugins/geoservices/nokia + +examplesinstallpath = location + +exampledirs += ../../../examples/location \ + snippets/ + + +imagedirs += images + +navigation.landingpage = "Qt Location" +navigation.cppclassespage = "Qt Location C++ Classes" +navigation.qmltypespage = "Qt Location QML Types" diff --git a/src/location/doc/snippets/cpp/cpp.pro b/src/location/doc/snippets/cpp/cpp.pro new file mode 100644 index 0000000..b961b98 --- /dev/null +++ b/src/location/doc/snippets/cpp/cpp.pro @@ -0,0 +1,8 @@ +TEMPLATE = app +TARGET = cppsnippet +QT = core location + +SOURCES += \ + main.cpp \ + cppqml.cpp + diff --git a/src/location/doc/snippets/cpp/cppqml.cpp b/src/location/doc/snippets/cpp/cppqml.cpp new file mode 100644 index 0000000..48a5007 --- /dev/null +++ b/src/location/doc/snippets/cpp/cppqml.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void cppQmlInterface(QObject *qmlObject) +{ + //! [Category get] + QPlaceCategory category = qmlObject->property("category").value(); + //! [Category get] + + //! [Category set] + qmlObject->setProperty("category", QVariant::fromValue(category)); + //! [Category set] + + //! [ContactDetail get] + QPlaceContactDetail contactDetail = qmlObject->property("contactDetail").value(); + //! [ContactDetail get] + + //! [ContactDetail set] + qmlObject->setProperty("contactDetail", QVariant::fromValue(contactDetail)); + //! [ContactDetail set] + + //! [Place get] + QPlace place = qmlObject->property("place").value(); + //! [Place get] + + //! [Place set] + qmlObject->setProperty("place", QVariant::fromValue(place)); + //! [Place set] + + //! [PlaceAttribute get] + QPlaceAttribute placeAttribute = qmlObject->property("attribute").value(); + //! [PlaceAttribute get] + + //! [PlaceAttribute set] + qmlObject->setProperty("attribute", QVariant::fromValue(placeAttribute)); + //! [PlaceAttribute set] + + //! [Icon get] + QPlaceIcon placeIcon = qmlObject->property("icon").value(); + //! [Icon get] + + //! [Icon set] + qmlObject->setProperty("icon", QVariant::fromValue(placeIcon)); + //! [Icon set] + + //! [User get] + QPlaceUser placeUser = qmlObject->property("user").value(); + //! [User get] + + //! [User set] + qmlObject->setProperty("user", QVariant::fromValue(placeUser)); + //! [User set] + + //! [Ratings get] + QPlaceRatings placeRatings = qmlObject->property("ratings").value(); + //! [Ratings get] + + //! [Ratings set] + qmlObject->setProperty("ratings", QVariant::fromValue(placeRatings)); + //! [Ratings set] + + //! [Supplier get] + QPlaceSupplier placeSupplier = qmlObject->property("supplier").value(); + //! [Supplier get] + + //! [Supplier set] + qmlObject->setProperty("supplier", QVariant::fromValue(placeSupplier)); + //! [Supplier set] +} + diff --git a/src/location/doc/snippets/cpp/main.cpp b/src/location/doc/snippets/cpp/main.cpp new file mode 100644 index 0000000..1cc1184 --- /dev/null +++ b/src/location/doc/snippets/cpp/main.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +int main(int /*argc*/, char ** /*argv*/) +{ + return 0; +} + diff --git a/src/location/doc/snippets/declarative/content/Cell.qml b/src/location/doc/snippets/declarative/content/Cell.qml new file mode 100644 index 0000000..0391e0b --- /dev/null +++ b/src/location/doc/snippets/declarative/content/Cell.qml @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import Qt 4.7 + +Item { + id: container + property string action: "no-op" + signal clicked(string action) + + width: 100; height:30 + + Rectangle { + id: rectangle + border.color: "white" + border.width: 2 + anchors.fill: parent // Fill the whole container + } + Text { + id: text + text: container.action + color: "black" + anchors.left: parent.left + anchors.leftMargin: 5 + anchors.verticalCenter: parent.verticalCenter + } + MouseArea { + anchors.fill: parent // Whole container is clickable + onClicked: container.clicked(container.action) + } +} diff --git a/src/location/doc/snippets/declarative/declarative-location.qml b/src/location/doc/snippets/declarative/declarative-location.qml new file mode 100644 index 0000000..8eb18e2 --- /dev/null +++ b/src/location/doc/snippets/declarative/declarative-location.qml @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//![0] +import QtQuick 2.0 +import QtPositioning 5.2 + +Rectangle { + id: page + width: 350 + height: 350 + PositionSource { + id: positionSource + updateInterval: 1000 + active: true + // nmeaSource: "nmealog.txt" + } + Column { + Text {text: "<==== PositionSource ====>"} + Text {text: "preferredPositioningMethods: " + printableMethod(positionSource.preferredPositioningMethods)} + Text {text: "supportedPositioningMethods: " + printableMethod(positionSource.supportedPositioningMethods)} + Text {text: "nmeaSource: " + positionSource.nmeaSource} + Text {text: "updateInterval: " + positionSource.updateInterval} + Text {text: "active: " + positionSource.active} + Text {text: "<==== Position ====>"} + Text {text: "latitude: " + positionSource.position.coordinate.latitude} + Text {text: "longitude: " + positionSource.position.coordinate.longitude} + Text {text: "altitude: " + positionSource.position.coordinate.altitude} + Text {text: "speed: " + positionSource.position.speed} + Text {text: "timestamp: " + positionSource.position.timestamp} + Text {text: "altitudeValid: " + positionSource.position.altitudeValid} + Text {text: "longitudeValid: " + positionSource.position.longitudeValid} + Text {text: "latitudeValid: " + positionSource.position.latitudeValid} + Text {text: "speedValid: " + positionSource.position.speedValid} + } + function printableMethod(method) { + if (method == PositionSource.SatellitePositioningMethods) + return "Satellite"; + else if (method == PositionSource.NoPositioningMethods) + return "Not available" + else if (method == PositionSource.NonSatellitePositioningMethods) + return "Non-satellite" + else if (method == PositionSource.AllPositioningMethods) + return "All/multiple" + return "source error"; + } + } +//![0] diff --git a/src/location/doc/snippets/declarative/declarative.pro b/src/location/doc/snippets/declarative/declarative.pro new file mode 100644 index 0000000..226010c --- /dev/null +++ b/src/location/doc/snippets/declarative/declarative.pro @@ -0,0 +1,18 @@ +TEMPLATE = aux + +content.files = \ + declarative-location.qml \ + maps.qml \ + places.qml \ + plugin.qml \ + routing.qml \ + places_loader.qml + +OTHER_FILES = \ + $${content.files} + +# Put content in INSTALLS so that content.files become part of the project tree in Qt Creator, +# but scoped with false as we don't actually want to install them. They are documentation +# snippets. +false:INSTALLS += content + diff --git a/src/location/doc/snippets/declarative/maps.qml b/src/location/doc/snippets/declarative/maps.qml new file mode 100644 index 0000000..3378ee0 --- /dev/null +++ b/src/location/doc/snippets/declarative/maps.qml @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [QtQuick import] +import QtQuick 2.0 +//! [QtQuick import] +//! [QtLocation import] +import QtPositioning 5.5 +import QtLocation 5.6 +//! [QtLocation import] + +Item { + id: page + + Plugin { + id: myPlugin + } + + //! [MapRoute] + Map { + RouteModel { + id: routeModel + } + + MapItemView { + model: routeModel + delegate: routeDelegate + } + + Component { + id: routeDelegate + + MapRoute { + route: routeData + line.color: "blue" + line.width: 5 + smooth: true + opacity: 0.8 + } + } + } + //! [MapRoute] + + //! [Map addMapItem MapCircle at current position] + PositionSource { + id: positionSource + } + + Map { + id: map + property MapCircle circle + + Component.onCompleted: { + circle = Qt.createQmlObject('import QtLocation 5.3; MapCircle {}', page) + circle.center = positionSource.position.coordinate + circle.radius = 5000.0 + circle.color = 'green' + circle.border.width = 3 + map.addMapItem(circle) + } + } + //! [Map addMapItem MapCircle at current position] +} diff --git a/src/location/doc/snippets/declarative/marker.png b/src/location/doc/snippets/declarative/marker.png new file mode 100644 index 0000000000000000000000000000000000000000..fd727d9c5cb0fedb49863726eb2d18df993a9f1e GIT binary patch literal 1855 zcmV-F2f+A=P)bP~(>eyE6Xlw0^)oGn}?BaCVe>$~jMXaDwWebE56w(M%x4NMa z1wp~EZ+5H;f}n^)kj1J9K`g1bkg$pl#nbOR6NgPDNYpp;&AIO;dGGx0IrrRq)1${T zuIlO%kzRk@;#uGE^YiHw9UUgRd^xLEkJt6e&p$iZ$H&`Rr8+WBqbV+X_^^Q``YT>~{YHxc(~G*>AqDk2Eq?f=?1e@-1CL?9~az|b8#wvB6OsCk%s{U7oUKS8s+ zB50s}Fj?3=)*y!vnk_j@8;+c$yizUI*H_nV*%CNu?{7a^ymFA#llw(%FJ8F4{3qoYvB zt?TOaWoZSa^p?w^C!^1D4L?n(xuxwCnR$hl?n))AUz9X){UORI)KG1$E>bS{9aCJK z{qm#Z?CdnGrlu-6FgBemmxZ)~V12VSNr=YLb5}Y9I+R{SGrgmP-)@c1B)$G_nOweM zkWN=@`BFi$veL4$E|=AE3blo3DRy*@PYi8}JKr8iT}nS}OQb%_gGpUdL1)gKq=bZs z;V%{BZk3*f+wFcPL|eTtIZDdY`R#xhX1aSgJ!rg7{{9M1G4qyZtQA(izOsHAO-Zh2 z=y8)EjOu3>LVmGX?IJVXGm}WTII? zP;P0qIP8T%STkj+Rv=syzVeTw>gs!l%I3*t1!-z}NJCcc=@t-HK>XusraFI>24bHWqEwZ6M9f&GfvIYl;>qVo4mHMiZP53-`m<9u)RvadB~$3WcI) zOL3pAtu6PzI9O9t6Im_qK9IH=z2-59lao__u~_^>GMTKuo10rdFE6jYt5&V*jn8`- zfw~03VdwCh3*_UCN2#%~jzK26yStBv^AlLEuCCl31C2y`$olo`2Y7gR^fpAxbQo+) zgb8Ub5Zi5WR8@7G;P66#d=C2sTWBHL3!skx@-Bm|Teof~M+xcH%Ve1R{9Fo%PB$A! zP--^i&F-z>HuRPPI;_nOc3?CNMB?e5Jlt*p7EM z#DEncRMXJN(TH+(b`FBmL1+sW+Jp8VumP~15CbiAadDZ#sb)yC&PN45?#=UL>Gt?; z2NBLFwK{^cDube1i0`AIF!-(3N@^qSi)SbRghHF4l>qq!AY-}8@x(W&iuw8Zh|Zo( zqvDD?G{))0VfcW>19R+HLK7TK?nrQ&VG=kpXe(jmu*;yis9>WFlit$gax9ZcMSR39 zEW1S`91e6DVysgrmENi%l`2XL=TbX>Fh(TakYXH-EzmmXYeeH6*kLBm5#v=+QD7mF zcvut`<_d=*{&T@p2p)h!jkYN%DehJW5G?^>p(AiO1UAr6!MJkG;`zagrAbw*?Nwf$ z*GDRqjJ$TOkk0*mL#R5Pg&4Y8Nd=mJNvRCZ@4$IZp?EkN!vN4hXeYE0S`95`2(0*H z0Ah`{s7ujkd0n`0LWCThq0to6sY};|gVXaMIC|MvZqWJcjOvaiGgl`#cL+8Z+JUwo zM*;06TyinN-}ChJ9NiMaq-d;O?a;GrA3sDUlk^456s@+LQgg06bB$rp^ny~#yOdJ{ zCsJCZ4wu$&b{_pZA%C=`kQ3TJL5rXd05J&wBY6MSC5We8L}Y=7^}+}Z#Dm|~YRf8P zG7CD~Av56d+-sy(=iD_o&uhbB=m4}E+RV=5IlsdG%<}*ae+oMrnv6wqB+=;&yLmyUu1^L4sA6n;9d)!#%6mT<0!?%b*1m74*=@O@sfx8mMcE|+`3 zu7JKbq|b-2(|F^E_8Sbq@8BqKndJfOveU`Q(bgUH^T_ZEIA@^=c=>|rs!G}taZ31C zk%2=0%pskwk|C6at%d^D1J*$*m2&PcLVGSOm(vL-r*9$xgE@ul?d|(=)LWiKUTE}v zeWeypiweJlYdOE%#a%p!cdeRRI&#|-P4b;7q{V^=%I?2OaeHIw1sypr;Y=8_vXIT8+#E%Yt)89w8@ tpUP(p%&%b*iNuCe$9QNmuF~dk{(rgB@id" + place.extendedAttributes[modelData].label + ": " + + place.extendedAttributes[modelData].text + } + } + //! [ExtendedAttributes] + + //! [ExtendedAttributes read] + function printExtendedAttributes(extendedAttributes) { + var keys = extendedAttributes.keys(); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + if (extendedAttributes[key].label !== "") + console.log(extendedAttributes[key].label + ": " + extendedAttributes[key].text); + } + } + //! [ExtendedAttributes read] + + function writeExtendedAttributes() { + //! [ExtendedAttributes write] + //assign a new attribute to a place + var smokingAttrib = Qt.createQmlObject('import QtLocation 5.3; PlaceAttribute {}', place); + smokingAttrib.label = "Smoking Allowed" + smokingAttrib.text = "No" + place.extendedAttributes.smoking = smokingAttrib; + + //modify an existing attribute + place.extendedAttributes.smoking.text = "Yes" + //! [ExtendedAttributes write] + } + + Icon { + id: icon + } + //! [Icon] + Image { + source: icon.url(Qt.size(64, 64)) + } + //! [Icon] + + Image { + //! [Icon default] + source: icon.url() + //! [Icon default] + } + + //! [SearchSuggestionModel] + PlaceSearchSuggestionModel { + id: suggestionModel + + plugin: myPlugin + + // Brisbane + searchArea: QtPositioning.circle(QtPositioning.coordinate(-27.46778, 153.02778)) + + onSearchTermChanged: update() + } + + ListView { + model: suggestionModel + delegate: Text { text: suggestion } + } + //! [SearchSuggestionModel] + + //! [EditorialModel] + EditorialModel { + id: editorialModel + batchSize: 3 + place: place + } + + ListView { + model: editorialModel + delegate: Item { + anchors.fill: parent + + Column { + width: parent.width + clip: true + + Text { + text: title + width: parent.width + wrapMode: Text.WordWrap + font.pixelSize: 24 + } + + Text { + text: text + width: parent.width + wrapMode: Text.WordWrap + font.pixelSize: 20 + } + + Row { + Image { + width: 16 + height: 16 + + source: supplier.icon.url(Qt.size(width, height), Icon.List) + } + + Text { + text: "Provided by " + supplier.name + font.pixelSize: 16 + } + } + + Text { + text: "Contributed by " + user.name + font.pixelSize: 16 + } + + Text { + text: attribution + font.pixelSize: 8 + } + } + } + } + //! [EditorialModel] + + //! [ImageModel] + ImageModel { + id: imageModel + batchSize: 3 + place: place + } + + ListView { + anchors.top: parent.top + width: parent.width + spacing: 10 + + model: imageModel + orientation: ListView.Horizontal + snapMode: ListView.SnapOneItem + + delegate: Item { + width: listView.width + height: listView.height + + Image { + anchors.fill: parent + source: url + fillMode: Image.PreserveAspectFit + } + + Text { + text: supplier.name + "\n" + supplier.url + width: parent.width + anchors.bottom: parent.bottom + } + } + } + //! [ImageModel] + + //! [Supplier] + Supplier { + id: placeSupplier + name: "Example" + url: "http://www.example.com/" + } + + Text { + text: "This place is was provided by " + placeSupplier.name + "\n" + placeSupplier.url + } + //! [Supplier] + + //! [Ratings] + Text { + text: "This place is rated " + place.ratings.average + " out of " + place.ratings.maximum + " stars." + } + //! [Ratings] + + //! [ContactDetails read] + function printContactDetails(contactDetails) { + var keys = contactDetails.keys(); + for (var i = 0; i < keys.length; ++i) { + var contactList = contactDetails[keys[i]]; + for (var j = 0; j < contactList.length; ++j) { + console.log(contactList[j].label + ": " + contactList[j].value); + } + } + } + //! [ContactDetails read] + + //! [ContactDetails write single] + function writeSingle() { + var phoneNumber = Qt.createQmlObject('import QtLocation 5.3; ContactDetail {}', place); + phoneNumber.label = "Phone"; + phoneNumber.value = "555-5555" + place.contactDetails.phone = phoneNumber; + } + //! [ContactDetails write single] + + //! [ContactDetails write multiple] + function writeMultiple() { + var bob = Qt.createQmlObject('import QtLocation 5.3; ContactDetail {}', place); + bob.label = "Bob"; + bob.value = "555-5555" + + var alice = Qt.createQmlObject('import QtLocation 5.3; ContactDetail {}', place); + alice.label = "Alice"; + alice.value = "555-8745" + + var numbers = new Array(); + numbers.push(bob); + numbers.push(alice); + + place.contactDetails.phone = numbers; + } + //! [ContactDetails write multiple] + + //! [ContactDetails phoneList] + ListView { + model: place.contactDetails.phone; + delegate: Text { text: modelData.label + ": " + modelData.value } + } + //! [ContactDetails phoneList] + + //! [Place savePlace def] + Place { + id: myPlace + plugin: myPlugin + + name: "Brisbane Technology Park" + location: Location { + address: Address { + street: "53 Brandl Street" + city: "Eight Mile Plains" + postalCode: "4113" + country: "Australia" + } + coordinate { + latitude: -27.579646 + longitude: 153.100308 + } + } + + visibility: Place.PrivateVisibility + } + //! [Place savePlace def] + + function fetchDetails() { + //! [Place fetchDetails] + if (!place.detailsFetched) + place.getDetails(); + //! [Place fetchDetails] + } + + function savePlace() { + //! [Place savePlace] + myPlace.save(); + //! [Place savePlace] + } + + function createAndSavePlace() { + //! [Place createAndSavePlace] + //creating and saving a place + var place = Qt.createQmlObject('import QtLocation 5.3; Place { }', parent); + place.plugin = myPlugin; + place.name = "New York"; + place.location.coordinate.latitude = 40.7 + place.location.coordinate.longitude = -74.0 + place.save(); + //! [Place createAndSavePlace] + } + + function removePlace() { + //! [Place removePlace] + //removing a place + place.remove(); + //! [Place removePlace] + } + + function saveToNewPlugin() { + //! [Place save to different plugin] + var place = Qt.createQmlObject('import QtLocation 5.3; Place { }', parent); + place.plugin = destinationPlugin; + place.copyFrom(originalPlace); + place.save(); + //! [Place save to different plugin] + } + + function getPlaceForId() { + //! [Place placeId] + place.plugin = myPlugin; + place.placeId = "known-place-id"; + place.getDetails(); + //! [Place placeId] + } + + function primaryContacts() { + //! [Place primaryPhone] + var primaryPhone; + if (place.contactDetails["phone"].length > 0) + primaryPhone = place.contactDetails["phone"][0].value; + //! [Place primaryPhone] + //! [Place primaryFax] + var primaryFax; + if (place.contactDetails["fax"].length > 0) + primaryFax = place.contactDetails["fax"][0].value; + //! [Place primaryFax] + //! [Place primaryEmail] + var primaryEmail; + if (place.contactDetails["email"].length > 0) + primaryEmail = place.contactDetails["email"][0].value; + //! [Place primaryEmail] + //! [Place primaryWebsite] + var primaryWebsite; + if (place.contactDetails["website"].length > 0) + primaryWebsite = place.contactDetails["website"][0].value; + //! [Place primaryWebsite] + } + + //! [Place favorite] + Text { text: place.favorite ? place.favorite.name : place.name } + //! [Place favorite] + + function saveFavorite() { + var place = Qt.createQmlObject('import QtLocation 5.3; Place { }', parent); + var destinationPlugin + //! [Place saveFavorite] + place.initializeFavorite(destinationPlugin); + //if necessary customizations to the favorite can be made here. + //... + place.favorite.save(); + //! [Place saveFavorite] + } + + function removeFavorite() { + var place; + //! [Place removeFavorite 1] + place.favorite.remove(); + //! [Place removeFavorite 1] + + //! [Place removeFavorite 2] + //check successful removal of the favorite by monitoring its status. + //once that is done we can assign null to the favorite + place.favorite = null; + //! [Place removeFavorite 2] + } + + function connectStatusChangedHandler() { + //! [Place checkStatus] + place.statusChanged.connect(statusChangedHandler); + //! [Place checkStatus] + } + + //! [Place checkStatus handler] + function statusChangedHandler() { + if (statusChangedHandler.prevStatus === Place.Saving) { + switch (place.status) { + case Place.Ready: + console.log('Save successful'); + break; + case Place.Error: + console.log('Save failed'); + break; + default: + break; + } + } + statusChangedHandler.prevStatus = place.status; + } + //! [Place checkStatus handler] +} diff --git a/src/location/doc/snippets/declarative/places_loader.qml b/src/location/doc/snippets/declarative/places_loader.qml new file mode 100644 index 0000000..a52a4b0 --- /dev/null +++ b/src/location/doc/snippets/declarative/places_loader.qml @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtPositioning 5.2 +import QtLocation 5.3 + +Rectangle { + width: 360 + height: 360 + property variant startCoordinate: QtPositioning.coordinate(-27.46778, 153.02778) + + Plugin { + id: myPlugin + name: "osm" + //specify plugin parameters if necessary + //PluginParameter {...} + //PluginParameter {...} + //... + } + + PlaceSearchModel { + id: searchModel + + plugin: myPlugin + + searchTerm: "pizza" + searchArea: QtPositioning.circle(startCoordinate) + + Component.onCompleted: update() + } + + //! [Handle Result Types] + Component { + id: resultDelegate + Loader { + Component { + id: placeResult + + Column { + Text { text: title } + Text { text: place.location.address.text } + } + } + + Component { + id: otherResult + Text { text: title } + } + + sourceComponent: type == PlaceSearchModel.PlaceResult ? placeResult : + otherResult + } + } + //! [Handle Result Types] + + ListView { + anchors.fill: parent + model: searchModel + delegate: resultDelegate + spacing: 10 + } + + Connections { + target: searchModel + onStatusChanged: { + if (searchModel.status == PlaceSearchModel.Error) + console.log(searchModel.errorString()); + } + } +} diff --git a/src/location/doc/snippets/declarative/plugin.qml b/src/location/doc/snippets/declarative/plugin.qml new file mode 100644 index 0000000..4428210 --- /dev/null +++ b/src/location/doc/snippets/declarative/plugin.qml @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +//! [Plugin import] +import QtLocation 5.3 +//! [Plugin import] + +Item { + //! [Plugin locale] + //single locale + Plugin { + locales: "en_US" + } + + //multiple locales + Plugin { + locales: ["fr_FR","en_US"] + } + //! [Plugin locale] +} diff --git a/src/location/doc/snippets/declarative/routing.qml b/src/location/doc/snippets/declarative/routing.qml new file mode 100644 index 0000000..643722b --- /dev/null +++ b/src/location/doc/snippets/declarative/routing.qml @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [QtQuick import] +import QtQuick 2.3 +//! [QtQuick import] +import QtPositioning 5.5 +import QtLocation 5.6 + +Item { + width: 1000 + height: 400 + + Plugin { + id: aPlugin + name: "osm" + } + + RouteQuery { + id: aQuery + waypoints: [ + { latitude: -27.575, longitude: 153.088}, + { latitude: -27.465, longitude: 153.023} + ] + travelModes: RouteQuery.CarTravel + routeOptimizations: RouteQuery.ShortestRoute + } + + //! [Route Maneuver List1] + RouteModel { + id: routeModel + // model initialization + //! [Route Maneuver List1] + plugin: aPlugin + autoUpdate: true + query: aQuery + //! [Route Maneuver List2] + } + + + ListView { + id: listview + anchors.fill: parent + spacing: 10 + model: routeModel.status == RouteModel.Ready ? routeModel.get(0).segments : null + visible: model ? true : false + delegate: Row { + width: parent.width + spacing: 10 + property bool hasManeuver : modelData.maneuver && modelData.maneuver.valid + visible: hasManeuver + Text { text: (1 + index) + "." } + Text { text: hasManeuver ? modelData.maneuver.instructionText : "" } + //! [Route Maneuver List2] + property RouteManeuver routeManeuver: modelData.maneuver + property RouteSegment routeSegment: modelData + + //! [RouteManeuver] + Text { + text: "Distance till next maneuver: " + routeManeuver.distanceToNextInstruction + + " meters, estimated time: " + routeManeuver.timeToNextInstruction + " seconds." + } + //! [RouteManeuver] + + //! [RouteSegment] + Text { + text: "Segment distance " + routeSegment.distance + " meters, " + routeSegment.path.length + " points." + } + //! [RouteSegment] + //! [Route Maneuver List3] + } + } + //! [Route Maneuver List3] +} diff --git a/src/location/doc/snippets/places/main.cpp b/src/location/doc/snippets/places/main.cpp new file mode 100644 index 0000000..8820fbc --- /dev/null +++ b/src/location/doc/snippets/places/main.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "requesthandler.h" + +int main(int /*argc*/, char ** /*argv*/) +{ + return 0; +} + diff --git a/src/location/doc/snippets/places/places.pro b/src/location/doc/snippets/places/places.pro new file mode 100644 index 0000000..9fa4c7f --- /dev/null +++ b/src/location/doc/snippets/places/places.pro @@ -0,0 +1,5 @@ +TEMPLATE=app +TARGET=placescppsnippet +QT = core location +SOURCES+=main.cpp +HEADERS += requesthandler.h diff --git a/src/location/doc/snippets/places/requesthandler.h b/src/location/doc/snippets/places/requesthandler.h new file mode 100644 index 0000000..04b1b05 --- /dev/null +++ b/src/location/doc/snippets/places/requesthandler.h @@ -0,0 +1,580 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class RequestHandler : public QObject +{ +public: + void initializeManager() { + //! [Initialize Manager] + //The "provider name" is used to select a particular provider + QGeoServiceProvider *provider = new QGeoServiceProvider("provider name"); + QPlaceManager *manager = provider->placeManager(); + //! [Initialize Manager] + Q_UNUSED(provider); + Q_UNUSED(manager); + } + + void simpleSearch() + { + //! [Simple search] + //1) Make an appropriate request + QPlaceSearchRequest searchRequest; + searchRequest.setSearchTerm("ice cream"); + searchRequest.setSearchArea(QGeoCircle(QGeoCoordinate(12.34, 56.78))); + + //2) Use the manager to initiate a request and retrieve a reply object + QPlaceSearchReply * searchReply = manager->search(searchRequest); + + //3) Connect the reply object to a slot which is invoked upon operation completion + connect(searchReply, SIGNAL(finished()), this, SLOT(processSearchReply())); + //! [Simple search] + } + + void search() + { + //! [Search for places cpp] + + //instantiate request and set parameters + QPlaceSearchRequest searchRequest; + searchRequest.setSearchTerm("ice cream"); + searchRequest.setSearchArea(QGeoCircle(QGeoCoordinate(12.34, 56.78))); + + //send off a search request + /*QPlaceSearchReply * */ searchReply = manager->search(searchRequest); + + //connect a slot to handle the reply + connect(searchReply, SIGNAL(finished()), this, SLOT(handleSearchReply())); + + //! [Search for places cpp] + } + + void searchPaging() + { + //! [Search paging] + QPlaceSearchRequest searchRequest; + searchRequest.setLimit(15); //specify how many results are to be retrieved. + //! [Search paging] + } + + void details() + { + QPlace place; + //! [Details check] + if (!place.detailsFetched()) { + /*QPlaceDetailsReply * */ detailsReply = manager->getPlaceDetails(place.placeId()); + connect(detailsReply, SIGNAL(finished()), this, SLOT(handleDetailsReply())); + } + //! [Details check] + } + + void images() + { + QPlace place; + + //! [Image request] + QPlaceContentRequest request; + request.setContentType(QPlaceContent::ImageType); + request.setPlaceId(place.placeId()); + request.setLimit(5); + /*QPlaceContentReply * */ contentReply = manager->getPlaceContent(request); + connect(contentReply, SIGNAL(finished()), this, SLOT(handleImagesReply())); + //! [Image request] + } + + + void suggestion() + { + //! [Suggestion request] + QPlaceSearchRequest request; + request.setSearchTerm("piz"); + request.setSearchArea(QGeoCircle(QGeoCoordinate(12.34, 56.78))); + /* QPlaceSearchSuggestion * */suggestionReply = manager->searchSuggestions(request); + connect(suggestionReply, SIGNAL(finished()), this, SLOT(handleSuggestionReply())); + //! [Suggestion request] + } + + void savePlace() + { + //! [Save place pt1] + QPlace place; + place.setName( "Fred's Ice Cream Parlor" ); + + QGeoLocation location; + location.setCoordinate(QGeoCoordinate(12.34, 56.78)); + + QGeoAddress address; + address.setStreet("111 Nother Street"); + //! [Save place pt1] + + //! [Save place pt2] + location.setAddress(address); + place.setLocation(location); + + /* QPlaceIdReply * */savePlaceReply = manager->savePlace(place); + connect(savePlaceReply, SIGNAL(finished()), this, SLOT(handleSavePlaceReply())); + //! [Save place pt2] + } + + void removePlace() + { + QPlace place; + //! [Remove place] + /* QPlaceIdReply * */removePlaceReply = manager->removePlace(place.placeId()); + connect(removePlaceReply, SIGNAL(finished()), this, SLOT(handleRemovePlaceReply())); + //! [Remove place] + } + + void initializeCategories() + { + //! [Initialize categories] + /* QPlaceReply * */initCatReply = manager->initializeCategories(); + connect(initCatReply, SIGNAL(finished()), this, SLOT(handleInitCatReply())); + //! [Initialize categories] + } + + void saveCategory() + { + //! [Save category] + QPlaceCategory fastFood; + + QPlaceCategory category; + category.setName("pizza"); + /*QPlaceIdReply */ saveCategoryReply = manager->saveCategory(category); + connect(saveCategoryReply, SIGNAL(finished()), this, SLOT(handleSaveCategoryReply())); + + //we could have saved a category as a child by supplying a parent identifier. + saveCategoryReply = manager->saveCategory(category, fastFood.categoryId()); + //! [Save category] + } + + void removeCategory() + { + QPlaceCategory category; + //! [Remove category] + /* QPlaceIdReply * */removeCategoryReply = manager->removeCategory(place.placeId()); + connect(removeCategoryReply, SIGNAL(finished()), this, SLOT(handleRemoveCategoryReply())); + //! [Remove category] + } + + void searchRequest() { + QPlaceCategory diner; + QPlaceCategory restaurant; + + //! [Search request] + QPlaceSearchRequest searchRequest; + searchRequest.setSearchTerm("Fast food"); //search term for what we are interested in + + //set a search center + searchRequest.setSearchArea(QGeoCircle(QGeoCoordinate(2.3, 48.87))); + + //set a distance hint as a relevancy hint. + //closer places have greater weighting in the ranking of results. + searchRequest.setRelevanceHint(QPlaceSearchRequest::DistanceHint); + + //use limit to adjust pagination. + //this limits the number of place results to 5 per page. + searchRequest.setLimit(5); + + //provide some categories to narrow down search + QList categories; + categories << diner << restaurant; + searchRequest.setCategories(categories); + //! [Search request] + } + + void content() { + QPlace place; + //! [Content request] + QPlaceContentRequest request; + request.setContentType(QPlaceContent::ImageType); + request.setPlaceId(place.placeId()); + request.setLimit(5); + + QPlaceContentReply *contentReply = manager->getPlaceContent(request); + //..connect signals..// + + //! [Content request] + Q_UNUSED(contentReply); + } + + void contentConversion() + { + //! [Content conversion] + QPlaceImage image; + image.setUrl(QUrl("www.example.com")); + + QPlaceContent content = image; + + QPlaceImage image2; + image2 = content; + qDebug() << image2.url(); //image2.url() == "www.example.com" + //! [Content conversion] + } + + void icon() { + QPlace place; + //! [icon] + QUrl iconSourceUrl = place.icon().url(QSize(32,32)); + + //A default icon may also be requested like so + iconSourceUrl = place.icon().url(); + //! [icon] + } + + void saveBetweenManagers() { + QPlaceResult result; + QPlaceIdReply *saveReply; + //! [ Save to different manager] + //result retrieved from a different manager) + QPlace place = manager->compatiblePlace(result.place()); + saveReply = manager->savePlace(place); + //! [ Save to different manager] + saveReply->abort();//needed to avoid warnings + } + + void ratings() { + //! [Ratings] + qDebug() << QString("This place rated ") + place.ratings().average() + + "out of " + place.ratings().maximum() + "stars"; + //! [Ratings] + } + + void matchPlaces() { + QList results; + //! [Match places] + QPlaceMatchRequest request; + request.setResults(results); + QVariantMap parameters; + parameters.insert(QPlaceMatchRequest::AlternativeId, "x_id_here"); + request.setParameters(parameters); + matchReply = manager->matchingPlaces(request); + //! [Match places] + } + +public slots: + // ![Simple search handler] + //4) Have the slot appropriately process the results of the operation + void processSearchReply() { + if (searchReply->error() == QPlaceReply::NoError) { + foreach (const QPlaceSearchResult &result, searchReply->results()) { + if (result.type() == QPlaceSearchResult::PlaceResult) + qDebug() << "Title:" << result.title(); + } + } + + //5) Discard the rely object when done. + searchReply->deleteLater(); + searchReply = 0; + } + // ![Simple search handler] + + //! [Search for places handler cpp] + void handleSearchReply() { + if (searchReply->error() == QPlaceReply::NoError) { + foreach (const QPlaceSearchResult &result, searchReply->results()) { + if (result.type() == QPlaceSearchResult::PlaceResult) { + QPlaceResult placeResult = result; + qDebug() << "Name: " << placeResult.place().name(); + qDebug() << "Coordinate " << placeResult.place().location().coordinate().toString(); + qDebug() << "Street: " << placeResult.place().location().address().street(); + qDebug() << "Distance: " << placeResult.distance(); + } + } + } + searchReply->deleteLater(); //discard reply + searchReply = 0; + } + //! [Search for places handler cpp] + + //! [Details handler cpp] + void handleDetailsReply() { + QPlace place; + if (detailsReply->error() == QPlaceReply::NoError) + place = detailsReply->place(); + + detailsReply->deleteLater(); //discard reply + detailsReply = 0; + } + //! [Details handler cpp] + + //! [Image handler] + void handleImagesReply() { + if (contentReply->error() == QPlaceReply::NoError) { + QMapIterator iter(contentReply->content()); + while (iter.hasNext()) { + qDebug() << "Index: " << iter.key(); + QPlaceImage image = iter.value(); + qDebug() << image.url(); + qDebug() << image.mimeType(); + } + + //alternatively if indexes are irrelevant + foreach (const QPlaceImage &image, contentReply->content()) { + qDebug() << image.url(); + qDebug() << image.mimeType(); + } + + //we can assign content to the place that it belongs to. + //the place object serves as a container where we can retrieve + //content that has already been fetched + place.insertContent(contentReply->request().contentType(), contentReply->content()); + place.setTotalContentCount(contentReply->request().contentType(), contentReply->totalCount()); + } + + contentReply->deleteLater(); + contentReply = 0; + } + //! [Image handler] + + //! [Suggestion handler] + void handleSuggestionReply() { + if (suggestionReply->error() == QPlaceReply::NoError) { + foreach (const QString &suggestion, suggestionReply->suggestions()) + qDebug() << suggestion; + } + + suggestionReply->deleteLater(); //discard reply + suggestionReply = 0; + } + + //! [Suggestion handler] + + //! [Save place handler] + void handleSavePlaceReply() { + if (savePlaceReply->error() == QPlaceReply::NoError) + qDebug() << savePlaceReply->id(); + + savePlaceReply->deleteLater(); //discard reply + savePlaceReply = 0; + } + //! [Save place handler] + + //! [Remove place handler] + void handleRemovePlaceReply() { + if (removePlaceReply->error() == QPlaceReply::NoError) + qDebug() << "Removal of place identified by" + << removePlaceReply->id() << "was successful"; + + removePlaceReply->deleteLater(); //discard reply + removePlaceReply = 0; + } + //! [Remove place handler] + + //! [Initialize categories reply] + void handleInitCatReply() { + if (initCatReply->error() == QPlaceReply::NoError) + qDebug() << "Categories initialized"; + else + qDebug() << "Failed to initialize categories"; + + initCatReply->deleteLater(); + initCatReply = 0; + } + //! [Initialize categories reply] + + void categories() { + QPlaceCategory pizza; + //! [Top level categories] + QList topLevelCategories = manager->childCategories(); + foreach (const QPlaceCategory &category, topLevelCategories) + qDebug() << category.name(); + //! [Top level categories] + + //! [Child categories] + QList childCategories = manager->childCategories(pizza.categoryId()); + //! [Child categories] + } + + //! [Save category handler] + void handleSaveCategoryReply() { + if (saveCategoryReply->error() == QPlaceReply::NoError) { + qDebug() << "Saved category id =" << saveCategoryReply->id(); + } + + saveCategoryReply->deleteLater(); + saveCategoryReply = 0; + } + //! [Save category handler] + + //! [Remove category handler] + void handleRemoveCategoryReply() { + if (removeCategoryReply->error() == QPlaceReply::NoError) + qDebug() << "Removal of category identified by" + << removeCategoryReply->id() << "was successful"; + + removeCategoryReply->deleteLater(); //discard reply + removeCategoryReply = 0; + } + //! [Remove category handler] + + //! [Content handler] + void contentHandler() { + if (contentReply->error() == QPlaceReply::NoError) { + place.insertContent(contentReply->request().contentType(), + contentReply->content()); + } + } + //! [Content handler] + + void phoneNumbers() { + //! [Phone numbers] + if (place.contactTypes().contains(QPlaceContactDetail::Phone)) { + foreach (const QPlaceContactDetail &number, place.contactDetails(QPlaceContactDetail::Phone)) + qDebug() << number.label() << ":" << number.value(); + } + //! [Phone numbers] + } + + + void openingHours() { + //! [Opening hours] + if (place.extendedAttributeTypes().contains(QPlaceAttribute::OpeningHours)) + qDebug() << place.extendedAttribute(QPlaceAttribute::OpeningHours).text(); + //! [Opening hours] + } + + //! [Match places handler] + void matchHandler() { + if (matchReply->error() == QPlaceReply::NoError) { + foreach (const QPlace place, matchReply->places()) { + if (place != QPlace()) + qDebug() << "Place is a favorite with name" << place.name(); + else + qDebug() << "Place is not a favorite"; + } + } + + matchReply->deleteLater(); + matchReply = 0; + } + //! [Match places handler] + + void convertSearchResult() { + QPlaceSearchResult result; + //! [Convert search result] + if (result.type() == QPlaceSearchResult::PlaceResult) { + QPlaceResult placeResult = result; + qDebug() << placeResult.place().name(); + qDebug() << placeResult.place().location().coordinate(); + qDebug() << placeResult.distance(); + } + //! [Convert search result] + } + +QPlaceSearchReply *searchReply; +QPlaceManager *manager; +QPlaceDetailsReply *detailsReply; +QPlaceContentReply *contentReply; +QPlaceSearchSuggestionReply *suggestionReply; +QPlaceIdReply *savePlaceReply; +QPlaceIdReply *removePlaceReply; +QPlaceIdReply *saveCategoryReply; +QPlaceIdReply *removeCategoryReply; +QPlaceReply *initCatReply; +QPlaceMatchReply *matchReply; +QPlace place; +}; + +class ManagerEngine : public QObject +{ +}; + +//! [Implement reply pt1] +class SearchReply : public QPlaceSearchReply +{ +public: + explicit SearchReply(ManagerEngine *engine) + : QPlaceSearchReply(engine), m_engine(engine){} + + ~SearchReply(); + void setResults(const QList &results); + void setRequest(const QPlaceSearchRequest &request); +//! [Implement reply pt1] + +//! [Implement reply pt2] + void triggerDone(QPlaceReply::Error error = QPlaceReply::NoError, + const QString &errorString = QString()); + + ManagerEngine *m_engine; +}; +//! [Implement reply pt2] + +class SearchSuggestionReply : public QPlaceSearchSuggestionReply +{ +public: + void triggerDone(QPlaceReply::Error error = QPlaceReply::NoError, + const QString &errorString = QString()); + + ManagerEngine *m_engine; + +}; + +//! [Trigger done] +void SearchSuggestionReply::triggerDone(QPlaceReply::Error error, + const QString &errorString) +{ + if (error != QPlaceReply::NoError) { + this->setError(error,errorString); + QMetaObject::invokeMethod(m_engine, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply *,this), + Q_ARG(QPlaceReply::Error, error), + Q_ARG(QString, errorString)); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, error), + Q_ARG(QString, errorString)); + } + + this->setFinished(true); + QMetaObject::invokeMethod(m_engine, "finished", Qt::QueuedConnection, + Q_ARG(QPlaceReply *,this)); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); +} +//! [Trigger done] diff --git a/src/location/doc/snippets/snippets.pro b/src/location/doc/snippets/snippets.pro new file mode 100644 index 0000000..e4946c8 --- /dev/null +++ b/src/location/doc/snippets/snippets.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += places declarative cpp diff --git a/src/location/doc/src/cpp-qml.qdoc b/src/location/doc/src/cpp-qml.qdoc new file mode 100644 index 0000000..f20e147 --- /dev/null +++ b/src/location/doc/src/cpp-qml.qdoc @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page location-cpp-qml.html +\title Interfaces between C++ and QML Code in Qt Location + +\brief Some of the location QML types providing interfaces to access and modify properties in C++. + +\section2 Category - QPlaceCategory +The \l {Category::category} {Category.category} property is used to provide an interface between C++ and QML code. First a pointer to a +Category object must be obtained from C++, then use the \l {QObject::property()}{property()} and +\l {QObject::setProperty()}{setProperty()} functions to get and set the \c category property. +The following gets the QPlaceCategory representing this object from C++: +\snippet cpp/cppqml.cpp Category get +The following sets the properties of this object based on a QPlaceCategory object from C++: +\snippet cpp/cppqml.cpp Category set + + +\section2 ContactDetail - QDeclarativeContactDetail +The \l {ContactDetail::contactDetail} {ContactDetail.contactDetail} property is used to provide an interface between C++ and QML code. First a pointer to a ContactDetail object must be obtained from C++, then use the +\l {QObject::property()}{property()} and \l {QObject::setProperty()}{setProperty()} functions +to get and set the \c contactDetail property. +The following gets the QPlaceContactDetail representing this object from C++: +\snippet cpp/cppqml.cpp ContactDetail get +The following sets the properties of this object based on a QPlaceContactDetail object from +C++: +\snippet cpp/cppqml.cpp ContactDetail set + + +\section2 Place - QPlace +The \l {Place::place} {Place.place} property is used to provide an interface between C++ and QML code. First a pointer to a Place object must be obtained from C++, then use the +\l {QObject::property()}{property()} and \l {QObject::setProperty()}{setProperty()} functions +to get and set the \c place property. +The following gets the QPlace representing this object from C++: +\snippet cpp/cppqml.cpp Place get +The following sets the properties of this object based on a QPlace object from C++: +\snippet cpp/cppqml.cpp Place set + + + +\section2 PlaceAttribute - QPlaceAttribute +The \l {PlaceAttribute::attribute} {PlaceAttribute.attribute} property is used to provide an interface between C++ and QML code. First a pointer to a +PlaceAttribute object must be obtained from C++, then use the +\l {QObject::property()}{property()} and \l {QObject::setProperty()}{setProperty()} functions +to get and set the \c attribute property. +The following gets the QPlaceAttribute representing this object from C++: +\snippet cpp/cppqml.cpp PlaceAttribute get +The following sets the properties of this object based on a QPlaceAttribute object from C++: +\snippet cpp/cppqml.cpp PlaceAttribute set + + +\section2 Icon - QPlaceIcon +The \l {Icon::icon} {Icon.icon} property is used to provide an interface between C++ and QML code. First a pointer to a Icon object must be obtained from C++, then use the \l {QObject::property()}{property()} and +\l {QObject::setProperty()}{setProperty()} functions to get and set the \c icon property. +The following gets the QPlaceIcon representing this object from C++: +\snippet cpp/cppqml.cpp Icon get +The following sets the properties of this object based on a QPlaceIcon object from C++: +\snippet cpp/cppqml.cpp Icon set + + +\section2 User - QPlaceUser +The \l {User::user} {User.user} property is used to provide an interface between C++ and QML code. First a pointer to a +User object must be obtained from C++, then use the \l {QObject::property()}{property()} and +\l {QObject::setProperty()}{setProperty()} functions to get and set the \c user property. +The following gets the QPlaceUser representing this object from C++: +\snippet cpp/cppqml.cpp User get +The following sets the properties of this object based on a QPlaceUser object from C++: +\snippet cpp/cppqml.cpp User set + + +\section2 Ratings - QPlaceRatings +The \l {Ratings::ratings} {Ratings.ratings} property is used to provide an interface between C++ and QML code. First a pointer to a +Ratings object must be obtained from C++, then use the \l {QObject::property()}{property()} and +\l {QObject::setProperty()}{setProperty()} functions to get and set the \c ratings property. +The following gets the QPlaceRating representing this object from C++: +\snippet cpp/cppqml.cpp Ratings get +The following sets the properties of this object based on a QPlaceRatings object from C++: +\snippet cpp/cppqml.cpp Ratings set + + +\section2 Supplier - QPlaceSupplier +The \l {Supplier::supplier} {Supplier.supplier} property is used to provide an interface between C++ and QML code. First a pointer to a +Supplier object must be obtained from C++, then use the \l {QObject::property()}{property()} and +\l {QObject::setProperty()}{setProperty()} functions to get and set the \c supplier property. +The following gets the QPlaceSupplier representing this object from C++: +\snippet cpp/cppqml.cpp Supplier get +The following sets the properties of this object based on a QPlaceSupplier object from C++: +\snippet cpp/cppqml.cpp Supplier set + +*/ diff --git a/src/location/doc/src/example-parameters.qdocinc b/src/location/doc/src/example-parameters.qdocinc new file mode 100644 index 0000000..2ae351b --- /dev/null +++ b/src/location/doc/src/example-parameters.qdocinc @@ -0,0 +1,12 @@ +The example can work with any of the available geo services plugins. However, some +plugins may require additional \l {QtLocation::PluginParameter}{plugin parameters} in order to +function correctly. \l {QtLocation::PluginParameter}{Plugin parameters} can be passed on the +command line using the \c {--plugin} argument, which takes the form: + +\badcode + --plugin. +\endcode + +Refer to the documentation for each of the geo services plugins for details on what plugin +parameters they support. The default plugin used by this example is +\l {Qt Location Open Street Map Plugin}, which does not require any parameters. diff --git a/src/location/doc/src/maps.qdoc b/src/location/doc/src/maps.qdoc new file mode 100644 index 0000000..759394e --- /dev/null +++ b/src/location/doc/src/maps.qdoc @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/*! +\page location-maps-qml.html +\title Maps and Navigation (QML) + +\brief Provides QtQuick user interfaces for displaying, navigating and + interacting with maps, as well as geocoding and navigation. + +\b{Maps and Navigation} provides QtQuick user interface types for +displaying geographic information on a map, as well as allowing user +interaction with map overlay objects and the display itself. It also +contains utilities for geocoding (finding a geographic coordinate from a +street address) and navigation (including driving and walking directions). + +It builds upon the API concepts and types in the \l{Positioning (QML)}{QML Positioning API}. +A more hands-on introduction of the Maps and Navigation types can be found in the +\l {QML Maps}{Maps and Navigation tutorial}. + +\section1 Maps + +\section2 Displaying Maps + +Displaying a map is done using the \l{QtLocation::Map}{Map} QML types. The Map type supports +user interaction through the \l{QtLocation::MapGestureArea}{MapGestureArea} QML type. The Map +object draws the map on-screen using OpenGL (ES), allowing for hardware-accelerated rendering +where available. + +\b{Key Types} +\table + \row + \li \l{QtLocation::Plugin}{Plugin} + \li A location-based services plugin provides data including map data which is then displayed in a Map object. + \row + \li \l{QtLocation::Map}{Map} + \li QtQuick item to display a map on-screen. + \row + \li \l{QtLocation::MapGestureArea}{MapGestureArea} + \li Interaction helper for panning, flicking and pinch-to-zoom gesture on a Map. +\endtable + +Note that the client must create a \l{QtLocation::Plugin}{Plugin} object +prior to using a \l{QtLocation::Map}{Map} type in order to have access +to map data to display. + +\section2 Putting Objects on a Map (Map Overlay Objects) + +Maps can also contain map overlay objects, which are used to display information +on its surface. There is a set of basic pre-defined map overlay objects, as well +as the ability to implement custom map overlay objects using the +\l{QtLocation::MapQuickItem}{MapQuickItem} type, which can contain any +standard QtQuick item. + +\b{Key Types} +\table + \row + \li \l{QtLocation::MapCircle}{MapCircle} + \li A geographic circle (all points at a set distance from a center), optionally with a border. + \row + \li \l{QtLocation::MapRectangle}{MapRectangle} + \li A rectangle whose top left and bottom right points are specified as + \l {coordinate} types, optionally with a border. + \row + \li \l{QtLocation::MapPolygon}{MapPolygon} + \li A polygon made of an arbitrary list of \l {coordinate}{coordinates}. + \row + \li \l{QtLocation::MapPolyline}{MapPolyline} + \li A polyline made of an arbitrary list of \l {coordinate}{coordinates}. + \row + \li \l{QtLocation::MapQuickItem}{MapQuickItem} + \li Turns any arbitrary QtQuick Item into a map overlay object. MapQuickItem is an enabler for specifying custom map overlay objects. +\endtable + +\section2 Model-View Design with Map Overlay Objects + +To automatically generate map overlay objects based on the contents of a QtQuick +model (for example a ListModel item), the \l{QtLocation::MapItemView}{MapItemView} +type is available. It accepts any map overlay object as its delegate, and can +only be created within a \l{QtLocation::Map}{Map}. + +\b{Key Types} +\table + \row + \li \l{QtLocation::MapItemView}{MapItemView} + \li Populates a Map with map overlay objects based on the data provided by a model. +\endtable + +\section2 Interaction with Map Overlay Objects + +Properties of map overlay objects that influence their appearance on the display can +be changed at any time, and many can also be used in animations. Animating +coordinate-based map overlay objects, such as MapPolygon and MapPolyline, is not yet +available. + +\section1 Geocoding -- Address to Coordinate and Vice Versa + +Geocoding is the translation of geographic coordinates into addresses, or vice +versa. Such a translation usually involves sending the source data to a server +which then performs the translation and returns the results, although some +location-based service provider \l{QtLocation::Plugin}{plugins} may be able to +provide some geocoding functionality without sending data to a remote server. +The availability and accuracy of the translation usually depends on the location +or address being translated, as different areas of the Earth are mapped to +varying degrees of accuracy. + +A geocoding query in QML is performed using the +\l{QtLocation::GeocodeModel}{GeocodeModel} type. For an address-to-coordinate +query, its \c{query} property may be set to either an +\l [QtPositioning]{Address} object or a string containing the textual +form of the address to search for. To perform the reverse, the same property +can be set to a \l {coordinate} instead. Results are made available in the +contents of the model. + +\b{Key Types} +\table + \row + \li \l{QtLocation::Plugin}{Plugin} + \li A location-based services plugin provides data including geocoding translation results which are exposed to clients via a GeocodeModel. + \row + \li \l{QtLocation::GeocodeModel}{GeocodeModel} + \li Queries the Plugin for geocoding translations and provides access to results via indexes in the model. + \row + \li \l[QtPositioning]{Address} + \li Structured address for use in queries and results of geocoding. +\endtable + +Note that the client must create a \l{QtLocation::Plugin}{Plugin} object +prior to using a \l{QtLocation::GeocodeModel}{GeocodeModel} object. This +will enable access to geocoding translation services and thus data to display. + +\section1 Routing and Navigation + +Routing is the determination of a navigable path from one point to another on +a map. Given a map that is aware of features that aid or hinder navigation, such as +bridges, waterways and so on, a series of segments that make +up the journey can be constructed. If these \l {RouteSegment}s are simple then we can +add navigation information at the connecting points, \l {RouteManeuver}s, +between the segments. + +\b{Key Types} +\table + \row + \li \l{QtLocation::Route}{Route} + \li The entire path to be navigated. + \row + \li \l{QtLocation::RouteSegment}{RouteSegment} + \li The individual components of a route. + \row + \li \l{QtLocation::RouteManeuver}{RouteManeuver} + \li The navigation information that joins segments. + \row + \li \l{QtLocation::RouteModel}{RouteModel} + \li The means of making requests on the backend to supply route + information. +\endtable + + + + + + +*/ + + +/*! +\page location-maps-cpp.html +\title Maps and Navigation (C++) + +\brief Provides C++ classes for Geocoding and Navigation. + +\b{Maps and Navigation} provides C++ utilities for geocoding (finding a +geographic coordinate from a street address) and navigation (including driving +and walking directions). + +Currently it is not possible to interact with maps via C++. Mapping applications +must use the \l {Maps and Navigation (QML)} API. + + +\section1 Geocoding + +In C++, an address-to-coordinate query is performed using the +\l{QGeoCodingManager::geocode()}{geocode()} method of the QGeoCodingManager +class. For coordinate-to-address queries, the +\l{QGeoCodingManager::reverseGeocode()}{reverseGeocode()} method is available +on the same class. Instances of QGeoCodingManager are available via +\l{QGeoServiceProvider}. + +\b{Key Classes} +\table + \row + \li \l{QGeoServiceProvider} + \li Provides a QGeoCodingManager instance ready for use. + \row + \li \l{QGeoCodingManager} + \li Accepts queries and produces QGeoCodeReply objects. + \row + \li \l{QGeoCodeReply} + \li Contains the results of a geocoding query. +\endtable + +\section1 Navigation + +In C++, a route query is performed using the \l{QGeoRoutingManager::calculateRoute()}{calculate()} +method of the QGeoRoutingManager class. The returned route reply can contain +multiple routes to the same destination. + +\b{Key Classes} +\table + \row + \li \l{QGeoServiceProvider} + \li Provides a QGeoCodingManager instance ready for use. + \row + \li \l{QGeoRoutingManager} + \li Accepts queries and produces QGeoRouteReply objects. + \row + \li \l{QGeoRouteReply} + \li Contains the results of a routing query. + \row + \li \l{QGeoRoute} + \li Contains information about a route. +\endtable + + +*/ diff --git a/src/location/doc/src/place-caveats.qdocinc b/src/location/doc/src/place-caveats.qdocinc new file mode 100644 index 0000000..d3d9bd0 --- /dev/null +++ b/src/location/doc/src/place-caveats.qdocinc @@ -0,0 +1,21 @@ + The Places API is currently designed for only saving \a {core} details. Saving rich content like + images and reviews or details like supplier and rating is not a supported use case. Typically a manager + will generally ignore these fields upon save and may produce a warning message if they are populated. + + The Places API only supports saving of the following \e {core details}: + \list + \li name + \li place id + \li location + \li contact details + \li icon + \li categories (tag-like names to describe a place) + \li visibility scope + \endlist + + It is possible that providers may only support a subset of these. + See the \l {Qt Location#Plugin References and Parameters}{plugin documentation} for more + details. + + Saving of properties such as the rating, extended attributes, + images, reviews, editorials and supplier is explicitly not supported by the Places API. diff --git a/src/location/doc/src/place-crossref.qdocinc b/src/location/doc/src/place-crossref.qdocinc new file mode 100644 index 0000000..f0502f4 --- /dev/null +++ b/src/location/doc/src/place-crossref.qdocinc @@ -0,0 +1,7 @@ + \code + origin R/O manager(here) destination R/W manager (places_jsondb) + Save + Place id: ae246 ---> Place id: 0001 + Attribute type: x_provider Attribute type: x_id_here + Attribute value: here Attribute text value: ae246 + \endcode diff --git a/src/location/doc/src/place-definition.qdocinc b/src/location/doc/src/place-definition.qdocinc new file mode 100644 index 0000000..2398775 --- /dev/null +++ b/src/location/doc/src/place-definition.qdocinc @@ -0,0 +1,27 @@ +A place is a point of interest, it could be a favorite restaurant, a park or someone's home. +A QPlace object represents a place by acting as a container for various information about that place. + +This information can be divided into 2 broad classifications + +\list +\li Details +\li Rich content +\endlist + +The place details consist of properties of the place, such as the name, +location, contact information and so on. When a place is returned during a +search, these details are filled in. Sometimes in order to save bandwidth, +there are further details about the place that can be retrieved on an +individual place by place basis, if the user is interested. The +QPlace::detailsFetched() function can be queried to see if all available +details have been fetched, and if not, QPlaceManager::getPlaceDetails() can +be used to retrieve them. Precisely which details are populated during a +search and which need to be fetched individually may vary from provider to +provider. See \l {Qt Location#Plugin References and Parameters}{plugin documentation} for +more details. + +The rich content of a place consists of items such as images, reviews and +editorials. Potentially there may be many rich content items, so they are +treated separately from the place details. They can be retrieved in a paged +fashion via QPlaceManager::getPlaceContent(). If necessary, the content may +be assigned to a place so it can act as a convenient container. diff --git a/src/location/doc/src/places.qdoc b/src/location/doc/src/places.qdoc new file mode 100644 index 0000000..72e31c9 --- /dev/null +++ b/src/location/doc/src/places.qdoc @@ -0,0 +1,433 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page location-places-qml.html + \title QML Places API + + \section1 Overview + + The Places API allows users to discover places of interest and view + details about them, such as address and contact information. Some places may have + additional content associated with them, such as images and reviews. + The Places API also facilitates management of places and + categories, allowing users to save and remove them. + + \section1 Introductory Concepts + + \section2 Plugin + A \l Plugin is an abstraction for a backend. One \l Plugin might access places from a + REST server while another may access places from a local database. The following + instantiates a \l Plugin object by providing a name of "osm". The \l Plugin name + identifies which backend to choose from. Plugins may also be provided with a set of + \l {PluginParameter} {parameters}, which essentially takes the form of a set of + key-value pairs. The \l {PluginParameter} {parameters} that can be specified vary + among the different \l Plugin backends. For documentation on the possible \l + {PluginParameter} {parameters} and nuances of each \l Plugin, see the \l {Plugin + references and parameters}{Plugin References}. + + \snippet places_list/places_list.qml Initialize Plugin + + \note The HERE plugin must be supplied with some mandatory parameters as outlined + in the \l {Mandatory Parameters} {HERE Plugin} documentation. + + \section2 Models, Views and Delegates + The QML Places API is built around the notion of models, views and delegates. + + \table + \row + \li \b Model + \li A model holds data items and maintains their structure. + The model is also responsible for retrieving the items from a data source. + \row + \li \b View + \li A view is a visual container that displays the data and manages how visual + items are shown such as in a list or a grid. The view may also + be responsible for navigating the data, for example, scrolling through + the visual items during a flicking motion. + \row + \li \b Delegate + \li A delegate defines how individual data elements should appear + as visual items in the view. The models expose a set of data roles + and the delegate uses them to construct a visual item. The delegate + may also define behaviour such as an operation to invoke when a visual + item is clicked. + \endtable + + The Common Use Cases section below demonstrates concrete examples of how + these concepts fit together. + + \section1 Common Use Cases + + \section2 Searching for Places + Searching is accomplished via the \l PlaceSearchModel. The \l + {PlaceSearchModel::plugin} {plugin} property specifies which backend to + perform search operations against. Search parameters may be provided + through properties such as the \l {PlaceSearchModel::searchTerm} + {searchTerm} and \l {PlaceSearchModel::searchArea} {searchArea}. A search + operation can then be started by invoking the \l {PlaceSearchModel::update} + {update()} method. For simplicity, the snippet below invokes \l + {PlaceSearchModel::update} {update()} once construction of the model as + been completed, typically \l {PlaceSearchModel::update} {update()} would be + invoked in response to a user action such as a button click. While the + search operation is underway the \l {PlaceSearchModel::status} property + transitions into the \c Loading state and when successfully completed moves + into the \c Ready state. + + \snippet places_list/places_list.qml PlaceSearchModel + + \section2 Display Search Results using a ListView + A \l ListView can be used to show the search results found by the model. + It defines the visual region for where the results are shown, and in the + case below fills the entirety of its parent. The \l ListView has built in + behavior that enables the region to respond to flicking events and to + scroll appropriately. + + In the snippet below, the search model has been assigned to the ListView's + \l {ListView::model} {model} property. When the model is updated with new + results, the \l ListView is automatically updated to reflect the model's new + data items. + + A simple delegate has been bound to the \l {ListView}'s \l + {ListView::delegate} {delegate} property. The \l PlaceSearchModel exposes + a set of \l {PlaceSearchModel Roles} {roles} of which the \e title and \e + place roles have been used below, these are of type string and \l Place + respectively. Essentially for each data item that should be visible in the + view, the view invokes the delegate to create a visual representation of + the item. + + \table + \row + \li + \snippet places_list/places_list.qml Places ListView + \li + \inlineimage places_list.png + \endtable + + \note For simplicty's sake we have assumed that every search result is of + \l {Search Result Types} {type} \c PlaceSearchResult and so always have + access to the \e place role, other search result types may not have a + \e place role. + + See the \l {Places List(QML)} {Places List} example for full source code. + + \section2 Display Search Results using a MapItemView + Instead of a \l ListView, the \l PlaceSearchModel can be used in + conjunction with a \l MapItemView to display markers on a map. Firstly a + \l Map is used to define the visual region occupied by the map, in this + case it fills the entirety of its parent. Other properties are specified + such as the \l {Map::plugin} {plugin} providing the maps, and the map's \l + {Map::center} {center} and \l {Map::zoomLevel} {zoomLevel}. + + Inside the \l Map, a \l MapItemView is declared, where the \l + {MapItemView::model} {model} property has been set to the search model and + a \l {MapItemView::delegate} {delegate} consisting of a \l MapQuickItem is + used to display a marker image. A marker is shown for every place that + was found by the search model. The delegate uses the \e place role + to position the marker. + + \table + \row + \li + \snippet places_map/places_map.qml Places MapItemView + \li + \inlineimage places_map.png + \endtable + + \note For simplicty's sake we have assumed that every search result is of + \l {Search Result Types} {type} \c PlaceSearchResult and so always have + access to the \e place role, other search result types may not have a + \e place role. + + See the \l {Places Map(QML)} {Places Map} example for full source code. + + \section2 Fetching Place Details + In order to save bandwidth, sometimes a backend will only return places which + are partially populated with details. This can be checked with the + Place::detailsFetched property which indicates whether all availalable details + have been fetched or not. If not, the Place::getDetails() method can be invoked + to fetch the remaining details. + + \snippet declarative/places.qml Place fetchDetails + + \section2 Saving and Removing Places + Some backends may support saving and removing places. This can be done by + calling the Place::save() and Place::remove() methods respectively. Note + that in order to save a \l Place, a \l Plugin must be assigned to specify + which backend we are saving to. The \l {Place::status} {status} property will + transition into the \c Saving state while the save operation is happening and on + successful completion will move to the \c Ready state. The following + snippet shows how to save and remove a place using javascript. + + \snippet declarative/places.qml Place createAndSavePlace + \codeline + \snippet declarative/places.qml Place removePlace + + \section2 Learn More + The above snippets only exhibit a small subset of Places functionality. + Refer to the \l {Places Types} shown below for richer content such as \l {ImageModel} {images}, \l {ReviewModel} {reviews} etc, as well as more indepth descriptions and explanations. + + See also the \l {Places (QML)}{Places (QML)} example for a more comprehensive demonstration on + how to use the API. + + \section1 Places Types + \section2 Data Types + \annotatedlist qml-QtLocation5-places-data + + \section2 Models + \annotatedlist qml-QtLocation5-places-models +*/ + +/*! + \page location-places-cpp.html + \title Places (C++) + + \section1 Overview + + The Places API allows users to discover places/points of interest + and view details about them such as address and contact information; + some places may even have rich content such as images and reviews. + The Places API also facilitates management of places and + categories, allowing users to save and remove them. + + \section1 Place Definition + \include place-definition.qdocinc + + \section1 Common Operations + + \section2 Initializing a Manager + All places functionality is facilitated by a QPlaceManager instance. One must specify + a QGeoServiceProvider in order to create the QPlaceManager + + \snippet places/requesthandler.h Initialize Manager + + \section2 Discovery/Search + + In order to perform a search operation we simply create a QPlaceSearchRequest + and set the desired search parameters, such as a search term and search center. + + \snippet places/requesthandler.h Search for places cpp + + The request is an asynchronous operation so we need a slot to handle the + completion of the request. In the handler we check that there are no errors and that our search result + type is a place. If so we can then retrieve some of the core details of the + place. At the end of the slot, we delete the reply since they are for single use only. + + \snippet places/requesthandler.h Search for places handler cpp + + \b {Note:} Depending upon the plugin backend that was chosen, the search results may contain places + which have further details that can be fetched on a place by place basis. To fetch these other details + see \l {Fetching Place Details}. + + \section3 Recommendations + Recommendations can be retrieved by supplying a place id via QPlaceSearchRequest::setRecommendationId(). + Any places similar to the given place are retrieved. + + \section3 Paging + If the plugin supports paging, the limit parameter may be provided to the search request. + \snippet places/requesthandler.h Search paging + + \section2 Fetching Place Details + A place that has been returned from a search request may have more details + that can be fetched. The following demonstrates how to check if there + are further details and if so how to request them. + + \snippet places/requesthandler.h Details check + \dots + \dots + \snippet places/requesthandler.h Details handler cpp + + \section2 Fetching Rich Content + Rich content such as images and reviews is retrieved through the manager and then if required assigned to a place. + \snippet places/requesthandler.h Image request + + We can handle the content request as shown below. + \snippet places/requesthandler.h Image handler + + It is important to note that the results in the QPlaceContentReply, + is a QPlaceContent::Collection which is essentially a QMap. The key \c {int} in this case is the + index of the content, and the value is the content itself. Due to the way Content is implemented + it is possible to convert a content type as follows + \code + QPlaceImage image = content; //provided that 'content' has a type QPlace::ImageType + \endcode + + The usage of the QPlaceContent::Collection and the conversion between content and its subtypes means + that code for handling the mechanics of paging reviews, images and editorials can be easily shared. + + \section2 Search Suggestions + The retrieval of search term suggestions is very similar to performing a place search. A QPlaceSearchRequest + is used just like a place search, the only difference being that the search term is set to a + partially completed string. + + \snippet places/requesthandler.h Suggestion request + And when the request is done, we can use the reply to show the suggestions. + \snippet places/requesthandler.h Suggestion handler + + \target Saving a place cpp + \section2 Saving a Place + The saving of a new place is performed as follows, we create a QPlace instance + and populate it with information such as a name, address and coordinate. Once + done we can invoke QPlaceManager::savePlace() to begin a save operation. + \snippet places/requesthandler.h Save place pt1 + \dots + \snippet places/requesthandler.h Save place pt2 + + Once a place is saved the reply contains the new identifier for that place. + \snippet places/requesthandler.h Save place handler + + Note that to save an already \e existing place, the QPlace::placeId() must + be filled in with the correct identifier. Otherwise a new place will be created if empty or the + wrong place overwritten if the identifier is incorrect. + + When a place is saved, the QPlaceManager may emit QPlaceManager::placedAdded() or QPlaceManager::placeUpdated() + signals. However whether a manager does so or not is provider specific, managers accessing places + from a web service will typically not emit these signals while managers accessing places locally stored generally will. + + \section3 Caveats + \input place-caveats.qdocinc + + \section3 Saving Between Managers + When saving places between managers, there are a few things to be aware of. + Some fields of a place such as the id, categories and icons are manager specific entities + for example the categories in one manager may not be recognized in another. + Therefore trying to save a place directly from one manager to another is not possible. + + The typical approach is to use the QPlaceManager::compatiblePlace() function, + it creates a copy of a place, but only copies data that the manager supports. + Manager specific data such as the place identifier is not copied over. The new + copy is now suitable for saving into the manager. If the manager supports matching by alternative + identifiers, an alternative identifier attribute is assigned to the copy (see \l {Matching places between managers}) + + \snippet places/requesthandler.h Save to different manager + + \target Removing a place cpp + \section2 Removing a Place + The removal of a place is performed as follows: + \snippet places/requesthandler.h Remove place + \dots + \dots + \snippet places/requesthandler.h Remove place handler + + When a place is removed, the QPlaceManager may emit the QPlaceManager::placeRemoved() signal. Whether a + manager does so is provider specific. Managers accessing places from a web service will typically not emit + these signals, while managers accessing places stored locally generally will. + + \section2 Using Categories + + Categories are keywords that can describe a place. For example, 'park', 'theater', + 'restaurant'. A place could be described by many categories, it could be a park and a music venue and a ferry or bus stop. + + To use categories they must first be initialized. + \snippet places/requesthandler.h Initialize categories + \dots + \dots + \snippet places/requesthandler.h Initialize categories reply + + After the categories have been initialized we can then use these category functions. + \list + \li QPlaceManager::childCategories() + \li QPlaceManager::category() + \li QPlaceManager::parentCategoryId() + \li QPlaceManager::childCategoryIds(); + \endlist + + To retrieve the top level categories + we use the QPlaceManager::childCategories() function but do not provide + a category identifier. + + \snippet places/requesthandler.h Top level categories + + If we did provide an identifier then we could retrieve a category's children. + + \snippet places/requesthandler.h Child categories + + \section2 Saving a Category + The following shows how to save a category + \snippet places/requesthandler.h Save category + \dots + \dots + \snippet places/requesthandler.h Save category handler + + When a category is saved, the QPlaceManager may emit QPlaceManager::categoryAdded() or QPlaceManager::categoryUpdated() + signals. However whether a manager does so or not is provider specific, managers accessing places + from a web service will typically not emit these signals while managers accessing places locally stored generally will. + + + \section2 Removing a Category + Category removal is very similar to removing a place + \snippet places/requesthandler.h Remove category + \dots + \dots + \snippet places/requesthandler.h Remove category handler + + When a category is removed, the QPlaceManager may emit the QPlaceManager::categoryRemoved() signal. Whether a + manager does so is provider specific. Managers accessing places from a web service will typically not emit + these signals, while managers accessing places stored locally generally will. + + \section2 Matching Places Between Managers + Sometimes you may want to cross reference whether places from one manager match those from another manager. + Such a situation may arise where one manager provides read-only access to places (origin manager) while another second r/w + manager (destination manager) is used to save selected favorites from the first. During a search + of the origin manager we may want to know which ones have been 'favorited' into the destination manager and perhaps display + a customized favorite name rather than the original name. + + The matching mechanism can vary between managers, but is typically accomplished through an alternative identifier. + As part of the save process, the place identifier from the origin manager is saved as an alternative identifier attribute in the destination manager + (which can have its own place identifier scheme). In the following example, the origin manager is from the 'here' QGeoServiceProider, therefore + as part of the saving process an alternative identifier attribute, x_id_here, is set for the place saved into the destination manager + (when QPlaceManager::compatiblePlace() is called) + + \input place-crossref.qdocinc + + In order to perform the matching, we create a QPlaceMatchRequest and assign it the search results from the origin manager. + The QPlaceMatchRequest will be used on the destination manager to return corresponding places. We also specify + matching parameters which are key value pairs. As mentioned previously, this can vary depending on the manager but typically + the key is QPlaceMatchRequest::AlternativeId to indicate we are matching by alternative id, the value in this case would be + x_id_here which specifies which alternative identifier attribute we are using to do the matching. + + \snippet places/requesthandler.h Match places + \dots + \dots + \snippet places/requesthandler.h Match places handler + + \section1 Classes in Places + + \section2 Data Classes + \annotatedlist QtLocation-places-data + + \section2 Request Classes + \annotatedlist QtLocation-places-requests + + \target Places Reply Classes + \section2 Reply classes + \annotatedlist QtLocation-places-replies + + \section2 Manager Classes + \annotatedlist QtLocation-places-manager +*/ + diff --git a/src/location/doc/src/plugins/mapbox.qdoc b/src/location/doc/src/plugins/mapbox.qdoc new file mode 100644 index 0000000..6cd326b --- /dev/null +++ b/src/location/doc/src/plugins/mapbox.qdoc @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Canonical Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page location-plugin-mapbox.html +\title Qt Location Mapbox Plugin +\ingroup QtLocation-plugins + +\brief Uses Mapbox for location services. + +\section1 Overview + +This geo services plugin allows applications to access +\l {http://mapbox.com}{Mapbox} location based services using the Qt Location API. +The use of these services is governed by the \l {https://www.mapbox.com/tos}{Mapbox terms of service}. +An access token is required to use these services. +Data is provided by \l {https://www.mapbox.com/about/maps}{OpenStreetMap and others}. + +The Mapbox geo services plugin can be loaded by using the plugin key "mapbox". + +\section1 Parameters + +\section2 Mandatory parameters +The following table lists mandatory parameters that \e must be passed to the Mapbox plugin. +\table +\header + \li Parameter + \li Description +\row + \li mapbox.access_token + \li \l{https://www.mapbox.com/help/define-access-token/}{Access token} provided by Mapbox. +\row + \li mapbox.map_id + \li \l{https://www.mapbox.com/help/define-map-id/}{ID} of the Mapbox map to show. An example ID is "examples.map-zr0njcqy". +\endtable + +The Mapbox geo services plugin requires an access token and map ID to use the +Mapbox services. To create a Mapbox account visit +\l{https://www.mapbox.com/#signup}. + +\section2 Optional parameters +The following table lists optional parameters that can be passed to the Mapbox plugin. +\table +\header + \li Parameter + \li Description +\row + \li mapbox.format + \li Data format to download tiles in, available values are "png", "png32", + "png64", "png128", "png256", (PNG with full, 32, 64, 128 and 256 color palette) + "jpg70", "jpg80", "jpg90" (JPEG with 70%, 80% and 90% compression). + Defaults to "png". +\row + \li useragent + \li User agent string set when making network requests. +\endtable +*/ diff --git a/src/location/doc/src/plugins/nokia.qdoc b/src/location/doc/src/plugins/nokia.qdoc new file mode 100644 index 0000000..1ed5e6e --- /dev/null +++ b/src/location/doc/src/plugins/nokia.qdoc @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page location-plugin-here.html +\title Qt Location HERE Plugin +\ingroup QtLocation-plugins + +\brief Uses the relevant services provided by HERE. + +\section1 Overview + +Included with Qt Location is a geo services plugin which accesses the relevant HERE services +provided by HERE/Nokia. The use of these services is governed by the terms and conditions +available at \l {https://developer.here.com/terms-conditions}. + +Note that accepting the terms and conditions only applies those terms and conditions to the use of +the HERE geo services plugin and does not limit the use of the other geo services plugins that may +be included with Qt. + +The HERE geo services plugin can be loaded by using the plugin key "here". + +The online plugin uses the tiled map classes, which caches tile data in heap memory and texture +memory. + +\section1 Parameters + +\section2 Mandatory parameters +The following table lists mandatory parameters that \e must be passed to the HERE plugin. +\table +\header + \li Parameter + \li Description +\row + \li here.app_id + \li Client \e app_id part of the app_id/token pair used for authentication by all managers. +\row + \li here.token + \li Client \e token part of the app_id/token pair for the service used for authentication by all managers. +\endtable + +The HERE geo services plugin requires an application id and token pair to authenticate the +application with the HERE services. To obtain an application id and token pair visit +\l{https://developer.here.com/} + +\section2 Optional parameters +The following table lists optional parameters that can be passed to the HERE plugin. + +\note Since Qt 5.5 all parameters below must be prefixed with \c here. Previous versions did not require +a prefix. + +\table +\header + \li Parameter + \li Description +\row + \li here.proxy + \li Proxy server URL used by all managers. For usage of the system proxy just pass "system" as value. + + \note See the notes in \l{QNetworkProxyFactory::systemProxyForQuery()} for further information. +\row + \li here.mapping.host + \li Base map tile service URL used by mapping manager. +\row + \li here.mapping.host.aerial + \li Aerial map tile service URL used by mapping manager. For all satellite, hybrid and terrain schemes. +\row + \li here.mapping.cache.directory + \li Absolute path to map tile cache directory used as network disk cache. + + The default place for the cache is \c{QtLocation/here} directory in \l {QStandardPaths::writableLocation()} {QStandardPaths::writableLocation}(\l{QStandardPaths::GenericCacheLocation}). + On systems that have no concept of a shared cache, the application-specific \l{QStandardPaths::CacheLocation} is used instead. + +\row + \li here.mapping.cache.disk.size + \li Map tile disk cache size in bytes. Default size of the cache is 20MB. +\row + \li here.mapping.cache.memory.size + \li Map tile memory cache size in bytes. Default size of the cache is 3MB. +\row + \li here.mapping.cache.texture.size + \li Map tile texture cache size in bytes. Default size of the cache is 6MB. Note that the texture cache has a hard minimum size which depends on the size of the map viewport (it must contain enough data to display the tiles currently visible on the display). This value is the amount of cache to be used in addition to the bare minimum. +\row + \li here.geocoding.host + \li Geocoding service URL used by geocoding manager. +\row + \li here.routing.host + \li Routing service URL used by routing manager. +\row + \li here.places.host + \li Search service URL used by search manager. +\row + \li here.places.api_version + \li Version of the REST API used by the places manager. Currently versions 1 and 2 are + supported. The version 1 is deprecated and will not be part of the final Qt release. The default is version 2. +\endtable + +\section1 Parameter Usage Example + +The following two examples show how to create a HERE plugin instance with +parameters supplied for an application id and token, which is required for +authentication. + +\section2 QML + +\code +Plugin { + name: "here" + PluginParameter { name: "here.app_id"; value: "myapp" } + PluginParameter { name: "here.token"; value: "abcdefg12345" } +} +\endcode + +\section2 C++ + +\code +QMap params; +params["here.app_id"] = "myapp"; +params["here.token"] = "abcdefg12345"; + +QGeoServiceProvider *gsp = new QGeoServiceProvider("here", params); +\endcode + +\section1 Places +The HERE provider remotely accesses places (read-only) from a REST based server. The specific capabilities +and behaviours are outlined below: + +\section2 Capabilities +\table + \row + \li Storage + \li remote + \row + \li Read/Write + \li read-only + \row + \li Icons + \li yes + \row + \li Search term suggestions + \li yes + \row + \li Recommendations + \li yes + \row + \li Category structure + \li Hierarchical + \row + \li (Rich) Content images + \li yes + \row + \li (Rich) Content reviews + \li yes + \row + \li (Rich) Content editorials + \li yes + \row + \li All details fetched during search + \li no + \row + \li Paging offset index + \li no + \row + \li Paging limit + \li yes + \row + \li Distance relevance hint + \li no + \row + \li Lexical name relevance hint + \li no + \row + \li Extended Attributes + \li yes + \row + \li Notifications for added/removed places/categories + \li no + \row + \li visibility scopes + \li public + \row + \li favorites matching/(usable as favoritesPlugin) + \li no +\endtable + +\section2 Plugin Specific Behaviors and Limitations. +\section3 Search +The following list shows what core place data is returned during a place search: +\list +\li name +\li location +\li contact information +\li attribution +\li categories +\li rating +\li visibility +\endlist + +The following list shows further details that may be retrieved +via QPlaceManager::getDetails() +\list +\li supplier +\li extended attributes +\endlist + +\section3 Searching for Places +\section4 Search Term and Categories +The HERE plugin supports searching with a \e {search term} and \e {category or categories}, however +both are not supported simultaneously. + +\list + \li Valid usage: \e {search term} + \e {search center} + \li Valid usage: \e {category} + \e {search center} + \li Invalid usage: \e {search term} + \e {category} + \e {search center} +\endlist + +This limitation applies when using the HERE plugin with \l PlaceSearchModel and QPlaceManager::search(). + +\section4 Search Area +The HERE plugin only supports provision of a \e {search center} when searching for places via \l PlaceSearchModel +and QPlaceManager::search(). A search center can be provided via a bounding circle, however the +radius should be kept at the default value of -1. Typically a developer should not have to set the radius at all. +If a developer sets a radius, it is ignored by the plugin and the boundaries are not honored. + +In a similar manner only the center of a bounding box is taken into consideration when searching. The boundaries +of the box are not honored. + +A search center \e {must} be provided for all searches. + +\section4 Relevancy Hints +The HERE plugin does not support relevancy hints. Any relevancy hints supplied to +a search request are consequently ignored. + +\section3 Search Term suggestions +Only a partial \e {search term} and \e {search center} is supported when retrieving suggestions. +This limitation applies when using the HERE plugin with the \l PlaceSearchSuggestionModel and QPlaceManager::searchSuggestions(). + +Both search term and search center \e {must} be provided when retrieving search term suggestions. + +\section3 Recommendations +Only a given \e {place identifier} is supported as a parameter for a recommendations. No other parameters +such as limit, offset, and search area are supported. This limitation applies when using the +HERE plugin with \l PlaceSearchModel and QPlaceManager::search(). + +\section3 Extended Attributes +The supported set of attributes provided by the HERE plugin are not fixed and +may grow over time. Also the attributes provided may vary according to a place +by place basis, e.g one place may provide opening hours while another does not. +At the time of writing, it is known that some places provide \c openingHours +(QPlaceAttribute::OpeningHours) and \c payment (QPlaceAttribute::Payment) +methods but other attributes may be made available by the backend server. All +places provided by the plugin will have the \c x_provider +(QPlaceAttribute::Provider) attribute set to \c here. + +\section3 Restrictions of Usage - ExtendedAttributes and Content +The extended attributes and rich content of places are not permitted +to be saved. For QML this is related to \l Place::extendedAttributes, \l ImageModel, +\l ReviewModel, and \l EditorialModel. For C++ this relates to QPlace::extendedAttribute(), +QPlace::content() and QPlaceManager::getPlaceContent(). + +(Note that the HERE plugin is a read-only source of places and +does not support saving functionality at all.) +*/ diff --git a/src/location/doc/src/plugins/osm.qdoc b/src/location/doc/src/plugins/osm.qdoc new file mode 100644 index 0000000..a0f2bc2 --- /dev/null +++ b/src/location/doc/src/plugins/osm.qdoc @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Aaron McCarthy +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page location-plugin-osm.html +\title Qt Location Open Street Map Plugin +\ingroup QtLocation-plugins + +\brief Uses Open Street Map and related services. + +\section1 Overview + +This geo services plugin allows applications to access +\l {http://openstreetmap.org}{Open Street Map} location based services using the Qt Location API. + +Data, imagery and map information provided by \l {http://korona.geog.uni-heidelberg.de/}{OpenMapSurfer}, +\l {http://www.thunderforest.com/}{ThunderForest}, OpenStreetMap and contributors. The data is +available under the \l {http://www.opendatacommons.org/licenses/odbl}{Open Database License}. + +The Open Street Map geo services plugin can be loaded by using the plugin key "osm". + +\note Since Qt 5.6.2, the available map types offered by this plugin may change without notice depending on the +actual availability of each provider. To prevent these changes, either a different geo service plugin should be used, or the plugin +parameter \e osm.mapping.providersrepository.address should be set to a user-specified repository, in order to take full control over +(and accept \b responsibility for) selecting the provider that is used for each map type. + +\section1 Parameters + +\section2 Optional parameters +The following table lists optional parameters that can be passed to the Open Street Map plugin. + +\note Since Qt 5.5 all parameters below must be prefixed with \c osm. Previous versions did not require +a prefix. + +\table +\header + \li Parameter + \li Description +\row + \li osm.useragent + \li User agent string set when making network requests. This parameter should be set to a + value that uniquely identifies the application. Note that providers might block applications not setting this + parameter, leaving it to the stock plugin user agent (e.g., \l {http://wiki.openstreetmap.org/wiki/Nominatim_usage_policy}{Nominatim} + for geocoding) +\row + \li osm.mapping.custom.host + \li Url string set when making network requests to the tile server. This parameter should be set to a + valid server url with the correct osm api and the \l{Map::activeMapType} to the corresponding \l{MapType}.CustomMap. + The CustomMap will only be available if this parameter is set. + \note Setting the mapping.custom.host parameter to a new server renders the map tile cache useless for the old custommap style. +\row + \li osm.mapping.custom.mapcopyright + \li Custom map copryright string is used when setting the \l{Map::activeMapType} to \l{MapType}.CustomMap via urlprefix parameter. + This copyright will only be used when using the CustomMap from above. If empty no map copyright will be displayed for the custom map. +\row + \li osm.mapping.custom.datacopyright + \li Custom data copryright string is used when setting the \l{Map::activeMapType} to \l{MapType}.CustomMap via urlprefix parameter. + This copyright will only be used when using the CustomMap from above. If empty no data copyright will be displayed for the custom map. +\row + \li osm.mapping.providersrepository.address + \li The OpenStreetMap plugin retrieves the provider's information from a remote repository. This is done to prevent using hardcoded + servers by default, which may become unavailable. By default this information is fetched from \l {http://maps-redirect.qt.io} {maps-redirect.qt.io}. + Setting this parameter changes the provider repository address to a user-specified one, which must contain the files + \tt{street}, \tt{satellite}, \tt{cycle}, \tt{transit}, \tt{night-transit}, \tt{terrain} and \tt{hiking}, each of which must contain valid provider information. +\row + \li osm.mapping.providersrepository.disabled + \li By default, the OpenStreetMap plugin retrieves the provider's information from a remote repository to avoid a loss of service due to unavailability of hardcoded services. + The plugin, however, still contains fallback hardcoded provider data, in case the provider repository becomes unreachable. + Setting this parameter to \b true makes the plugin use the hardcoded urls only and therefore prevents the plugin from fetching provider data from the remote repository. +\row + \li osm.routing.host + \li Url string set when making network requests to the routing server. This parameter should be set to a + valid server url with the correct osrm API. If not specified the default \l {http://router.project-osrm.org/route/v1/driving/}{url} will be used. + \note The API documentation and sources are available at \l {http://project-osrm.org/}{Project OSRM}. + +\row + \li osm.routing.apiversion + \li String defining the api version of the (custom) OSRM server. Valid values are \b{v4} and \b{v5}. The default is \b{v5}. + This parameter should be set only if \tt{osm.routing.host} is set, and is an OSRM v4 server. + +\row + \li osm.geocoding.host + \li Url string set when making network requests to the geocoding server. This parameter should be set to a + valid server url with the correct osm API. If not specified the default \l {http://nominatim.openstreetmap.org/}{url} will be used. + \note The API documentation is available at \l {https://wiki.openstreetmap.org/wiki/Nominatim}{Project OSM Nominatim}. +\row + \li osm.places.host + \li Url string set when making network requests to the places server. + This parameter should be set to a valid server url with the correct osm API. + If not specified the default \l {http://nominatim.openstreetmap.org/search}{url} + will be used. + \note The API documentation is available at \l {https://wiki.openstreetmap.org/wiki/Nominatim}{Project OSM Nominatim}. +\endtable + +\section1 Parameter Usage Example + +The following example shows how to create an OSM plugin instance with +parameters supplied for an useragent, and if necessary, a custom server url plus the corresponding copyright information for the tile provider. +Additionally, it is possible to choose another routing server than the public osrm one. + +\section2 QML + +\code +Plugin { + name: "osm" + PluginParameter { name: "osm.useragent"; value: "My great Qt OSM application" } + PluginParameter { name: "osm.mapping.host"; value: "http://osm.tile.server.address/" } + PluginParameter { name: "osm.mapping.copyright"; value: "All mine" } + PluginParameter { name: "osm.routing.host"; value: "http://osrm.server.address/viaroute" } + PluginParameter { name: "osm.geocoding.host"; value: "http://geocoding.server.address" } +} +\endcode + +\section1 Other Plugin-specific Information + +\section2 Tile cache + +The tiles are cached in a \c{QtLocation/osm} directory in \l {QStandardPaths::writableLocation()}{QStandardPaths::writableLocation} +(\l{QStandardPaths::GenericCacheLocation}). On systems that have no concept of a shared cache, the application-specific +\l{QStandardPaths::CacheLocation} is used instead. +*/ diff --git a/src/location/doc/src/plugins/places-backend.qdoc b/src/location/doc/src/plugins/places-backend.qdoc new file mode 100644 index 0000000..b6de60e --- /dev/null +++ b/src/location/doc/src/plugins/places-backend.qdoc @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page location-places-backend.html +\title Places Backend + +\brief The Places backend is responsible for managing a places datastore whether + it is located remotely or locally + +\section1 Overview + +The QPlaceManager interface, provided to clients to allow access to place information, +depends directly on an implementation of QPlaceManagerEngine. The engine provides +the backend function implementations which are called by the manager. + +A places backend implementer needs to derive from QPlaceManagerEngine and provide implementations +for the virtual functions relevant for their backend. Most of these functions are asynchronous and +so implementers will also need to derive the appropriate \l {Places Reply Classes}{reply classes}. +The reply objects are responsible for managing an asynchronous request; they are used to notify +when a request is complete and hold the results of that request. QPlaceManagerEngine provides a +default implementation for all virtual functions. The default implementations for the asynchronous +functions return a reply that will emit the error() and finished() signals at the next iteration +through the event loop. + +\section1 Implementing/Inheriting Reply Objects +A reply object would be inherited as follows: +\snippet places/requesthandler.h Implement reply pt1 +\dots +\snippet places/requesthandler.h Implement reply pt2 + +The implementation of a QPlaceManagerEngine must ensure that any signals emitted by the reply +objects are delayed until the request functions have returned and the application code has a chance +to connect those signals to slots. The typical approach is to use \l {QMetaObject::invokeMethod()} +with a \l {Qt::QueuedConnection} to emit the signals. + +\snippet places/requesthandler.h Trigger done + +Note that the \c finished signals should always be emitted when a reply is complete, even if +an error has been encountered, that is, if there is an error, both the \c error and \c finished signals +should be emitted while if there is no error, only the \c finished signals are emitted. + +The protected functions of QPlaceSearchReply::setResults() and QPlaceSearchReply::setRequest() +are made publicly accessible so the plugin can assign results and requests. Because +these functions are not publically exported, accessibility is not so much of an issue. +An alternative would have been to declare a friend class in SearchReply. + +Typically the engine instance would be made the \c parent of the reply. If the developer +fails to discard the replies when finished, the engine can clean those upon destruction. +Commonly, the reply also has a pointer reference back to the engine, which may be used +to emit the QPlaceManagerEngine::finished() and QPlaceManagerEngine::error() signals. This is +just one of many ways the reply could be implemented. + +\section1 Icon URLs +Icon URLs are provided through the QPlaceManagerEngine::constructIconUrl() function. +The expected behaviour is that the engine will use the QPlaceIcon::parameters() +in order to construct an appropriate URL. When a QPlace object is returned +from the manager either from a search or a query to get place details, +it is expected the engine will correctly populate the parameters as necessary. + +The backend is free to choose what the parameter key and values are, however +if a backend only ever has one URL per icon it is recommended that the QPlaceIcon::SingleUrl +be used as the key. + +\section1 Categories +The categories of a manager engine are relatively static entities; for engines accessing +remote place datastores it may be desirable to cache the category structure rather than +querying a server every time QPlaceManagerEngine::initializeCategories() is called. +Depending on how dynamic the categories are, always downloading the freshest +set of categories may be more appropriate. + +\section1 Saving Places to the Manager +A place generally cannot be saved directly between managers as is because it contains manager specific data such as icons +and categories. In order to facilitate saving to one's own manager, engine implementers should implement +the QPlaceManagerEngine::compatiblePlace() function. This function returns a copy of the input place +with properties pruned or modified as necessary such that the copy can be saved into manager. + +Construction of a compatible place may involve ignoring certain properties from the +original place, for example if contact details are not supported, these are left out of the +compatible place. Other times it may involve modifying certain properties, for example +modifying the icon parameters to facilitate copying or downloading of the original +place's icon to a location that the backend can access. + +\section1 Cross-Referencing Places Between Managers +Sometimes a situation may arise where we wish to cross-reference and match places between managers. +Such a situation may arise where one manager provides read-only access to places (origin manager), while another second r/w +manager (destination manager) is used to save selected favorites from the first. During a search of the origin manager, we may want to +know which ones have been 'favorited' into the destination manager and perhaps display the customized favorite name +rather than the original name. + +\section2 Alternative Identifier Cross-Referencing +In order to accomplish cross-referencing, there needs to be a link between the original place and the favorited place +and this is typically handled via an alternative identifier attribute. The favorited place contains an alternative identifier attribute which has the identifier of the original place. + +\include place-crossref.qdocinc + +There are 3 prerequisites for implementing cross-referencing by alternative identifier. The first is that the origin manager must provide the x_provider attribute +with the value being the name of the manager's QGeoServiceProvider. The attribute label should be kept empty, indicating the attribute should not +be displayed to users. \note It is generally expected that all managers should set the \c x_provider attribute. + +The second is that QPlaceManager::compatiblePlace() of the destination manager use the \c x_provider attribute of the initial place +and set an alternative identifier attribute of the place to be saved. The key of the alternative identifier attribute is \c x_id_ and +the text value is the identifier of the initial place. The \c x_provider attribute should not be passed to the compatible place. When +it is saved, the x_provider of the saved place is considered to be the destination manager. + +The third is that QPlaceManager::matchingPlaces() of the destination manager accept the QPlaceMatchRequest::AlternativeId as a parameter key +and the alternative identifier attribute key as the value, in this case \c x_id_ would be the expected value. +This indicates that the identifiers of places in the QPlaceMatchRequest should be matched against the \c x_id_ alternative identifier attributes. + +Note that if the destination manager is to facilitate saving and cross-referencing from any arbitrary manager, it internally must +accommodate saving of arbitrary key value pairs since we cannot know the provider names before hand, nor can we know what structure the +ids will be. + +\section3 Other Methods of Linking +If an origin manager does not supply a place id, it may be necessary to provide some other means of cross-referencing/matching. +One approach might be to do so via the place coordinates, if the coordinate of a place in the origin manager is identical or close +to a place in the destination manager, there is a high likelihood that they are the same place. +In this case, the manager might implement QPlaceManager::matchingPlaces() to accept a QPlaceMatchRequest with a parameter key of 'proximity' +and a parameter value of the distance two places must be in order to detect a match. for example if an origin place and destination place are within 50m +of each other, they can be considered the same place. + +Generally however it is recommended that cross referencing be implemented via alternative identifiers as mentioned above. + +\section3 User Readable vs Non-User Readable Extended Attributes +If an attribute is not intended to be readable by end users, the label field should be kept +empty as an indicator of this fact. +*/ diff --git a/src/location/doc/src/qml-maps.qdoc b/src/location/doc/src/qml-maps.qdoc new file mode 100644 index 0000000..ecc2167 --- /dev/null +++ b/src/location/doc/src/qml-maps.qdoc @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \group qml-QtLocation5-maps + \title QML Maps Plugin + QML Support for the Qt Location API. +*/ + + +/*! +\page qml-location5-maps.html +\title QML Maps + +\brief Maps deals with maps, their contents and navigation. + +\section1 Overview + +The \l Map type allows the display of a map and placing objects within the map. +Various points of interest can be defined and added to the map for display. +Also the \l Map has features to control how the map is displayed. With the +Map item you can center the map, zoom, pinch and make the item flickable. + +The places to be added to the map are +\l {Maps and Navigation (QML)#Putting Objects on a Map (Map Overlay Objects)}{MapItems}. The item's +position is defined by a \l {coordinate} which includes latitude, +longitude and altitude. The item is then displayed automatically after it is added to the \l Map. + +\section2 Position on map + +All position APIs are part of the \l {QtPositioning} module. +The basic piece of position information is the \l {coordinate}. A +coordinate encapsulates data for the latitude, longitude and altitude of the location. Altitude is +in meters. It also has a method to determine distance to another +\l {coordinate}. The \l {coordinate} type may +also be held within a \l [QtPositioning]{Location} element, this will also have information +on a bounding box size to determine sufficient proximity to the location and a location address. + + +Here is an example of a client that uses a \l{PositionSource}{position source} +to center a \l{Map}{map} on the current position: + +\code + Rectangle { + + import QtPositioning 5.2 + import QtLocation 5.3 + ... + + Map { + id: map + // initialize map + ... + } + + PositionSource { + onPositionChanged: { + // center the map on the current position + map.center = position.coordinate + } + } + } +\endcode + +\section2 Geocoding + +\l {http://en.wikipedia.org/wiki/Geocoding}{Geocoding} is the derivation of +geographical coordinates (latitude and longitude) from other geographical references +to the locations. For example, this can be a street address. Reverse geocoding is also possible with +a street address being used to determine a geographical coordinate. Geocoding +is performed by using the \l [QML]{GeocodeModel} type. + +The following code examples are a small part of the \c map component in the +\l {Map Viewer (QML)}{Map Viewer (QML)} example. The snippets +demonstrate the declaration of the \l GeocodeModel component. + +In the snippet we see that the [QML]{GeocodeModel} contains the plugin +and two signal handlers. One for changes in status \l [QML]{GeocodeModel::status}{\c onStatusChanged} and +the other to update the centering of the Map object \l [QML]{GeocodeModel::locationsChanged}{\c onLocationsChanged}. + +\snippet mapviewer/map/MapComponent.qml geocodemodel0 +\codeline +\snippet mapviewer/map/MapComponent.qml geocodeview + +The geocoding features are called from a higher level piece of code. In this +snippet we see an \l [QML]{Address} object filled with the desired parameters. + +\snippet mapviewer/mapviewer.qml geocode0 + +The \l [QML]{Address} is later used in a query for the \l GeocodeModel to +process and determine the geographical \l [QML]{coordinate}{coordinates}. + +\snippet mapviewer/map/MapComponent.qml geocode1 + + +\section2 Navigation + +A very important function of the \l Map type is navigation +from one place to a destination with possible waypoints along the route. The +route will be divided up into a series of segments. At the end of each segment +is a vertex called a \e maneuver. The \e segments contain information about +the time and distance to the end of the segment. The \e maneuvers contain information +about what to do next, how to get onto the next segment, if there is one. So +a \e maneuver contains navigational information, for example "turn right now". + +To find a suitable route we will need to use a \l RouteQuery to define the +selection criteria and adding any required waypoints. +The \l RouteModel should return a list of \l {RouteSegment}s that defines the +route to the destination complete with navigation advice at the joins between +segments, called \l {RouteManeuver}s + +There are many options that you can add to the query to narrow the criteria. +The \l RouteQuery properties can include + +\table 60% + \row + \li \l {RouteQuery::}{numberAlternativeRoutes} + \li The number of alternative routes + \row + \li \l {RouteQuery::}{travelModes} + \li Travel modes + \row + \li \l {RouteQuery::}{routeOptimizations} + \li Required route optimizations + \row + \li \l {RouteQuery::}{segmentDetail} + \li Level of detail in segments + \row + \li \l {RouteQuery::}{maneuverDetail} + \li Level of detail in maneuvers between segments + \row + \li \l {RouteQuery::}{waypoints} + \li A list of waypoints + \row + \li \l {RouteQuery::}{excludedAreas} + \li A list of excluded areas that the route must not cross + \row + \li \l {RouteQuery::}{featureTypes} + \li Relevant map features, for example highway, ferry +\endtable + + +In the following example a default \l [QML]{RouteQuery} is declared within \l [QML]{RouteModel}. + +\snippet mapviewer/map/MapComponent.qml routemodel0 + +The user enters some information such as the starting point +of the route, some waypoints and the destination. All of these locations are +waypoints so the locations from start to finish will be entered as a sequence +of waypoints. Then other query properties can be set that may be specific to +this trip. + +\snippet mapviewer/map/MapComponent.qml routerequest0 +\snippet mapviewer/map/MapComponent.qml routerequest1 + +The \c routeInfoModel \l {Models and Views in Qt Quick#ListModel}{ListModel} is used to grab the +results of the query and construct a suitable list for display. +\snippet mapviewer/forms/RouteList.qml routeinfomodel0 +\snippet mapviewer/forms/RouteList.qml routeinfomodel1 +\snippet mapviewer/forms/RouteList.qml routeinfomodel3 + +The \l {Models and Views in Qt Quick#ListModel}{ListModel} \c routeInfoModel can be filled +with values using a code, that loops through the segments extracting the segment length, +instruction text and distance to the next instruction. The extracted data is formatted +for display as it is retrieved. + +\snippet mapviewer/forms/RouteList.qml routeinfomodel2 + +For more information on the example see the \l {Map Viewer (QML)}{Map Viewer (QML)} example. + + +\section2 Zoom, Pinch and Flickable + +The \l Map item also supports user interface interactions with the map using +tactile and mouse gestures. That is features such as swiping to pan, +pinching to zoom. + +Enabling and configuring pinch and flickable is easy within the \l Map type. + +\snippet mapviewer/map/MapComponent.qml top +\snippet mapviewer/map/MapComponent.qml mapnavigation +\snippet mapviewer/map/MapComponent.qml end + +Zoom can also be controlled by other objects like sliders, with binding +to the Map \l {QtLocation::Map::}{zoomLevel}. + +\section1 QML Types + +\section3 Maps +\annotatedlist qml-QtLocation5-maps + +\section3 Geocoding +\annotatedlist qml-QtLocation5-geocoding + +\section3 Routing +\annotatedlist qml-QtLocation5-routing + + + +\section1 Example + +The above snippets are taken from the \l {Map Viewer (QML)}{Map Viewer (QML)} example. + +*/ + diff --git a/src/location/doc/src/qtlocation-changes.qdoc b/src/location/doc/src/qtlocation-changes.qdoc new file mode 100644 index 0000000..1330412 --- /dev/null +++ b/src/location/doc/src/qtlocation-changes.qdoc @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page qtlocation-changes.html +\title Qt Location QML API changes since 5.4 +\brief Information about the Qt Location QML API changes since 5.4 + +This page lists the QtLocation QML API changes since the first Qt Location Technology Preview +in Qt 5.4. Since Qt 5.6 this API is considered to be final and subsequent releases will +not break the given API anymore. + +\note The public C++ API remained binary compatible since Qt 5.4. + +\b{\l{QtLocation::Map}{Map} Component} +\list + \li removed wheelAngleChanged() signal + \li added \l[QML]{QtLocation::Map::}{error} property + \li added \l[QML]{QtLocation::Map::}{errorString} property + \li added \l[QML]{QtLocation::Map::}{copyrightLinkActivated} signal + \li removed toScreenPosition() method + \li added \l[QML]{QtLocation::Map::}{fromCoordinate}() method + \li replaced cameraStopped() method with \l[QML]{QtLocation::Map::}{prefetchData} method + \li replaced fitViewportToGeoShape() method with \l[QML]{QtLocation::Map::}{visibleRegion} property + \li added \l[QML]{QtLocation::Map::}{color} property + \li added \l[QML]{QtLocation::Map::}{clearData} method +\endlist + +\b{\l{QtLocation::MapGestureArea}{MapGestureArea} Component} +\list + \li removed movementStopped() signal + \li replaced isPanActive and isPinchActive properties with \l[QML]{QtLocation::MapGestureArea::}{panActive} + and \l[QML]{QtLocation::MapGestureArea::}{pinchActive} properties + \li replaced activeGestures with \l[QML]{QtLocation::MapGestureArea::}{acceptedGestures} + \li replaced MapGestureArea.ZoomGesture with \l[QML]{QtLocation::MapGestureArea::acceptedGestures}{MapGestureArea.PinchGesture} + \li removed properties panEnabled and pinchEnabled, please use \l[QML]{QtLocation::MapGestureArea::}{acceptedGestures} instead +\endlist + +\b{\l{QtLocation::MapPolyline}{MapPolyline} Component} +\list + \li added \l[QML]{QtLocation::MapPolyline::}{containsCoordinate} method + \li added \l[QML]{QtLocation::MapPolyline::}{coordinateAt} method + \li added \l[QML]{QtLocation::MapPolyline::}{insertCoordinate} method + \li added \l[QML]{QtLocation::MapPolyline::}{replaceCoordinate} method + \li added \l[QML]{QtLocation::MapPolyline::}{removeCoordinate} method +\endlist + +\b Geoservice's plugin parameters +\list + \li the \l{Qt Location HERE Plugin}{HERE} plugin uses the \c here prefix in front of each plugin parameter name + \li the \l{Qt Location Open Street Map Plugin}{OSM} plugin uses the \c osm prefix in front of each plugin parameter name +\endlist +*/ diff --git a/src/location/doc/src/qtlocation-cpp.qdoc b/src/location/doc/src/qtlocation-cpp.qdoc new file mode 100644 index 0000000..82ac766 --- /dev/null +++ b/src/location/doc/src/qtlocation-cpp.qdoc @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page qtlocation-cpp.html +\title Qt Location C++ API +\brief Information about the Qt Location C++ API + +The Location API provides a library for mapping, navigation and place information. + +\tableofcontents + +The Qt Location API provides the developer with a set of functions to interact +with maps, navigational data and places of interest. This is particularly useful +when associated with current position information which can be retrieved via the +\l QtPositioning module. + +With the Maps API we can associate a position with a map in various formats supplied by a backend. +Then the Places API could be used to populate places on the Map or even +specify the current position as a place of interest and associate it with +an icon, contact details and other information. + +The following table provides links to more detailed information on sections of the +Qt Location C++ API. + +\table + \row + \li \l {Maps and Navigation (C++)}{Maps and Navigation} + \li Displaying maps and finding routes. + \row + \li \l {Places (C++)} {Places} + \li Searching for and managing points of interest. + \row + \li \l {Qt Location GeoServices}{Geoservices Plugin Implementation} + \li Implement new geoservices and positioning plugins. +\endtable + + +\section1 Geoservice Provider Classes + + \annotatedlist QtLocation-common + + +\section1 Maps and Navigation Classes + +Currently it is not possible to interact with maps data via C++. The only available interface is the \l {Maps and Navigation (QML)} API. + + \annotatedlist QtLocation-maps + + \annotatedlist QtLocation-routing + + \annotatedlist QtLocation-geocoding + + +\section1 Places Classes + + \annotatedlist QtLocation-places + + +\section1 Geoservices and Positioning Plugin Classes + + \annotatedlist QtLocation-impl + +*/ + diff --git a/src/location/doc/src/qtlocation-examples.qdoc b/src/location/doc/src/qtlocation-examples.qdoc new file mode 100644 index 0000000..6155a6f --- /dev/null +++ b/src/location/doc/src/qtlocation-examples.qdoc @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \group qtlocation-examples + \title Qt Location Examples + \brief Examples for the Qt Location module + \ingroup all-examples + \ingroup qtlocation + + These examples show a range of different uses for \l{Qt Location}, + such as displaying a map within a QML user interface, implementing basic routing and + place search, as well as integrating positioning data types. + + These examples can work with any of the available geo services plugins. However, some plugins may + require additional \l {QtLocation::PluginParameter}{plugin parameters} in order to function correctly. + The default plugin used by these examples is \l {Qt Location Open Street Map Plugin}, which does not + require any parameters. + +*/ + diff --git a/src/location/doc/src/qtlocation-geoservices.qdoc b/src/location/doc/src/qtlocation-geoservices.qdoc new file mode 100644 index 0000000..008f98a --- /dev/null +++ b/src/location/doc/src/qtlocation-geoservices.qdoc @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page qtlocation-geoservices.html +\title Qt Location GeoServices +\brief Implementing Qt Location GeoService plugins + +The Qt Location provides the majority of its functionality through GeoService plugins. This +document outlines how to develop a new GeoService plugin. + +\section1 Plugin Description + +Each plugin is described by a json file. The json describes the plugins capabilities and +version. Below is an example of a json file used by the OpenStreenMap plugin: + + +\quotefile ../../../plugins/geoservices/osm/osm_plugin.json + +The entries have the following meaning: + +\table + \header + \li Key + \li Description + \row + \li Keys + \li The unique name/key of the plugin. Each GeoService plugin must have a unique name. + \row + \li Provider + \li The provider name of the services. Multiple plugins may have the same name. + In such cases the Version string will be used to further distinguish the plugins. + \row + \li Experimental + \li Marks the service plugin as experimental. API developers may choose to ignore + such plugins when instanciating \l QGeoServiceProvider::QGeoServiceProvider(). + \row + \li Version + \li The plugin version. If multiple plugins have the same provider name, the plugin + with the higest version will be used. + \row + \li Features + \li List of features provided by the plugin/service. Each feature is a string + representation of the corresponding features in \l QGeoServiceProvider. For more + details see \l QGeoServiceProvider::routingFeatures(), + \l QGeoServiceProvider::geocodingFeatures() and \l QGeoServiceProvider::placesFeatures(). + +\endtable + +\section1 Implementing Plugins + +A plugin implementer needs to subclass QGeoServiceProviderFactory and as +many of the ManagerEngine classes as they want to provide implementations for. + +Subclassing QGeoServiceProviderFactory will only involves overriding of one of the following +methods: + +\list + \li \l QGeoServiceProviderFactory::createGeocodingManagerEngine() + \li \l QGeoServiceProviderFactory::createRoutingManagerEngine() + \li \l QGeoServiceProviderFactory::createPlaceManagerEngine() +\endlist + +If a plugin does not provide an engine the relevant function should return 0. + +\annotatedlist QtLocation-impl + +*/ diff --git a/src/location/doc/src/qtlocation-qml.qdoc b/src/location/doc/src/qtlocation-qml.qdoc new file mode 100644 index 0000000..37d21dc --- /dev/null +++ b/src/location/doc/src/qtlocation-qml.qdoc @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \qmlmodule QtLocation 5.3 + \title Qt Location QML Types + \ingroup qmlmodules + \brief Provides QML types for mapping and location information + +\section1 Overview + +Provided that a position has been obtained, this module can add a +\l{QtLocation::Map}{Map} with Places of Interest (POI) and +\l{QtLocation::Place}{Places}. The user can be made aware of nearby features +and related information, displayed graphically. Features on the \l Map can be +places of business, entertainment, and so on. They may include paths, roads, +or forms of transport, enabling navigation optimization and assistance. + +To perform navigation we need \l {Route}s from start to destination. These routes +are made up of segments, where each \l {QtLocation::RouteSegment}{RouteSegment} +can be considered a navigation subtask: drive 100 meters, turn left. The beginning and +end of each segment is a \e waypoint, that is, one part of our journey. + +A typical use case for the API is a user looking for a particular type of +place, say a restaurant. The user could enter a search string into the map +application and respond to a list or display of results for restaurants +"near" the device. The application could then be used to navigate to the +restaurant using an optimized route that is aware of features in the +environment that can help or hinder the journey. The navigation then +proceeds with the user's progress monitored by means of the current +\l Location. In the context of this API the map application would be aware +of the location and size of various places and the location of the user. +Plugins would supply the data required by the application to determine routes +and navigation instructions. The \l Place types would hold information about the +destination and surrounding objects including displayable representations. +The \l Map type would enable this information to be displayed, panned, +zoomed and so on. The \l Route would be determined by a plugin with each +\l RouteSegment holding the navigation instructions guided by the updated +current \l Location. + +\l {Plugin}s are a means of specifying which location-based service to use. For +example, a plugin may allow connection to a provider's service that provides +geocoding and routing information, which can be consumed by the application. +There may be various GeoServices plugins for various tasks with some plugins +providing more than one service. One QML \l Plugin must be created for each +required GeoService plugin. Plugins are required for maps, routing and geocoding, +and places, however the default plugin handles all four of these services. A plugin may +require online access or may support on-board maps and data. + +\note Plugins may not provide features such as paging or relevance hints. + +The following links provide more detailed information about maps and places: + +\table + \row + \li \l {Maps and Navigation (QML)}{Maps and Navigation} + \li Displaying maps and finding routes. + \row + \li \l {QML PLaces API} {Places} + \li Searching for and managing points of interest. +\endtable + +\section1 Common QML Types + +\annotatedlist qml-QtLocation5-common + +\section1 Maps QML Types + +\annotatedlist qml-QtLocation5-maps + +\section1 Navigation and Routing QML Types + +\annotatedlist qml-QtLocation5-routing + +\section1 Geocoding QML Types + +\annotatedlist qml-QtLocation5-geocoding + +\section1 Places QML Types + +\annotatedlist qml-QtLocation5-places + +\section1 Alphabetical Listing of All QML Types +*/ diff --git a/src/location/doc/src/qtlocation.qdoc b/src/location/doc/src/qtlocation.qdoc new file mode 100644 index 0000000..7979bbe --- /dev/null +++ b/src/location/doc/src/qtlocation.qdoc @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + + + + +/*! + \module QtLocation + \title Qt Location C++ Classes + \ingroup modules + \qtvariable location + + \brief Provides C++ interfaces to retrieve location and navigational + information. + + The C++ API provides access to geocoding and navigation information, + and also place search. Use the \l{Maps and Navigation (QML)}{QML} + API to render this information on an interactive map that supports + touch gestures, overlays, and so on. + + Include the appropriate header in your C++ code. For example, + applications using routes can include: + + \code #include \endcode + + Add the \e location keyword in the project file to link against the + Qt Location library: + + \code QT += location \endcode + + See more in the \l{Qt Location}{Qt Location Overview}. + +*/ + + + +/*! +\page qtlocation-index.html +\title Qt Location +\brief Provides QML and C++ interfaces to create location-aware +applications. +\ingroup technology-apis + +The Qt Location API helps you create viable mapping solutions using the data +available from some of the popular location services. + +\section1 Overview + +The Qt Location API enables you to: +\list + \li access and present map data, + \li support touch gesture on a specific area of the map, + \li query for a specific geographical location and route, + \li add additional layers on top, such as polylines and circles, + \li and search for places and related images. +\endlist + +\section1 Getting Started + +To load the Qt Location module, add the following statement to your .qml files + +\code + import QtPositioning 5.5 + import QtLocation 5.6 +\endcode + +The QtLocation QML module depends on the QtPositioning QML module. +Therefore every QML application that imports the QtLocation QML module must always +import the QtPositioning module as well. + +For C++ projects include the header appropriate for the current use case, +for example applications using routes may use + +\code #include \endcode + +The .pro file should have the \e location keyword added + +\code QT += location \endcode + +\section2 Submodules + +The API is split into sub-modules, which provide QML and C++ interfaces for +specific purposes. They focus mainly on Map and Place information. The required +position data can be retrieved using the \l {QtPositioning} module. + +\section3 Places + +The Places submodule is the natural complement to Positioning, providing a +source of geographical data about Places of Interest (POI). Besides the source +information, the API provides information about the location, size, and +other related information about a POI. The Places API can also +retrieve images, reviews, and other content related to a place. + +\table +\row + \li Places introduction: + \li \l{QML Places API}{for QML} + \li \l{Places (C++)}{for C++} +\endtable + +\section3 Maps and Navigation + +The module provides the QML and C++ alternatives for maps and navigation. +The C++ alternative provides utility classes to get geocoding (finding a geographic +coordinate from a street address) and navigation (including driving and walking +directions) information, whereas its QML counterpart provides UI components to render +the information. + +\table +\row +\li Maps and Navigation introduction: + \li \l{Maps and Navigation (QML)}{for QML} + \li \l{Maps and Navigation (C++)}{for C++} +\endtable + +\section1 API References and Examples + +The following are lists of the classes and UI components provided by the module, +with example applications to demonstrate their usage: + +\table + \row + \li \l {Qt Location QML Types}{QML API Reference} + \li Full list of QML components in the Qt Location API + \row + \li \l {Qt Location C++ API}{C++ API Reference by domain} + \li Full list of C++ classes and methods of the Qt Location APIs sorted by domain + \row + \li \l {Qt Location C++ Classes}{C++ API Reference} + \li Full list of C++ classes and methods of the Qt Location APIs + \row + \li \l {Qt Location Examples}{Example Apps} + \li Examples demonstrating use of the Qt Location APIs + \row + \li \l {QML Maps}{Maps and Navigation Tutorial} + \li Tutorial introducing the QML Maps Types +\endtable + +\section1 Plugin References and Parameters + +Information about plugins, important notes on their usage, parameters that can +be provided to influence their behavior. + +\annotatedlist QtLocation-plugins + +\section2 Implementing New Back-Ends and Porting + +For systems integrators and distributors, information related to making +Qt Location available for a new platform. + +\table + \row + \li \l {Qt Location GeoServices}{GeoServices} + \li Information about the Qt Location GeoServices plugins + \row + \li \l {Places Backend} {Places} + \li Information for places backend implementors + \row + \li \l {Qt Location QML API changes since 5.4}{API changes} + \li Information about QML API changes since 5.4 +\endtable + +*/ diff --git a/src/location/doc/src/src.pro b/src/location/doc/src/src.pro new file mode 100644 index 0000000..fe90f13 --- /dev/null +++ b/src/location/doc/src/src.pro @@ -0,0 +1,9 @@ +TEMPLATE = subdirs +SUBDIRS += snippets + +OTHER_FILES = \ + *.qdoc \ + *.qdocinc \ + plugins/*.qdoc \ + examples/*.qdoc \ + imports/*.qdoc diff --git a/src/location/location.pro b/src/location/location.pro new file mode 100644 index 0000000..052a197 --- /dev/null +++ b/src/location/location.pro @@ -0,0 +1,22 @@ +TARGET = QtLocation +QT = core-private positioning-private + +MODULE_PLUGIN_TYPES = \ + geoservices + +QMAKE_DOCS = $$PWD/doc/qtlocation.qdocconf +OTHER_FILES += doc/src/*.qdoc # show .qdoc files in Qt Creator + +PUBLIC_HEADERS += \ + qlocation.h \ + qlocationglobal.h + +SOURCES += \ + qlocation.cpp + +include(maps/maps.pri) +include(places/places.pri) + +HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS + +load(qt_module) diff --git a/src/location/maps/maps.pri b/src/location/maps/maps.pri new file mode 100644 index 0000000..db43962 --- /dev/null +++ b/src/location/maps/maps.pri @@ -0,0 +1,99 @@ + +INCLUDEPATH += maps + +QT += gui quick + +PUBLIC_HEADERS += \ + maps/qgeocodereply.h \ + maps/qgeocodingmanagerengine.h \ + maps/qgeocodingmanager.h \ + maps/qgeomaneuver.h \ + maps/qgeoroute.h \ + maps/qgeoroutereply.h \ + maps/qgeorouterequest.h \ + maps/qgeoroutesegment.h \ + maps/qgeoroutingmanagerengine.h \ + maps/qgeoroutingmanager.h \ + maps/qgeoserviceproviderfactory.h \ + maps/qgeoserviceprovider.h + +PRIVATE_HEADERS += \ + maps/qgeocameracapabilities_p.h \ + maps/qgeocameradata_p.h \ + maps/qgeocameratiles_p.h \ + maps/qgeocodereply_p.h \ + maps/qgeocodingmanagerengine_p.h \ + maps/qgeocodingmanager_p.h \ + maps/qgeomaneuver_p.h \ + maps/qgeotiledmapscene_p.h \ + maps/qgeotilerequestmanager_p.h \ + maps/qgeomap_p.h \ + maps/qgeomap_p_p.h \ + maps/qgeotiledmap_p.h \ + maps/qgeotiledmap_p_p.h \ + maps/qgeotilefetcher_p.h \ + maps/qgeotilefetcher_p_p.h \ + maps/qgeomappingmanager_p.h \ + maps/qgeomappingmanager_p_p.h \ + maps/qgeomappingmanagerengine_p.h \ + maps/qgeomappingmanagerengine_p_p.h \ + maps/qgeotiledmappingmanagerengine_p.h \ + maps/qgeotiledmappingmanagerengine_p_p.h \ + maps/qgeomaptype_p.h \ + maps/qgeomaptype_p_p.h \ + maps/qgeoroute_p.h \ + maps/qgeoroutereply_p.h \ + maps/qgeorouterequest_p.h \ + maps/qgeoroutesegment_p.h \ + maps/qgeoroutingmanagerengine_p.h \ + maps/qgeoroutingmanager_p.h \ + maps/qgeoserviceprovider_p.h \ + maps/qabstractgeotilecache_p.h \ + maps/qgeofiletilecache_p.h \ + maps/qgeotiledmapreply_p.h \ + maps/qgeotiledmapreply_p_p.h \ + maps/qgeotilespec_p.h \ + maps/qgeotilespec_p_p.h \ + maps/qgeorouteparser_p.h \ + maps/qgeorouteparser_p_p.h \ + maps/qgeorouteparserosrmv5_p.h \ + maps/qgeorouteparserosrmv4_p.h \ + maps/qcache3q_p.h + +SOURCES += \ + maps/qgeocameracapabilities.cpp \ + maps/qgeocameradata.cpp \ + maps/qgeocameratiles.cpp \ + maps/qgeocodereply.cpp \ + maps/qgeocodingmanager.cpp \ + maps/qgeocodingmanagerengine.cpp \ + maps/qgeomaneuver.cpp \ + maps/qgeotilerequestmanager.cpp \ + maps/qgeomap.cpp \ + maps/qgeomappingmanager.cpp \ + maps/qgeomappingmanagerengine.cpp \ + maps/qgeotiledmappingmanagerengine.cpp \ + maps/qgeotilefetcher.cpp \ + maps/qgeomaptype.cpp \ + maps/qgeoroute.cpp \ + maps/qgeoroutereply.cpp \ + maps/qgeorouterequest.cpp \ + maps/qgeoroutesegment.cpp \ + maps/qgeoroutingmanager.cpp \ + maps/qgeoroutingmanagerengine.cpp \ + maps/qgeoserviceprovider.cpp \ + maps/qgeoserviceproviderfactory.cpp \ + maps/qabstractgeotilecache.cpp \ + maps/qgeofiletilecache.cpp \ + maps/qgeotiledmapreply.cpp \ + maps/qgeotilespec.cpp \ + maps/qgeotiledmap.cpp \ + maps/qgeotiledmapscene.cpp \ + maps/qgeorouteparser.cpp \ + maps/qgeorouteparserosrmv5.cpp \ + maps/qgeorouteparserosrmv4.cpp + + + + + diff --git a/src/location/maps/qabstractgeotilecache.cpp b/src/location/maps/qabstractgeotilecache.cpp new file mode 100644 index 0000000..739123d --- /dev/null +++ b/src/location/maps/qabstractgeotilecache.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qabstractgeotilecache_p.h" + +#include "qgeotilespec_p.h" + +#include "qgeomappingmanager_p.h" + +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QSet) + +QT_BEGIN_NAMESPACE + +QGeoTileTexture::QGeoTileTexture() + : textureBound(false) {} + +QGeoTileTexture::~QGeoTileTexture() +{ +} + +QAbstractGeoTileCache::QAbstractGeoTileCache(QObject *parent) + : QObject(parent) +{ + qRegisterMetaType(); + qRegisterMetaType >(); + qRegisterMetaType >(); +} + +QAbstractGeoTileCache::~QAbstractGeoTileCache() +{ +} + +void QAbstractGeoTileCache::printStats() +{ +} + +void QAbstractGeoTileCache::handleError(const QGeoTileSpec &, const QString &error) +{ + qWarning() << "tile request error " << error; +} + +void QAbstractGeoTileCache::setMaxDiskUsage(int diskUsage) +{ + Q_UNUSED(diskUsage); +} + +int QAbstractGeoTileCache::maxDiskUsage() const +{ + return 0; +} + +int QAbstractGeoTileCache::diskUsage() const +{ + return 0; +} + +void QAbstractGeoTileCache::setMaxMemoryUsage(int memoryUsage) +{ + Q_UNUSED(memoryUsage); +} + +int QAbstractGeoTileCache::maxMemoryUsage() const +{ + return 0; +} + +int QAbstractGeoTileCache::memoryUsage() const +{ + return 0; +} + +QString QAbstractGeoTileCache::baseCacheDirectory() +{ + QString dir; + + // Try the shared cache first and use a specific directory. (e.g. ~/.cache/QtLocation) + // If this is not supported by the platform, use the application-specific cache + // location. (e.g. ~/.cache//QtLocation) + dir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); + + if (!dir.isEmpty()) { + // The shared cache may not be writable when application isolation is enforced. + static bool writable = false; + static bool writableChecked = false; + if (!writableChecked) { + writableChecked = true; + QDir::root().mkpath(dir); + QFile writeTestFile(QDir(dir).filePath(QStringLiteral("qt_cache_check"))); + writable = writeTestFile.open(QIODevice::WriteOnly); + if (writable) + writeTestFile.remove(); + } + if (!writable) + dir = QString(); + } + + if (dir.isEmpty()) + dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + + if (!dir.endsWith(QLatin1Char('/'))) + dir += QLatin1Char('/'); + + dir += QLatin1String("QtLocation/"); + + return dir; +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qabstractgeotilecache_p.h b/src/location/maps/qabstractgeotilecache_p.h new file mode 100644 index 0000000..141aa9b --- /dev/null +++ b/src/location/maps/qabstractgeotilecache_p.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QABSTRACTGEOTILECACHE_P_H +#define QABSTRACTGEOTILECACHE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include +#include +#include "qcache3q_p.h" +#include +#include +#include + +#include "qgeotilespec_p.h" +#include "qgeotiledmappingmanagerengine_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoMappingManager; + +class QGeoTile; +class QAbstractGeoTileCache; + +class QThread; + +/* This is also used in the mapgeometry */ +class Q_LOCATION_EXPORT QGeoTileTexture +{ +public: + + QGeoTileTexture(); + ~QGeoTileTexture(); + + QGeoTileSpec spec; + QImage image; + bool textureBound; +}; + +class Q_LOCATION_EXPORT QAbstractGeoTileCache : public QObject +{ + Q_OBJECT +public: + virtual ~QAbstractGeoTileCache(); + + virtual void setMaxDiskUsage(int diskUsage); + virtual int maxDiskUsage() const; + virtual int diskUsage() const; + + virtual void setMaxMemoryUsage(int memoryUsage); + virtual int maxMemoryUsage() const; + virtual int memoryUsage() const; + + virtual void setMinTextureUsage(int textureUsage) = 0; + virtual void setExtraTextureUsage(int textureUsage) = 0; + virtual int maxTextureUsage() const = 0; + virtual int minTextureUsage() const = 0; + virtual int textureUsage() const = 0; + virtual void clearAll() = 0; + + virtual QSharedPointer get(const QGeoTileSpec &spec) = 0; + + virtual void insert(const QGeoTileSpec &spec, + const QByteArray &bytes, + const QString &format, + QGeoTiledMappingManagerEngine::CacheAreas areas = QGeoTiledMappingManagerEngine::AllCaches) = 0; + virtual void handleError(const QGeoTileSpec &spec, const QString &errorString); + + static QString baseCacheDirectory(); + +protected: + QAbstractGeoTileCache(QObject *parent = 0); + + virtual void printStats() = 0; +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTGEOTILECACHE_P_H diff --git a/src/location/maps/qcache3q_p.h b/src/location/maps/qcache3q_p.h new file mode 100644 index 0000000..debce5d --- /dev/null +++ b/src/location/maps/qcache3q_p.h @@ -0,0 +1,471 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCACHE3Q_H +#define QCACHE3Q_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +template +class QCache3QDefaultEvictionPolicy +{ +protected: + /* called just before a key/value pair is about to be _evicted_ */ + void aboutToBeEvicted(const Key &key, QSharedPointer obj); + /* called just before a key/value pair is about to be removed, by + * clear(), remove() or by the destructor (which calls clear) */ + void aboutToBeRemoved(const Key &key, QSharedPointer obj); +}; + +template +void QCache3QDefaultEvictionPolicy::aboutToBeEvicted(const Key &key, QSharedPointer obj) +{ + Q_UNUSED(key); + Q_UNUSED(obj); +} + +template +void QCache3QDefaultEvictionPolicy::aboutToBeRemoved(const Key &key, QSharedPointer obj) +{ + Q_UNUSED(key); + Q_UNUSED(obj); +} + +/* + * QCache3Q + * + * A cache template class for managing QSharedPointers to objects with an + * associated key. It's a lot like QCache, but uses an alternative algorithm + * '3Q' -- which is the '2Q' algorithm plus an extra queue for previously popular + * but evicted nodes, and a 'ghost' list of recent evictions to make a better + * placement choice if they are requested again. + * + * New nodes enter the cache on the "newbies" queue, which is evicted LRA + * (least-recently-added). If a newbie is popular enough (it has been requested + * more than promoteAt times), it will be promoted to a "regular". Regulars + * are evicted LRU (least-recently-used). If a regular is under consideration + * for eviction, its popularity is compared to the mean popularity of the whole + * regulars queue. If it is greater, it is instead moved to the "hobos" queue. + * The "hobos" queue is also evicted LRU, but has a maximum size constraint + * so eviction from it is less likely than from the regulars. + * + * Tweakables: + * * maxCost = maximum total cost for the whole cache + * * minRecent = minimum size that q1 ("newbies") has to be before eviction + * from it takes place + * * maxOldPopular = maximum size that q3 ("hobos") can reach before eviction + * from it takes place + * * promoteAt = minimum popularity necessary to promote a node from + * "newbie" to "regular" + */ +template > +class QCache3Q : public EvPolicy +{ +private: + class Queue; + class Node + { + public: + inline explicit Node() : q(0), n(0), p(0), pop(0), cost(0) {} + + Queue *q; + Node *n; + Node *p; + Key k; + QSharedPointer v; + quint64 pop; // popularity, incremented each ping + int cost; + }; + + class Queue + { + public: + inline explicit Queue() : f(0), l(0), cost(0), pop(0), size(0) {} + + Node *f; + Node *l; + int cost; // total cost of nodes on the queue + quint64 pop; // sum of popularity values on the queue + int size; // size of the queue + }; + + Queue *q1_; // "newbies": seen only once, evicted LRA (least-recently-added) + Queue *q2_; // regular nodes, promoted from newbies, evicted LRU + Queue *q3_; // "hobos": evicted from q2 but were very popular (above mean) + Queue *q1_evicted_; // ghosts of recently evicted newbies and regulars + QHash lookup_; + +public: + explicit QCache3Q(int maxCost = 100, int minRecent = -1, int maxOldPopular = -1); + inline ~QCache3Q() { clear(); delete q1_; delete q2_; delete q3_; delete q1_evicted_; } + + inline int maxCost() const { return maxCost_; } + void setMaxCost(int maxCost, int minRecent = -1, int maxOldPopular = -1); + + inline int promoteAt() const { return promote_; } + inline void setPromoteAt(int p) { promote_ = p; } + + inline int totalCost() const { return q1_->cost + q2_->cost + q3_->cost; } + + void clear(); + bool insert(const Key &key, QSharedPointer object, int cost = 1); + QSharedPointer object(const Key &key) const; + QSharedPointer operator[](const Key &key) const; + + void remove(const Key &key); + + void printStats(); + + // Copy data directly into a queue. Designed for single use after construction + void deserializeQueue(int queueNumber, const QList &keys, + const QList > &values, const QList &costs); + // Copy data from specific queue into list + void serializeQueue(int queueNumber, QList > &buffer); + +private: + int maxCost_, minRecent_, maxOldPopular_; + int hitCount_, missCount_, promote_; + + void rebalance(); + void unlink(Node *n); + void link_front(Node *n, Queue *q); + +private: + // make these private so they can't be used + inline QCache3Q(const QCache3Q &) {} + inline QCache3Q &operator=(const QCache3Q &) {} +}; + +template +void QCache3Q::printStats() +{ + qDebug("\n=== cache %p ===", this); + qDebug("hits: %d (%.2f%%)\tmisses: %d\tfill: %.2f%%", hitCount_, + 100.0 * float(hitCount_) / (float(hitCount_ + missCount_)), + missCount_, + 100.0 * float(totalCost()) / float(maxCost())); + qDebug("q1g: size=%d, pop=%llu", q1_evicted_->size, q1_evicted_->pop); + qDebug("q1: cost=%d, size=%d, pop=%llu", q1_->cost, q1_->size, q1_->pop); + qDebug("q2: cost=%d, size=%d, pop=%llu", q2_->cost, q2_->size, q2_->pop); + qDebug("q3: cost=%d, size=%d, pop=%llu", q3_->cost, q3_->size, q3_->pop); +} + +template +QCache3Q::QCache3Q(int maxCost, int minRecent, int maxOldPopular) + : q1_(new Queue), q2_(new Queue), q3_(new Queue), q1_evicted_(new Queue), + maxCost_(maxCost), minRecent_(minRecent), maxOldPopular_(maxOldPopular), + hitCount_(0), missCount_(0), promote_(0) +{ + if (minRecent_ < 0) + minRecent_ = maxCost_ / 3; + if (maxOldPopular_ < 0) + maxOldPopular_ = maxCost_ / 5; +} + +template +void QCache3Q::serializeQueue(int queueNumber, QList > &buffer) +{ + Q_ASSERT(queueNumber >= 1 && queueNumber <= 4); + Queue *queue = queueNumber == 1 ? q1_ : + queueNumber == 2 ? q2_ : + queueNumber == 3 ? q3_ : + q1_evicted_; + for (Node *node = queue->f; node; node = node->n) + buffer.append(node->v); +} + +template +void QCache3Q::deserializeQueue(int queueNumber, const QList &keys, + const QList > &values, const QList &costs) +{ + Q_ASSERT(queueNumber >= 1 && queueNumber <= 4); + int bufferSize = keys.size(); + if (bufferSize == 0) + return; + clear(); + Queue *queue = queueNumber == 1 ? q1_ : + queueNumber == 2 ? q2_ : + queueNumber == 3 ? q3_ : + q1_evicted_; + for (int i = 0; iv = values[i]; + node->k = keys[i]; + node->cost = costs[i]; + link_front(node, queue); + lookup_[keys[i]] = node; + } +} + + +template +inline void QCache3Q::setMaxCost(int maxCost, int minRecent, int maxOldPopular) +{ + maxCost_ = maxCost; + minRecent_ = minRecent; + maxOldPopular_ = maxOldPopular; + if (minRecent_ < 0) + minRecent_ = maxCost_ / 3; + if (maxOldPopular_ < 0) + maxOldPopular_ = maxCost_ / 5; + rebalance(); +} + +template +bool QCache3Q::insert(const Key &key, QSharedPointer object, int cost) +{ + if (cost > maxCost_) { + return false; + } + + if (lookup_.contains(key)) { + Node *n = lookup_[key]; + n->v = object; + n->q->cost -= n->cost; + n->cost = cost; + n->q->cost += cost; + + if (n->q == q1_evicted_) { + if (n->pop > (uint)promote_) { + unlink(n); + link_front(n, q2_); + rebalance(); + } + } else if (n->q != q1_) { + Queue *q = n->q; + unlink(n); + link_front(n, q); + rebalance(); + } + + return true; + } + + Node *n = new Node; + n->v = object; + n->k = key; + n->cost = cost; + link_front(n, q1_); + lookup_[key] = n; + + rebalance(); + + return true; +} + +template +void QCache3Q::clear() +{ + while (q1_evicted_->f) { + Node *n = q1_evicted_->f; + unlink(n); + delete n; + } + + while (q1_->f) { + Node *n = q1_->f; + unlink(n); + EvPolicy::aboutToBeRemoved(n->k, n->v); + delete n; + } + + while (q2_->f) { + Node *n = q2_->f; + unlink(n); + EvPolicy::aboutToBeRemoved(n->k, n->v); + delete n; + } + + while (q3_->f) { + Node *n = q3_->f; + unlink(n); + EvPolicy::aboutToBeRemoved(n->k, n->v); + delete n; + } + + lookup_.clear(); +} + +template +void QCache3Q::unlink(Node *n) +{ + if (n->n) + n->n->p = n->p; + if (n->p) + n->p->n = n->n; + if (n->q->f == n) + n->q->f = n->n; + if (n->q->l == n) + n->q->l = n->p; + n->n = 0; + n->p = 0; + n->q->pop -= n->pop; + n->q->cost -= n->cost; + n->q->size--; + n->q = 0; +} + +template +void QCache3Q::link_front(Node *n, Queue *q) +{ + n->n = q->f; + n->p = 0; + n->q = q; + if (q->f) + q->f->p = n; + q->f = n; + if (!q->l) + q->l = n; + + q->pop += n->pop; + q->cost += n->cost; + q->size++; +} + +template +void QCache3Q::rebalance() +{ + while (q1_evicted_->size > (q1_->size + q2_->size + q3_->size) * 4) { + Node *n = q1_evicted_->l; + unlink(n); + lookup_.remove(n->k); + delete n; + } + + while ((q1_->cost + q2_->cost + q3_->cost) > maxCost_) { + if (q3_->cost > maxOldPopular_) { + Node *n = q3_->l; + unlink(n); + EvPolicy::aboutToBeEvicted(n->k, n->v); + lookup_.remove(n->k); + delete n; + } else if (q1_->cost > minRecent_) { + Node *n = q1_->l; + unlink(n); + EvPolicy::aboutToBeEvicted(n->k, n->v); + n->v.clear(); + n->cost = 0; + link_front(n, q1_evicted_); + } else { + Node *n = q2_->l; + unlink(n); + if (n->pop > (q2_->pop / q2_->size)) { + link_front(n, q3_); + } else { + EvPolicy::aboutToBeEvicted(n->k, n->v); + n->v.clear(); + n->cost = 0; + link_front(n, q1_evicted_); + } + } + } +} + +template +void QCache3Q::remove(const Key &key) +{ + if (!lookup_.contains(key)) { + return; + } + Node *n = lookup_[key]; + unlink(n); + if (n->q != q1_evicted_) + EvPolicy::aboutToBeRemoved(n->k, n->v); + lookup_.remove(key); + delete n; +} + +template +QSharedPointer QCache3Q::object(const Key &key) const +{ + if (!lookup_.contains(key)) { + const_cast *>(this)->missCount_++; + return QSharedPointer(0); + } + + QCache3Q *me = const_cast *>(this); + + Node *n = me->lookup_[key]; + n->pop++; + n->q->pop++; + + if (n->q == q1_) { + me->hitCount_++; + + if (n->pop > (quint64)promote_) { + me->unlink(n); + me->link_front(n, q2_); + me->rebalance(); + } + } else if (n->q != q1_evicted_) { + me->hitCount_++; + + Queue *q = n->q; + me->unlink(n); + me->link_front(n, q); + me->rebalance(); + } else { + me->missCount_++; + } + + return n->v; +} + +template +inline QSharedPointer QCache3Q::operator[](const Key &key) const +{ + return object(key); +} + +QT_END_NAMESPACE + +#endif // QCACHE3Q_H diff --git a/src/location/maps/qgeocameracapabilities.cpp b/src/location/maps/qgeocameracapabilities.cpp new file mode 100644 index 0000000..7b4a014 --- /dev/null +++ b/src/location/maps/qgeocameracapabilities.cpp @@ -0,0 +1,312 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocameracapabilities_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoCameraCapabilitiesPrivate : public QSharedData +{ +public: + QGeoCameraCapabilitiesPrivate(); + QGeoCameraCapabilitiesPrivate(const QGeoCameraCapabilitiesPrivate &other); + ~QGeoCameraCapabilitiesPrivate(); + + QGeoCameraCapabilitiesPrivate &operator = (const QGeoCameraCapabilitiesPrivate &other); + + bool supportsBearing_; + bool supportsRolling_; + bool supportsTilting_; + + // this is mutable so that it can be set from accessor functions that are const + mutable bool valid_; + + double minZoom_; + double maxZoom_; + double minTilt_; + double maxTilt_; +}; + +QGeoCameraCapabilitiesPrivate::QGeoCameraCapabilitiesPrivate() + : supportsBearing_(false), + supportsRolling_(false), + supportsTilting_(false), + valid_(false), + minZoom_(0.0), + maxZoom_(0.0), + minTilt_(0.0), + maxTilt_(0.0) {} + + +QGeoCameraCapabilitiesPrivate::QGeoCameraCapabilitiesPrivate(const QGeoCameraCapabilitiesPrivate &other) + : QSharedData(other), + supportsBearing_(other.supportsBearing_), + supportsRolling_(other.supportsRolling_), + supportsTilting_(other.supportsTilting_), + valid_(other.valid_), + minZoom_(other.minZoom_), + maxZoom_(other.maxZoom_), + minTilt_(other.minTilt_), + maxTilt_(other.maxTilt_) {} + +QGeoCameraCapabilitiesPrivate::~QGeoCameraCapabilitiesPrivate() {} + +QGeoCameraCapabilitiesPrivate &QGeoCameraCapabilitiesPrivate::operator = (const QGeoCameraCapabilitiesPrivate &other) +{ + if (this == &other) + return *this; + + supportsBearing_ = other.supportsBearing_; + supportsRolling_ = other.supportsRolling_; + supportsTilting_ = other.supportsTilting_; + valid_ = other.valid_; + minZoom_ = other.minZoom_; + maxZoom_ = other.maxZoom_; + minTilt_ = other.minTilt_; + maxTilt_ = other.maxTilt_; + + return *this; +} + +/*! + \class QGeoCameraCapabilities + \inmodule QtLocation + \ingroup QtLocation-impl + \since 5.6 + \internal + + \brief The QGeoCameraCapabilities class describes the limitations on camera settings imposed by a mapping plugin. + + Different mapping plugins will support different ranges of zoom levels, and not all mapping plugins will + be able to support, bearing, tilting and rolling of the camera. + + This class describes what the plugin supports, and is used to restrict changes to the camera information + associated with a \l QGeoMap such that the camera information stays within these limits. +*/ + +/*! + Constructs a camera capabilities object. +*/ +QGeoCameraCapabilities::QGeoCameraCapabilities() + : d(new QGeoCameraCapabilitiesPrivate()) {} + +/*! + Constructs a camera capabilities object from the contents of \a other. +*/ +QGeoCameraCapabilities::QGeoCameraCapabilities(const QGeoCameraCapabilities &other) + : d(other.d) {} + +/*! + Destroys this camera capabilities object. +*/ +QGeoCameraCapabilities::~QGeoCameraCapabilities() {} + +/*! + Assigns the contents of \a other to this camera capabilities object and + returns a reference to this camera capabilities object. +*/ +QGeoCameraCapabilities &QGeoCameraCapabilities::operator = (const QGeoCameraCapabilities &other) +{ + if (this == &other) + return *this; + + d = other.d; + return *this; +} + +/*! + Returns whether this instance of the class is considered "valid". To be + valid, the instance must have had at least one capability set (to either + true or false) using a set method, or copied from another instance + (such as by the assignment operator). +*/ +bool QGeoCameraCapabilities::isValid() const +{ + return d->valid_; +} + +/*! + Sets the minimum zoom level supported by the associated plugin to \a maximumZoomLevel. + + Larger values of the zoom level correspond to more detailed views of the + map. +*/ +void QGeoCameraCapabilities::setMinimumZoomLevel(double minimumZoomLevel) +{ + d->minZoom_ = minimumZoomLevel; + d->valid_ = true; +} + +/*! + Returns the minimum zoom level supported by the associated plugin. + + Larger values of the zoom level correspond to more detailed views of the + map. +*/ +double QGeoCameraCapabilities::minimumZoomLevel() const +{ + return d->minZoom_; +} + +/*! + Sets the maximum zoom level supported by the associated plugin to \a maximumZoomLevel. + + Larger values of the zoom level correspond to more detailed views of the + map. +*/ +void QGeoCameraCapabilities::setMaximumZoomLevel(double maximumZoomLevel) +{ + d->maxZoom_ = maximumZoomLevel; + d->valid_ = true; +} + +/*! + Returns the maximum zoom level supported by the associated plugin. + + Larger values of the zoom level correspond to more detailed views of the + map. +*/ +double QGeoCameraCapabilities::maximumZoomLevel() const +{ + return d->maxZoom_; +} + +/*! + Sets whether the associated plugin can render a map when the camera + has an arbitrary bearing to \a supportsBearing. +*/ +void QGeoCameraCapabilities::setSupportsBearing(bool supportsBearing) +{ + d->supportsBearing_ = supportsBearing; + d->valid_ = true; +} + +/*! + Returns whether the associated plugin can render a map when the camera + has an arbitrary bearing. +*/ +bool QGeoCameraCapabilities::supportsBearing() const +{ + return d->supportsBearing_; +} + +/*! + Sets whether the associated plugin can render a map when the + camera is rolled to \a supportsRolling. +*/ +void QGeoCameraCapabilities::setSupportsRolling(bool supportsRolling) +{ + d->supportsRolling_ = supportsRolling; + d->valid_ = true; +} + +/*! + Returns whether the associated plugin can render a map when the + camera is rolled. +*/ +bool QGeoCameraCapabilities::supportsRolling() const +{ + return d->supportsRolling_; +} + +/*! + Sets whether the associated plugin can render a map when the + camera is tilted to \a supportsTilting. +*/ +void QGeoCameraCapabilities::setSupportsTilting(bool supportsTilting) +{ + d->supportsTilting_ = supportsTilting; + d->valid_ = true; +} + +/*! + Returns whether the associated plugin can render a map when the + camera is tilted. +*/ +bool QGeoCameraCapabilities::supportsTilting() const +{ + return d->supportsTilting_; +} + +/*! + Sets the minimum tilt supported by the associated plugin to \a minimumTilt. + + The value is in degrees where 0 is equivalent to 90 degrees between + the line of view and earth's surface, that is, looking straight down to earth. +*/ +void QGeoCameraCapabilities::setMinimumTilt(double minimumTilt) +{ + d->minTilt_ = minimumTilt; + d->valid_ = true; +} + +/*! + Returns the minimum tilt supported by the associated plugin. + + The value is in degrees where 0 is equivalent to 90 degrees between + the line of view and earth's surface, that is, looking straight down to earth. +*/ +double QGeoCameraCapabilities::minimumTilt() const +{ + return d->minTilt_; +} + +/*! + Sets the maximum tilt supported by the associated plugin to \a maximumTilt. + + The value is in degrees where 0 is equivalent to 90 degrees between + the line of view and earth's surface, that is, looking straight down to earth. +*/ +void QGeoCameraCapabilities::setMaximumTilt(double maximumTilt) +{ + d->maxTilt_ = maximumTilt; + d->valid_ = true; +} + +/*! + Returns the maximum tilt supported by the associated plugin. + + The value is in degrees where 0 is equivalent to 90 degrees between + the line of view and earth's surface, that is, looking straight down to earth. +*/ +double QGeoCameraCapabilities::maximumTilt() const +{ + return d->maxTilt_; +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeocameracapabilities_p.h b/src/location/maps/qgeocameracapabilities_p.h new file mode 100644 index 0000000..4e498b1 --- /dev/null +++ b/src/location/maps/qgeocameracapabilities_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCAMERACAPABILITIES_P_H +#define QGEOCAMERACAPABILITIES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoCameraCapabilitiesPrivate; + +class Q_LOCATION_EXPORT QGeoCameraCapabilities +{ +public: + QGeoCameraCapabilities(); + QGeoCameraCapabilities(const QGeoCameraCapabilities &other); + ~QGeoCameraCapabilities(); + + QGeoCameraCapabilities &operator = (const QGeoCameraCapabilities &other); + + void setMinimumZoomLevel(double minimumZoomLevel); + double minimumZoomLevel() const; + + void setMaximumZoomLevel(double maximumZoomLevel); + double maximumZoomLevel() const; + + void setSupportsBearing(bool supportsBearing); + bool supportsBearing() const; + + void setSupportsRolling(bool supportsRolling); + bool supportsRolling() const; + + void setSupportsTilting(bool supportsTilting); + bool supportsTilting() const; + + void setMinimumTilt(double minimumTilt); + double minimumTilt() const; + + void setMaximumTilt(double maximumTilt); + double maximumTilt() const; + + bool isValid() const; + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +#endif // QGEOCAMERACAPABILITIES_P_H diff --git a/src/location/maps/qgeocameradata.cpp b/src/location/maps/qgeocameradata.cpp new file mode 100644 index 0000000..2358608 --- /dev/null +++ b/src/location/maps/qgeocameradata.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeocameradata_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCameraDataPrivate : public QSharedData +{ +public: + QGeoCameraDataPrivate(); + QGeoCameraDataPrivate(const QGeoCameraDataPrivate &rhs); + + QGeoCameraDataPrivate &operator = (const QGeoCameraDataPrivate &rhs); + + bool operator == (const QGeoCameraDataPrivate &rhs) const; + + QGeoCoordinate m_center; + double m_bearing; + double m_tilt; + double m_roll; + double m_zoomLevel; +}; + +QGeoCameraDataPrivate::QGeoCameraDataPrivate() + : QSharedData(), + m_center(0, 0), + m_bearing(0.0), + m_tilt(0.0), + m_roll(0.0), + m_zoomLevel(0.0) {} + +QGeoCameraDataPrivate::QGeoCameraDataPrivate(const QGeoCameraDataPrivate &rhs) + : QSharedData(rhs), + m_center(rhs.m_center), + m_bearing(rhs.m_bearing), + m_tilt(rhs.m_tilt), + m_roll(rhs.m_roll), + m_zoomLevel(rhs.m_zoomLevel) {} + +QGeoCameraDataPrivate &QGeoCameraDataPrivate::operator = (const QGeoCameraDataPrivate &rhs) +{ + if (this == &rhs) + return *this; + + m_center = rhs.m_center; + m_bearing = rhs.m_bearing; + m_tilt = rhs.m_tilt; + m_roll = rhs.m_roll; + m_zoomLevel = rhs.m_zoomLevel; + + return *this; +} + +bool QGeoCameraDataPrivate::operator == (const QGeoCameraDataPrivate &rhs) const +{ + return ((m_center == rhs.m_center) + && (m_bearing == rhs.m_bearing) + && (m_tilt == rhs.m_tilt) + && (m_roll == rhs.m_roll) + && (m_zoomLevel == rhs.m_zoomLevel)); +} + +QVariant cameraInterpolator(const QGeoCameraData &start, + const QGeoCameraData &end, + qreal progress) +{ + QGeoCameraData result = start; + QGeoCoordinate from = start.center(); + QGeoCoordinate to = end.center(); + + if (from == to) { + if (progress < 0.5) { + result.setCenter(from); + } else { + result.setCenter(to); + } + } + else { + QGeoCoordinate coordinateResult = QGeoProjection::coordinateInterpolation(from, to, progress); + result.setCenter(coordinateResult); + } + + double sf = 1.0 - progress; + double ef = progress; + + result.setBearing(sf * start.bearing() + ef * end.bearing()); + result.setTilt(sf * start.tilt() + ef * end.tilt()); + result.setRoll(sf * start.roll() + ef * end.roll()); + result.setZoomLevel(sf * start.zoomLevel() + ef * end.zoomLevel()); + + return QVariant::fromValue(result); +} + +QGeoCameraData::QGeoCameraData() + : d(new QGeoCameraDataPrivate()) +{ + qRegisterMetaType(); + qRegisterAnimationInterpolator(cameraInterpolator); +} + +QGeoCameraData::QGeoCameraData(const QGeoCameraData &other) + : d(other.d) {} + +QGeoCameraData::~QGeoCameraData() +{ +} + +QGeoCameraData &QGeoCameraData::operator = (const QGeoCameraData &other) +{ + if (this == &other) + return *this; + + d = other.d; + return *this; +} + +bool QGeoCameraData::operator == (const QGeoCameraData &rhs) const +{ + return (*(d.constData()) == *(rhs.d.constData())); +} + +bool QGeoCameraData::operator != (const QGeoCameraData &other) const +{ + return !(operator==(other)); +} + +void QGeoCameraData::setCenter(const QGeoCoordinate ¢er) +{ + d->m_center = center; +} + +QGeoCoordinate QGeoCameraData::center() const +{ + return d->m_center; +} + +void QGeoCameraData::setBearing(double bearing) +{ + d->m_bearing = bearing; +} + +double QGeoCameraData::bearing() const +{ + return d->m_bearing; +} + +void QGeoCameraData::setTilt(double tilt) +{ + d->m_tilt = tilt; +} + +double QGeoCameraData::tilt() const +{ + return d->m_tilt; +} + +void QGeoCameraData::setRoll(double roll) +{ + d->m_roll = roll; +} + +double QGeoCameraData::roll() const +{ + return d->m_roll; +} + +void QGeoCameraData::setZoomLevel(double zoomFactor) +{ + d->m_zoomLevel = zoomFactor; +} + +double QGeoCameraData::zoomLevel() const +{ + return d->m_zoomLevel; +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeocameradata_p.h b/src/location/maps/qgeocameradata_p.h new file mode 100644 index 0000000..a1434fd --- /dev/null +++ b/src/location/maps/qgeocameradata_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOCAMERADATA_P_H +#define QGEOCAMERADATA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qlocationglobal.h" +#include "qgeocoordinate.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoCameraDataPrivate; + +class Q_LOCATION_EXPORT QGeoCameraData +{ +public: + QGeoCameraData(); + QGeoCameraData(const QGeoCameraData &other); + ~QGeoCameraData(); + + QGeoCameraData &operator = (const QGeoCameraData &other); + + bool operator == (const QGeoCameraData &other) const; + bool operator != (const QGeoCameraData &other) const; + + void setCenter(const QGeoCoordinate &coordinate); + QGeoCoordinate center() const; + + void setBearing(double bearing); + double bearing() const; + + void setTilt(double tilt); + double tilt() const; + + void setRoll(double roll); + double roll() const; + + void setZoomLevel(double zoomLevel); + double zoomLevel() const; + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoCameraData) + +#endif // QGEOCAMERADATA_P_H diff --git a/src/location/maps/qgeocameratiles.cpp b/src/location/maps/qgeocameratiles.cpp new file mode 100644 index 0000000..5eae7c0 --- /dev/null +++ b/src/location/maps/qgeocameratiles.cpp @@ -0,0 +1,946 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeocameratiles_p.h" +#include "qgeocameradata_p.h" +#include "qgeotilespec_p.h" +#include "qgeomaptype_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +struct Frustum +{ + QDoubleVector3D topLeftNear; + QDoubleVector3D topLeftFar; + QDoubleVector3D topRightNear; + QDoubleVector3D topRightFar; + QDoubleVector3D bottomLeftNear; + QDoubleVector3D bottomLeftFar; + QDoubleVector3D bottomRightNear; + QDoubleVector3D bottomRightFar; +}; + +typedef QVector PolygonVector; + +class QGeoCameraTilesPrivate +{ +public: + QGeoCameraTilesPrivate(); + ~QGeoCameraTilesPrivate(); + + QString m_pluginString; + QGeoMapType m_mapType; + int m_mapVersion; + QGeoCameraData m_camera; + QSize m_screenSize; + int m_tileSize; + QSet m_tiles; + + int m_intZoomLevel; + int m_sideLength; + + bool m_dirtyGeometry; + bool m_dirtyMetadata; + + double m_viewExpansion; + void updateMetadata(); + void updateGeometry(); + + Frustum createFrustum(double fieldOfViewGradient) const; + + class LengthSorter + { + public: + QDoubleVector3D base; + bool operator()(const QDoubleVector3D &lhs, const QDoubleVector3D &rhs) + { + return (lhs - base).lengthSquared() < (rhs - base).lengthSquared(); + } + }; + + void appendZIntersects(const QDoubleVector3D &start, const QDoubleVector3D &end, double z, QVector &results) const; + PolygonVector frustumFootprint(const Frustum &frustum) const; + + QPair splitPolygonAtAxisValue(const PolygonVector &polygon, int axis, double value) const; + QPair clipFootprintToMap(const PolygonVector &footprint) const; + + QList > tileIntersections(double p1, int t1, double p2, int t2) const; + QSet tilesFromPolygon(const PolygonVector &polygon) const; + + struct TileMap + { + TileMap(); + + void add(int tileX, int tileY); + + QMap > data; + }; +}; + +QGeoCameraTiles::QGeoCameraTiles() + : d_ptr(new QGeoCameraTilesPrivate()) {} + +QGeoCameraTiles::~QGeoCameraTiles() +{ +} + +void QGeoCameraTiles::setCameraData(const QGeoCameraData &camera) +{ + if (d_ptr->m_camera == camera) + return; + + d_ptr->m_dirtyGeometry = true; + d_ptr->m_camera = camera; + d_ptr->m_intZoomLevel = static_cast(std::floor(d_ptr->m_camera.zoomLevel())); + d_ptr->m_sideLength = 1 << d_ptr->m_intZoomLevel; +} + +QGeoCameraData QGeoCameraTiles::cameraData() const +{ + return d_ptr->m_camera; +} + +void QGeoCameraTiles::setScreenSize(const QSize &size) +{ + if (d_ptr->m_screenSize == size) + return; + + d_ptr->m_dirtyGeometry = true; + d_ptr->m_screenSize = size; +} + +void QGeoCameraTiles::setPluginString(const QString &pluginString) +{ + if (d_ptr->m_pluginString == pluginString) + return; + + d_ptr->m_dirtyMetadata = true; + d_ptr->m_pluginString = pluginString; +} + +void QGeoCameraTiles::setMapType(const QGeoMapType &mapType) +{ + if (d_ptr->m_mapType == mapType) + return; + + d_ptr->m_dirtyMetadata = true; + d_ptr->m_mapType = mapType; +} + +void QGeoCameraTiles::setMapVersion(int mapVersion) +{ + if (d_ptr->m_mapVersion == mapVersion) + return; + + d_ptr->m_dirtyMetadata = true; + d_ptr->m_mapVersion = mapVersion; +} + +void QGeoCameraTiles::setTileSize(int tileSize) +{ + if (d_ptr->m_tileSize == tileSize) + return; + + d_ptr->m_dirtyGeometry = true; + d_ptr->m_tileSize = tileSize; +} + +void QGeoCameraTiles::setViewExpansion(double viewExpansion) +{ + d_ptr->m_viewExpansion = viewExpansion; + d_ptr->m_dirtyGeometry = true; +} + +int QGeoCameraTiles::tileSize() const +{ + return d_ptr->m_tileSize; +} + +const QSet& QGeoCameraTiles::createTiles() +{ + if (d_ptr->m_dirtyGeometry) { + d_ptr->m_tiles.clear(); + d_ptr->updateGeometry(); + d_ptr->m_dirtyGeometry = false; + } + + if (d_ptr->m_dirtyMetadata) { + d_ptr->updateMetadata(); + d_ptr->m_dirtyMetadata = false; + } + + return d_ptr->m_tiles; +} + +QGeoCameraTilesPrivate::QGeoCameraTilesPrivate() +: m_mapVersion(-1), + m_tileSize(0), + m_intZoomLevel(0), + m_sideLength(0), + m_dirtyGeometry(false), + m_dirtyMetadata(false), + m_viewExpansion(1.0) +{ +} + +QGeoCameraTilesPrivate::~QGeoCameraTilesPrivate() {} + +void QGeoCameraTilesPrivate::updateMetadata() +{ + typedef QSet::const_iterator iter; + + QSet newTiles; + + iter i = m_tiles.constBegin(); + iter end = m_tiles.constEnd(); + + for (; i != end; ++i) { + QGeoTileSpec tile = *i; + newTiles.insert(QGeoTileSpec(m_pluginString, m_mapType.mapId(), tile.zoom(), tile.x(), tile.y(), m_mapVersion)); + } + + m_tiles = newTiles; +} + +void QGeoCameraTilesPrivate::updateGeometry() +{ + // Find the frustum from the camera / screen / viewport information + // The larger frustum when stationary is a form of prefetching + Frustum f = createFrustum(m_viewExpansion); + + // Find the polygon where the frustum intersects the plane of the map + PolygonVector footprint = frustumFootprint(f); + + // Clip the polygon to the map, split it up if it cross the dateline + QPair polygons = clipFootprintToMap(footprint); + + if (!polygons.first.isEmpty()) { + QSet tilesLeft = tilesFromPolygon(polygons.first); + m_tiles.unite(tilesLeft); + } + + if (!polygons.second.isEmpty()) { + QSet tilesRight = tilesFromPolygon(polygons.second); + m_tiles.unite(tilesRight); + } +} + +Frustum QGeoCameraTilesPrivate::createFrustum(double fieldOfViewGradient) const +{ + QDoubleVector3D center = m_sideLength * QGeoProjection::coordToMercator(m_camera.center()); + center.setZ(0.0); + + double f = qMin(m_screenSize.width(), m_screenSize.height()); + + double z = std::pow(2.0, m_camera.zoomLevel() - m_intZoomLevel) * m_tileSize; + + double altitude = f / (2.0 * z); + QDoubleVector3D eye = center; + eye.setZ(altitude); + + QDoubleVector3D view = eye - center; + QDoubleVector3D side = QDoubleVector3D::normal(view, QDoubleVector3D(0.0, 1.0, 0.0)); + QDoubleVector3D up = QDoubleVector3D::normal(side, view); + + double nearPlane = 1 / (4.0 * m_tileSize ); + double farPlane = altitude + 1.0; + + double aspectRatio = 1.0 * m_screenSize.width() / m_screenSize.height(); + + double hn,wn,hf,wf = 0.0; + + // fixes field of view at 45 degrees + // this assumes that viewSize = 2*nearPlane x 2*nearPlane + + if (aspectRatio > 1.0) { + hn = 2 * fieldOfViewGradient * nearPlane; + wn = hn * aspectRatio; + + hf = 2 * fieldOfViewGradient * farPlane; + wf = hf * aspectRatio; + } else { + wn = 2 * fieldOfViewGradient * nearPlane; + hn = wn / aspectRatio; + + wf = 2 * fieldOfViewGradient * farPlane; + hf = wf / aspectRatio; + } + + QDoubleVector3D d = center - eye; + d.normalize(); + up.normalize(); + QDoubleVector3D right = QDoubleVector3D::normal(d, up); + + QDoubleVector3D cf = eye + d * farPlane; + QDoubleVector3D cn = eye + d * nearPlane; + + Frustum frustum; + + frustum.topLeftFar = cf + (up * hf / 2) - (right * wf / 2); + frustum.topRightFar = cf + (up * hf / 2) + (right * wf / 2); + frustum.bottomLeftFar = cf - (up * hf / 2) - (right * wf / 2); + frustum.bottomRightFar = cf - (up * hf / 2) + (right * wf / 2); + + frustum.topLeftNear = cn + (up * hn / 2) - (right * wn / 2); + frustum.topRightNear = cn + (up * hn / 2) + (right * wn / 2); + frustum.bottomLeftNear = cn - (up * hn / 2) - (right * wn / 2); + frustum.bottomRightNear = cn - (up * hn / 2) + (right * wn / 2); + + return frustum; +} + +void QGeoCameraTilesPrivate::appendZIntersects(const QDoubleVector3D &start, + const QDoubleVector3D &end, + double z, + QVector &results) const +{ + if (start.z() == end.z()) { + if (start.z() == z) { + results.append(start); + results.append(end); + } + } else { + double f = (start.z() - z) / (start.z() - end.z()); + if ((0 <= f) && (f <= 1.0)) { + results.append((1 - f) * start + f * end); + } + } +} + +/***************************************************/ +/* Local copy of qSort & qSortHelper to suppress deprecation warnings + * following the deprecation of QtAlgorithms. The comparison has subtle + * differences which eluded detection so far. We just reuse old qSort for now. + **/ + +template +inline void localqSort(RandomAccessIterator start, RandomAccessIterator end, LessThan lessThan) +{ + if (start != end) + localqSortHelper(start, end, *start, lessThan); +} + +template +void localqSortHelper(RandomAccessIterator start, RandomAccessIterator end, const T &t, LessThan lessThan) +{ +top: + int span = int(end - start); + if (span < 2) + return; + + --end; + RandomAccessIterator low = start, high = end - 1; + RandomAccessIterator pivot = start + span / 2; + + if (lessThan(*end, *start)) + qSwap(*end, *start); + if (span == 2) + return; + + if (lessThan(*pivot, *start)) + qSwap(*pivot, *start); + if (lessThan(*end, *pivot)) + qSwap(*end, *pivot); + if (span == 3) + return; + + qSwap(*pivot, *end); + + while (low < high) { + while (low < high && lessThan(*low, *end)) + ++low; + + while (high > low && lessThan(*end, *high)) + --high; + + if (low < high) { + qSwap(*low, *high); + ++low; + --high; + } else { + break; + } + } + + if (lessThan(*low, *end)) + ++low; + + qSwap(*end, *low); + localqSortHelper(start, low, t, lessThan); + + start = low + 1; + ++end; + goto top; +} +/***************************************************/ + + +// Returns the intersection of the plane of the map and the camera frustum as a right handed polygon +PolygonVector QGeoCameraTilesPrivate::frustumFootprint(const Frustum &frustum) const +{ + PolygonVector points; + points.reserve(24); + + appendZIntersects(frustum.topLeftNear, frustum.topLeftFar, 0.0, points); + appendZIntersects(frustum.topRightNear, frustum.topRightFar, 0.0, points); + appendZIntersects(frustum.bottomLeftNear, frustum.bottomLeftFar, 0.0, points); + appendZIntersects(frustum.bottomRightNear, frustum.bottomRightFar, 0.0, points); + + appendZIntersects(frustum.topLeftNear, frustum.bottomLeftNear, 0.0, points); + appendZIntersects(frustum.bottomLeftNear, frustum.bottomRightNear, 0.0, points); + appendZIntersects(frustum.bottomRightNear, frustum.topRightNear, 0.0, points); + appendZIntersects(frustum.topRightNear, frustum.topLeftNear, 0.0, points); + + appendZIntersects(frustum.topLeftFar, frustum.bottomLeftFar, 0.0, points); + appendZIntersects(frustum.bottomLeftFar, frustum.bottomRightFar, 0.0, points); + appendZIntersects(frustum.bottomRightFar, frustum.topRightFar, 0.0, points); + appendZIntersects(frustum.topRightFar, frustum.topLeftFar, 0.0, points); + + if (points.isEmpty()) + return points; + + // sort points into a right handed polygon + + LengthSorter sorter; + + // - initial sort to remove duplicates + sorter.base = points.first(); + localqSort(points.begin(), points.end(), sorter); + //std::sort(points.begin(), points.end(), sorter); + for (int i = points.size() - 1; i > 0; --i) { + if (points.at(i) == points.at(i - 1)) + points.remove(i); + } + + // - proper sort + // - start with the first point, put it in the sorted part of the list + // - add the nearest unsorted point to the last sorted point to the end + // of the sorted points + PolygonVector::iterator i; + for (i = points.begin(); i != points.end(); ++i) { + sorter.base = *i; + if (i + 1 != points.end()) + std::sort(i + 1, points.end(), sorter) ; + } + + // - determine if what we have is right handed + int size = points.size(); + if (size >= 3) { + QDoubleVector3D normal = QDoubleVector3D::normal(points.at(1) - points.at(0), + points.at(2) - points.at(1)); + // - if not, reverse the list + if (normal.z() < 0.0) { + int halfSize = size / 2; + for (int i = 0; i < halfSize; ++i) { + QDoubleVector3D spare = points.at(i); + points[i] = points[size - 1 - i]; + points[size - 1 - i] = spare; + } + } + } + + return points; +} + +QPair QGeoCameraTilesPrivate::splitPolygonAtAxisValue(const PolygonVector &polygon, int axis, double value) const +{ + PolygonVector polygonBelow; + PolygonVector polygonAbove; + + int size = polygon.size(); + + if (size == 0) { + return QPair(polygonBelow, polygonAbove); + } + + QVector comparisons = QVector(polygon.size()); + + for (int i = 0; i < size; ++i) { + double v = polygon.at(i).get(axis); + if (qFuzzyCompare(v - value + 1.0, 1.0)) { + comparisons[i] = 0; + } else { + if (v < value) { + comparisons[i] = -1; + } else if (value < v) { + comparisons[i] = 1; + } + } + } + + for (int index = 0; index < size; ++index) { + int prevIndex = index - 1; + if (prevIndex < 0) + prevIndex += size; + int nextIndex = (index + 1) % size; + + int prevComp = comparisons[prevIndex]; + int comp = comparisons[index]; + int nextComp = comparisons[nextIndex]; + + if (comp == 0) { + if (prevComp == -1) { + polygonBelow.append(polygon.at(index)); + if (nextComp == 1) { + polygonAbove.append(polygon.at(index)); + } + } else if (prevComp == 1) { + polygonAbove.append(polygon.at(index)); + if (nextComp == -1) { + polygonBelow.append(polygon.at(index)); + } + } else if (prevComp == 0) { + if (nextComp == -1) { + polygonBelow.append(polygon.at(index)); + } else if (nextComp == 1) { + polygonAbove.append(polygon.at(index)); + } else if (nextComp == 0) { + // do nothing + } + } + } else { + if (comp == -1) { + polygonBelow.append(polygon.at(index)); + } else if (comp == 1) { + polygonAbove.append(polygon.at(index)); + } + + // there is a point between this and the next point + // on the polygon that lies on the splitting line + // and should be added to both the below and above + // polygons + if ((nextComp != 0) && (nextComp != comp)) { + QDoubleVector3D p1 = polygon.at(index); + QDoubleVector3D p2 = polygon.at(nextIndex); + + double p1v = p1.get(axis); + double p2v = p2.get(axis); + + double f = (p1v - value) / (p1v - p2v); + + if (((0 <= f) && (f <= 1.0)) + || qFuzzyCompare(f + 1.0, 1.0) + || qFuzzyCompare(f + 1.0, 2.0) ) { + QDoubleVector3D midPoint = (1.0 - f) * p1 + f * p2; + polygonBelow.append(midPoint); + polygonAbove.append(midPoint); + } + } + } + } + + return QPair(polygonBelow, polygonAbove); +} + + +QPair QGeoCameraTilesPrivate::clipFootprintToMap(const PolygonVector &footprint) const +{ + bool clipX0 = false; + bool clipX1 = false; + bool clipY0 = false; + bool clipY1 = false; + + double side = 1.0 * m_sideLength; + + typedef PolygonVector::const_iterator const_iter; + + const_iter i = footprint.constBegin(); + const_iter end = footprint.constEnd(); + for (; i != end; ++i) { + QDoubleVector3D p = *i; + if ((p.x() < 0.0) || (qFuzzyIsNull(p.x()))) + clipX0 = true; + if ((side < p.x()) || (qFuzzyCompare(side, p.x()))) + clipX1 = true; + if (p.y() < 0.0) + clipY0 = true; + if (side < p.y()) + clipY1 = true; + } + + PolygonVector results = footprint; + + if (clipY0) { + results = splitPolygonAtAxisValue(results, 1, 0.0).second; + } + + if (clipY1) { + results = splitPolygonAtAxisValue(results, 1, side).first; + } + + if (clipX0) { + if (clipX1) { + results = splitPolygonAtAxisValue(results, 0, 0.0).second; + results = splitPolygonAtAxisValue(results, 0, side).first; + return QPair(results, PolygonVector()); + } else { + QPair pair = splitPolygonAtAxisValue(results, 0, 0.0); + if (pair.first.isEmpty()) { + // if we touched the line but didn't cross it... + for (int i = 0; i < pair.second.size(); ++i) { + if (qFuzzyIsNull(pair.second.at(i).x())) + pair.first.append(pair.second.at(i)); + } + if (pair.first.size() == 2) { + double y0 = pair.first[0].y(); + double y1 = pair.first[1].y(); + pair.first.clear(); + pair.first.append(QDoubleVector3D(side, y0, 0.0)); + pair.first.append(QDoubleVector3D(side - 0.001, y0, 0.0)); + pair.first.append(QDoubleVector3D(side - 0.001, y1, 0.0)); + pair.first.append(QDoubleVector3D(side, y1, 0.0)); + } else if (pair.first.size() == 1) { + // FIXME this is trickier + // - touching at one point on the tile boundary + // - probably need to build a triangular polygon across the edge + // - don't want to add another y tile if we can help it + // - initial version doesn't care + double y = pair.first.at(0).y(); + pair.first.clear(); + pair.first.append(QDoubleVector3D(side - 0.001, y, 0.0)); + pair.first.append(QDoubleVector3D(side, y + 0.001, 0.0)); + pair.first.append(QDoubleVector3D(side, y - 0.001, 0.0)); + } + } else { + for (int i = 0; i < pair.first.size(); ++i) { + pair.first[i].setX(pair.first.at(i).x() + side); + } + } + return pair; + } + } else { + if (clipX1) { + QPair pair = splitPolygonAtAxisValue(results, 0, side); + if (pair.second.isEmpty()) { + // if we touched the line but didn't cross it... + for (int i = 0; i < pair.first.size(); ++i) { + if (qFuzzyCompare(side, pair.first.at(i).x())) + pair.second.append(pair.first.at(i)); + } + if (pair.second.size() == 2) { + double y0 = pair.second[0].y(); + double y1 = pair.second[1].y(); + pair.second.clear(); + pair.second.append(QDoubleVector3D(0, y0, 0.0)); + pair.second.append(QDoubleVector3D(0.001, y0, 0.0)); + pair.second.append(QDoubleVector3D(0.001, y1, 0.0)); + pair.second.append(QDoubleVector3D(0, y1, 0.0)); + } else if (pair.second.size() == 1) { + // FIXME this is trickier + // - touching at one point on the tile boundary + // - probably need to build a triangular polygon across the edge + // - don't want to add another y tile if we can help it + // - initial version doesn't care + double y = pair.second.at(0).y(); + pair.second.clear(); + pair.second.append(QDoubleVector3D(0.001, y, 0.0)); + pair.second.append(QDoubleVector3D(0.0, y - 0.001, 0.0)); + pair.second.append(QDoubleVector3D(0.0, y + 0.001, 0.0)); + } + } else { + for (int i = 0; i < pair.second.size(); ++i) { + pair.second[i].setX(pair.second.at(i).x() - side); + } + } + return pair; + } else { + return QPair(results, PolygonVector()); + } + } + +} + +QList > QGeoCameraTilesPrivate::tileIntersections(double p1, int t1, double p2, int t2) const +{ + if (t1 == t2) { + QList > results = QList >(); + results.append(QPair(0.0, t1)); + return results; + } + + int step = 1; + if (t1 > t2) { + step = -1; + } + + int size = 1 + ((t2 - t1) / step); + + QList > results = QList >(); + + results.append(QPair(0.0, t1)); + + if (step == 1) { + for (int i = 1; i < size; ++i) { + double f = (t1 + i - p1) / (p2 - p1); + results.append(QPair(f, t1 + i)); + } + } else { + for (int i = 1; i < size; ++i) { + double f = (t1 - i + 1 - p1) / (p2 - p1); + results.append(QPair(f, t1 - i)); + } + } + + return results; +} + +QSet QGeoCameraTilesPrivate::tilesFromPolygon(const PolygonVector &polygon) const +{ + int numPoints = polygon.size(); + + if (numPoints == 0) + return QSet(); + + QVector tilesX(polygon.size()); + QVector tilesY(polygon.size()); + + // grab tiles at the corners of the polygon + for (int i = 0; i < numPoints; ++i) { + + QDoubleVector2D p = polygon.at(i).toVector2D(); + + int x = 0; + int y = 0; + + if (qFuzzyCompare(p.x(), m_sideLength * 1.0)) + x = m_sideLength - 1; + else { + x = static_cast(p.x()) % m_sideLength; + if ( !qFuzzyCompare(p.x(), 1.0 * x) && qFuzzyCompare(p.x(), 1.0 * (x + 1)) ) + x++; + } + + if (qFuzzyCompare(p.y(), m_sideLength * 1.0)) + y = m_sideLength - 1; + else { + y = static_cast(p.y()) % m_sideLength; + if ( !qFuzzyCompare(p.y(), 1.0 * y) && qFuzzyCompare(p.y(), 1.0 * (y + 1)) ) + y++; + } + + tilesX[i] = x; + tilesY[i] = y; + } + + QGeoCameraTilesPrivate::TileMap map; + + // walk along the edges of the polygon and add all tiles covered by them + for (int i1 = 0; i1 < numPoints; ++i1) { + int i2 = (i1 + 1) % numPoints; + + double x1 = polygon.at(i1).get(0); + double x2 = polygon.at(i2).get(0); + + bool xFixed = qFuzzyCompare(x1, x2); + bool xIntegral = qFuzzyCompare(x1, std::floor(x1)) || qFuzzyCompare(x1 + 1.0, std::floor(x1 + 1.0)); + + QList > xIntersects + = tileIntersections(x1, + tilesX.at(i1), + x2, + tilesX.at(i2)); + + double y1 = polygon.at(i1).get(1); + double y2 = polygon.at(i2).get(1); + + bool yFixed = qFuzzyCompare(y1, y2); + bool yIntegral = qFuzzyCompare(y1, std::floor(y1)) || qFuzzyCompare(y1 + 1.0, std::floor(y1 + 1.0)); + + QList > yIntersects + = tileIntersections(y1, + tilesY.at(i1), + y2, + tilesY.at(i2)); + + int x = xIntersects.takeFirst().second; + int y = yIntersects.takeFirst().second; + + + /* + If the polygon coincides with the tile edges we must be + inclusive and grab all tiles on both sides. We also need + to handle tiles with corners coindent with the + corners of the polygon. + e.g. all tiles marked with 'x' will be added + + "+" - tile boundaries + "O" - polygon boundary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x + x + x + + + + + + + + + + + + + + + + + + O O O O O + + + + + + + + + + + O 0 + + + + + x O x 0 x + + + + + O 0 + + + + + + + + + + + O 0 0 0 0 + + + + + + + + + + + + + + + + + + x + x + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ + + + int xOther = x; + int yOther = y; + + if (xFixed && xIntegral) { + if (y2 < y1) { + xOther = qMax(0, x - 1); + } + } + + if (yFixed && yIntegral) { + if (x1 < x2) { + yOther = qMax(0, y - 1); + + } + } + + if (xIntegral) { + map.add(xOther, y); + if (yIntegral) + map.add(xOther, yOther); + + } + + if (yIntegral) + map.add(x, yOther); + + map.add(x,y); + + // top left corner + int iPrev = (i1 + numPoints - 1) % numPoints; + double xPrevious = polygon.at(iPrev).get(0); + double yPrevious = polygon.at(iPrev).get(1); + bool xPreviousFixed = qFuzzyCompare(xPrevious, x1); + if (xIntegral && xPreviousFixed && yIntegral && yFixed) { + if ((x2 > x1) && (yPrevious > y1)) { + if ((x - 1) > 0 && (y - 1) > 0) + map.add(x - 1, y - 1); + } else if ((x2 < x1) && (yPrevious < y1)) { + // what? + } + } + + // for the simple case where intersections do not coincide with + // the boundaries, we move along the edge and add tiles until + // the x and y intersection lists are exhausted + + while (!xIntersects.isEmpty() && !yIntersects.isEmpty()) { + QPair nextX = xIntersects.first(); + QPair nextY = yIntersects.first(); + if (nextX.first < nextY.first) { + x = nextX.second; + map.add(x, y); + xIntersects.removeFirst(); + + } else if (nextX.first > nextY.first) { + y = nextY.second; + map.add(x, y); + yIntersects.removeFirst(); + + } else { + map.add(x, nextY.second); + map.add(nextX.second, y); + x = nextX.second; + y = nextY.second; + map.add(x, y); + xIntersects.removeFirst(); + yIntersects.removeFirst(); + } + } + + while (!xIntersects.isEmpty()) { + x = xIntersects.takeFirst().second; + map.add(x, y); + if (yIntegral && yFixed) + map.add(x, yOther); + + } + + while (!yIntersects.isEmpty()) { + y = yIntersects.takeFirst().second; + map.add(x, y); + if (xIntegral && xFixed) + map.add(xOther, y); + } + } + + QSet results; + + int z = m_intZoomLevel; + + typedef QMap >::const_iterator iter; + iter i = map.data.constBegin(); + iter end = map.data.constEnd(); + + for (; i != end; ++i) { + int y = i.key(); + int minX = i->first; + int maxX = i->second; + for (int x = minX; x <= maxX; ++x) { + results.insert(QGeoTileSpec(m_pluginString, m_mapType.mapId(), z, x, y, m_mapVersion)); + } + } + + return results; +} + +QGeoCameraTilesPrivate::TileMap::TileMap() {} + +void QGeoCameraTilesPrivate::TileMap::add(int tileX, int tileY) +{ + if (data.contains(tileY)) { + int oldMinX = data.value(tileY).first; + int oldMaxX = data.value(tileY).second; + data.insert(tileY, QPair(qMin(tileX, oldMinX), qMax(tileX, oldMaxX))); + } else { + data.insert(tileY, QPair(tileX, tileX)); + } +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeocameratiles_p.h b/src/location/maps/qgeocameratiles_p.h new file mode 100644 index 0000000..d10895f --- /dev/null +++ b/src/location/maps/qgeocameratiles_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOCAMERATILES_P_H +#define QGEOCAMERATILES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCameraData; +class QGeoTileSpec; +class QGeoMapType; +class QGeoCameraTilesPrivate; +class QSize; + +class Q_LOCATION_EXPORT QGeoCameraTiles { +public: + QGeoCameraTiles(); + ~QGeoCameraTiles(); + + void setCameraData(const QGeoCameraData &camera); + QGeoCameraData cameraData() const; + void setScreenSize(const QSize &size); + void setTileSize(int tileSize); + int tileSize() const; + void setViewExpansion(double viewExpansion); + void setPluginString(const QString &pluginString); + void setMapType(const QGeoMapType &mapType); + void setMapVersion(int mapVersion); + const QSet& createTiles(); + +protected: + QScopedPointer d_ptr; + Q_DISABLE_COPY(QGeoCameraTiles) +}; + +QT_END_NAMESPACE + +#endif // QGEOCAMERATILES_P_H diff --git a/src/location/maps/qgeocodereply.cpp b/src/location/maps/qgeocodereply.cpp new file mode 100644 index 0000000..4701821 --- /dev/null +++ b/src/location/maps/qgeocodereply.cpp @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocodereply.h" +#include "qgeocodereply_p.h" + +QT_BEGIN_NAMESPACE +/*! + \class QGeoCodeReply + \inmodule QtLocation + \ingroup QtLocation-geocoding + \since 5.6 + + \brief The QGeoCodeReply class manages an operation started by an + instance of QGeoCodingManager. + + Instances of QGeoCodeReply manage the state and results of these + operations. + + The isFinished(), error() and errorString() methods provide information + on whether the operation has completed and if it completed successfully. + + The finished() and error(QGeoCodeReply::Error,QString) + signals can be used to monitor the progress of the operation. + + It is possible that a newly created QGeoCodeReply may be in a finished + state, most commonly because an error has occurred. Since such an instance + will never emit the finished() or + error(QGeoCodeReply::Error,QString) signals, it is + important to check the result of isFinished() before making the connections + to the signals. The documentation for QGeoCodingManager demonstrates how + this might be carried out. + + If the operation completes successfully the results will be able to be + accessed with locations(). +*/ + +/*! + \enum QGeoCodeReply::Error + + Describes an error which prevented the completion of the operation. + + \value NoError + No error has occurred. + \value EngineNotSetError + The geocoding manager that was used did not have a QGeoCodingManagerEngine instance associated with it. + \value CommunicationError + An error occurred while communicating with the service provider. + \value ParseError + The response from the service provider was in an unrecognizable format. + \value UnsupportedOptionError + The requested operation or one of the options for the operation are not + supported by the service provider. + \value CombinationError + An error occurred while results where being combined from multiple sources. + \value UnknownError + An error occurred which does not fit into any of the other categories. +*/ + +/*! + Constructs a geocode reply with the specified \a parent. +*/ +QGeoCodeReply::QGeoCodeReply(QObject *parent) + : QObject(parent), + d_ptr(new QGeoCodeReplyPrivate()) {} + +/*! + Constructs a geocode reply with a given \a error and \a errorString and the specified \a parent. +*/ +QGeoCodeReply::QGeoCodeReply(Error error, const QString &errorString, QObject *parent) + : QObject(parent), + d_ptr(new QGeoCodeReplyPrivate(error, errorString)) {} + +/*! + Destroys this reply object. +*/ +QGeoCodeReply::~QGeoCodeReply() +{ + delete d_ptr; +} + +/*! + Sets whether or not this reply has finished to \a finished. + + If \a finished is true, this will cause the finished() signal to be + emitted. + + If the operation completed successfully, QGeoCodeReply::setLocations() + should be called before this function. If an error occurred, + QGeoCodeReply::setError() should be used instead. +*/ +void QGeoCodeReply::setFinished(bool finished) +{ + d_ptr->isFinished = finished; + if (d_ptr->isFinished) + emit this->finished(); +} + +/*! + Return true if the operation completed successfully or encountered an + error which cause the operation to come to a halt. +*/ +bool QGeoCodeReply::isFinished() const +{ + return d_ptr->isFinished; +} + +/*! + Sets the error state of this reply to \a error and the textual + representation of the error to \a errorString. + + This will also cause error() and finished() signals to be emitted, in that + order. +*/ +void QGeoCodeReply::setError(QGeoCodeReply::Error error, const QString &errorString) +{ + d_ptr->error = error; + d_ptr->errorString = errorString; + emit this->error(error, errorString); + setFinished(true); +} + +/*! + Returns the error state of this reply. + + If the result is QGeoCodeReply::NoError then no error has occurred. +*/ +QGeoCodeReply::Error QGeoCodeReply::error() const +{ + return d_ptr->error; +} + +/*! + Returns the textual representation of the error state of this reply. + + If no error has occurred this will return an empty string. It is possible + that an error occurred which has no associated textual representation, in + which case this will also return an empty string. + + To determine whether an error has occurred, check to see if + QGeoCodeReply::error() is equal to QGeoCodeReply::NoError. +*/ +QString QGeoCodeReply::errorString() const +{ + return d_ptr->errorString; +} + +/*! + Sets the viewport which contains the results to \a viewport. +*/ +void QGeoCodeReply::setViewport(const QGeoShape &viewport) +{ + d_ptr->viewport = viewport; +} + +/*! + Returns the viewport which contains the results. + + This function will return 0 if no viewport bias + was specified in the QGeoCodingManager function which created this reply. +*/ +QGeoShape QGeoCodeReply::viewport() const +{ + return d_ptr->viewport; +} + +/*! + Returns a list of locations. + + The locations are the results of the operation corresponding to the + QGeoCodingManager function which created this reply. +*/ +QList QGeoCodeReply::locations() const +{ + return d_ptr->locations; +} + +/*! + Adds \a location to the list of locations in this reply. +*/ +void QGeoCodeReply::addLocation(const QGeoLocation &location) +{ + d_ptr->locations.append(location); +} + +/*! + Sets the list of \a locations in the reply. +*/ +void QGeoCodeReply::setLocations(const QList &locations) +{ + d_ptr->locations = locations; +} + +/*! + Cancels the operation immediately. + + This will do nothing if the reply is finished. +*/ +void QGeoCodeReply::abort() +{ + if (!isFinished()) + setFinished(true); +} + +/*! + Returns the limit on the number of responses from each data source. + + If no limit was set this function will return -1. + + This may be more than locations().length() if the number of responses + was less than the number requested. +*/ +int QGeoCodeReply::limit() const +{ + return d_ptr->limit; +} + +/*! + Returns the offset into the entire result set at which to start + fetching results. +*/ +int QGeoCodeReply::offset() const +{ + return d_ptr->offset; +} + +/*! + Sets the limit on the number of responses from each data source to \a limit. + + If \a limit is -1 then all available responses will be returned. +*/ +void QGeoCodeReply::setLimit(int limit) +{ + d_ptr->limit = limit; +} + +/*! + Sets the offset in the entire result set at which to start + fetching result to \a offset. +*/ +void QGeoCodeReply::setOffset(int offset) +{ + d_ptr->offset = offset; +} + +/*! + \fn void QGeoCodeReply::finished() + + This signal is emitted when this reply has finished processing. + + If error() equals QGeoCodeReply::NoError then the processing + finished successfully. + + This signal and QGeoCodingManager::finished() will be + emitted at the same time. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ +/*! + \fn void QGeoCodeReply::error(QGeoCodeReply::Error error, const QString &errorString) + + This signal is emitted when an error has been detected in the processing of + this reply. The finished() signal will probably follow. + + The error will be described by the error code \a error. If \a errorString is + not empty it will contain a textual description of the error. + + This signal and QGeoCodingManager::error() will be emitted at the same time. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ + +/******************************************************************************* +*******************************************************************************/ + +QGeoCodeReplyPrivate::QGeoCodeReplyPrivate() + : error(QGeoCodeReply::NoError), + isFinished(false), + limit(-1), + offset(0) {} + +QGeoCodeReplyPrivate::QGeoCodeReplyPrivate(QGeoCodeReply::Error error, const QString &errorString) + : error(error), + errorString(errorString), + isFinished(true), + limit(-1), + offset(0) {} + +QGeoCodeReplyPrivate::~QGeoCodeReplyPrivate() {} + + +#include "moc_qgeocodereply.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeocodereply.h b/src/location/maps/qgeocodereply.h new file mode 100644 index 0000000..048493b --- /dev/null +++ b/src/location/maps/qgeocodereply.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODEREPLY_H +#define QGEOCODEREPLY_H + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoShape; +class QGeoCodeReplyPrivate; + +class Q_LOCATION_EXPORT QGeoCodeReply : public QObject +{ + Q_OBJECT + +public: + enum Error { + NoError, + EngineNotSetError, + CommunicationError, + ParseError, + UnsupportedOptionError, + CombinationError, + UnknownError + }; + + explicit QGeoCodeReply(Error error, const QString &errorString, QObject *parent = Q_NULLPTR); + virtual ~QGeoCodeReply(); + + bool isFinished() const; + Error error() const; + QString errorString() const; + + QGeoShape viewport() const; + QList locations() const; + + int limit() const; + int offset() const; + + virtual void abort(); + +Q_SIGNALS: + void finished(); + void error(QGeoCodeReply::Error error, const QString &errorString = QString()); + +protected: + explicit QGeoCodeReply(QObject *parent = Q_NULLPTR); + + void setError(Error error, const QString &errorString); + void setFinished(bool finished); + + void setViewport(const QGeoShape &viewport); + void addLocation(const QGeoLocation &location); + void setLocations(const QList &locations); + + void setLimit(int limit); + void setOffset(int offset); + +private: + QGeoCodeReplyPrivate *d_ptr; + Q_DISABLE_COPY(QGeoCodeReply) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeocodereply_p.h b/src/location/maps/qgeocodereply_p.h new file mode 100644 index 0000000..fefe788 --- /dev/null +++ b/src/location/maps/qgeocodereply_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODEREPLY_P_H +#define QGEOCODEREPLY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeocodereply.h" + +#include "qgeoshape.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoLocation; + +class QGeoCodeReplyPrivate +{ +public: + QGeoCodeReplyPrivate(); + QGeoCodeReplyPrivate(QGeoCodeReply::Error error, const QString &errorString); + ~QGeoCodeReplyPrivate(); + + QGeoCodeReply::Error error; + QString errorString; + bool isFinished; + + QGeoShape viewport; + QList locations; + + int limit; + int offset; +private: + Q_DISABLE_COPY(QGeoCodeReplyPrivate) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeocodingmanager.cpp b/src/location/maps/qgeocodingmanager.cpp new file mode 100644 index 0000000..70c5de8 --- /dev/null +++ b/src/location/maps/qgeocodingmanager.cpp @@ -0,0 +1,327 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocodingmanager.h" +#include "qgeocodingmanager_p.h" +#include "qgeocodingmanagerengine.h" + +#include "qgeorectangle.h" +#include "qgeocircle.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoCodingManager + \inmodule QtLocation + \ingroup QtLocation-geocoding + \since 5.6 + + \brief The QGeoCodingManager class provides support for geocoding + operations. + + The geocode() and reverseGeocode() functions return + QGeoCodeReply objects, which manage these operations and report on the + result of the operations and any errors which may have occurred. + + The geocode() and reverseGeocode() functions can be used to convert + QGeoAddress instances to QGeoCoordinate instances and vice-versa. + + The geocode() function is also overloaded to allow a user to perform a free text + geocoding operation, if the string provided can be interpreted as + an address it can be geocoded to coordinate information. + + Instances of QGeoCodingManager can be accessed with + QGeoServiceProvider::geocodingManager(). +*/ + +/*! + Constructs a new manager with the specified \a parent and with the + implementation provided by \a engine. + + This constructor is used interally by QGeoServiceProviderFactory. Regular + users should acquire instances of QGeoCodingManager with + QGeoServiceProvider::geocodingManager(); +*/ +QGeoCodingManager::QGeoCodingManager(QGeoCodingManagerEngine *engine, QObject *parent) + : QObject(parent), + d_ptr(new QGeoCodingManagerPrivate()) +{ + d_ptr->engine = engine; + if (d_ptr->engine) { + d_ptr->engine->setParent(this); + + connect(d_ptr->engine, + SIGNAL(finished(QGeoCodeReply*)), + this, + SIGNAL(finished(QGeoCodeReply*))); + + connect(d_ptr->engine, + SIGNAL(error(QGeoCodeReply*,QGeoCodeReply::Error,QString)), + this, + SIGNAL(error(QGeoCodeReply*,QGeoCodeReply::Error,QString))); + } else { + qFatal("The geocoding manager engine that was set for this geocoding manager was NULL."); + } +} + +/*! + Destroys this manager. +*/ +QGeoCodingManager::~QGeoCodingManager() +{ + delete d_ptr; +} + +/*! + Returns the name of the engine which implements the behaviour of this + geocoding manager. + + The combination of managerName() and managerVersion() should be unique + amongst the plugin implementations. +*/ +QString QGeoCodingManager::managerName() const +{ +// if (!d_ptr->engine) +// return QString(); + + return d_ptr->engine->managerName(); +} + +/*! + Returns the version of the engine which implements the behaviour of this + geocoding manager. + + The combination of managerName() and managerVersion() should be unique + amongst the plugin implementations. +*/ +int QGeoCodingManager::managerVersion() const +{ +// if (!d_ptr->engine) +// return -1; + + return d_ptr->engine->managerVersion(); +} + +/*! + Begins the geocoding of \a address. Geocoding is the process of finding a + coordinate that corresponds to a given address. + + A QGeoCodeReply object will be returned, which can be used to manage the + geocoding operation and to return the results of the operation. + + This manager and the returned QGeoCodeReply object will emit signals + indicating if the operation completes or if errors occur. + + If supportsGeocoding() returns false an + QGeoCodeReply::UnsupportedOptionError will occur. + + Once the operation has completed, QGeoCodeReply::locations() can be used to + retrieve the results, which will consist of a list of QGeoLocation objects. + These objects represent a combination of coordinate and address data. + + The address data returned in the results may be different from \a address. + This will usually occur if the geocoding service backend uses a different + canonical form of addresses or if \a address was only partially filled out. + + If \a bounds is non-null and is a valid QGeoShape it will be used to + limit the results to those that are contained within \a bounds. This is + particularly useful if \a address is only partially filled out, as the + service will attempt to geocode all matches for the specified data. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoCodingManager::finished(), + QGeoCodingManager::error(), QGeoCodeReply::finished() or + QGeoCodeReply::error() with deleteLater(). +*/ +QGeoCodeReply *QGeoCodingManager::geocode(const QGeoAddress &address, const QGeoShape &bounds) +{ + return d_ptr->engine->geocode(address, bounds); +} + + +/*! + Begins the reverse geocoding of \a coordinate. Reverse geocoding is the + process of finding an address that corresponds to a given coordinate. + + A QGeoCodeReply object will be returned, which can be used to manage the + reverse geocoding operation and to return the results of the operation. + + This manager and the returned QGeoCodeReply object will emit signals + indicating if the operation completes or if errors occur. + + If supportsReverseGeocoding() returns false an + QGeoCodeReply::UnsupportedOptionError will occur. + + At that point QGeoCodeReply::locations() can be used to retrieve the + results, which will consist of a list of QGeoLocation objects. These objects + represent a combination of coordinate and address data. + + The coordinate data returned in the results may be different from \a + coordinate. This will usually occur if the reverse geocoding service + backend shifts the coordinates to be closer to the matching addresses, or + if the backend returns results at multiple levels of detail. + + If multiple results are returned by the reverse geocoding service backend + they will be provided in order of specificity. This normally occurs if the + backend is configured to reverse geocode across multiple levels of detail. + As an example, some services will return address and coordinate pairs for + the street address, the city, the state and the country. + + If \a bounds is non-null and a valid QGeoRectangle it will be used to + limit the results to those that are contained within \a bounds. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoCodingManager::finished(), + QGeoCodingManager::error(), QGeoCodeReply::finished() or + QGeoCodeReply::error() with deleteLater(). +*/ +QGeoCodeReply *QGeoCodingManager::reverseGeocode(const QGeoCoordinate &coordinate, const QGeoShape &bounds) +{ + return d_ptr->engine->reverseGeocode(coordinate, bounds); +} + +/*! + Begins geocoding for a location matching \a address. + + A QGeoCodeReply object will be returned, which can be used to manage the + geocoding operation and to return the results of the operation. + + This manager and the returned QGeoCodeReply object will emit signals + indicating if the operation completes or if errors occur. + + Once the operation has completed, QGeoCodeReply::locations() can be used to + retrieve the results, which will consist of a list of QGeoLocation objects. + These objects represent a combination of coordinate and address data. + + If \a limit is -1 the entire result set will be returned, otherwise at most + \a limit results will be returned. + + The \a offset parameter is used to ask the geocoding service to not return the + first \a offset results. + + The \a limit and \a offset results are used together to implement paging. + + If \a bounds is non-null and a valid QGeoShape it will be used to + limit the results to those that are contained within \a bounds. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoCodingManager::finished(), + QGeoCodingManager::error(), QGeoCodeReply::finished() or + QGeoCodeReply::error() with deleteLater(). +*/ +QGeoCodeReply *QGeoCodingManager::geocode(const QString &address, + int limit, + int offset, + const QGeoShape &bounds) +{ + QGeoCodeReply *reply = d_ptr->engine->geocode(address, + limit, + offset, + bounds); + return reply; +} + +/*! + Sets the locale to be used by this manager to \a locale. + + If this geocoding manager supports returning the results + in different languages, they will be returned in the language of \a locale. + + The locale used defaults to the system locale if this is not set. +*/ +void QGeoCodingManager::setLocale(const QLocale &locale) +{ + d_ptr->engine->setLocale(locale); +} + +/*! + Returns the locale used to hint to this geocoding manager about what + language to use for the results. +*/ +QLocale QGeoCodingManager::locale() const +{ + return d_ptr->engine->locale(); +} + +/*! +\fn void QGeoCodingManager::finished(QGeoCodeReply *reply) + + This signal is emitted when \a reply has finished processing. + + If reply::error() equals QGeoCodeReply::NoError then the processing + finished successfully. + + This signal and QGeoCodeReply::finished() will be emitted at the same + time. + + \note Do not delete the \a reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ + +/*! +\fn void QGeoCodingManager::error(QGeoCodeReply *reply, QGeoCodeReply::Error error, QString errorString) + + This signal is emitted when an error has been detected in the processing of + \a reply. The QGeoCodingManager::finished() signal will probably follow. + + The error will be described by the error code \a error. If \a errorString is + not empty it will contain a textual description of the error. + + This signal and QGeoCodeReply::error() will be emitted at the same time. + + \note Do not delete the \a reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ + +/******************************************************************************* +*******************************************************************************/ + +QGeoCodingManagerPrivate::QGeoCodingManagerPrivate() + : engine(0) {} + +QGeoCodingManagerPrivate::~QGeoCodingManagerPrivate() +{ + delete engine; +} + +/******************************************************************************* +*******************************************************************************/ + +#include "moc_qgeocodingmanager.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeocodingmanager.h b/src/location/maps/qgeocodingmanager.h new file mode 100644 index 0000000..eb366f6 --- /dev/null +++ b/src/location/maps/qgeocodingmanager.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODINGMANAGER_H +#define QGEOCODINGMANAGER_H + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QLocale; + +class QGeoCodingManagerEngine; +class QGeoCodingManagerPrivate; + +class Q_LOCATION_EXPORT QGeoCodingManager : public QObject +{ + Q_OBJECT +public: + ~QGeoCodingManager(); + + QString managerName() const; + int managerVersion() const; + + QGeoCodeReply *geocode(const QGeoAddress &address, + const QGeoShape &bounds = QGeoShape()); + QGeoCodeReply *geocode(const QString &searchString, + int limit = -1, + int offset = 0, + const QGeoShape &bounds = QGeoShape()); + + QGeoCodeReply *reverseGeocode(const QGeoCoordinate &coordinate, + const QGeoShape &bounds = QGeoShape()); + + void setLocale(const QLocale &locale); + QLocale locale() const; + +Q_SIGNALS: + void finished(QGeoCodeReply *reply); + void error(QGeoCodeReply *reply, QGeoCodeReply::Error error, QString errorString = QString()); + +private: + explicit QGeoCodingManager(QGeoCodingManagerEngine *engine, QObject *parent = Q_NULLPTR); + + QGeoCodingManagerPrivate *d_ptr; + Q_DISABLE_COPY(QGeoCodingManager) + + friend class QGeoServiceProvider; + friend class QGeoServiceProviderPrivate; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeocodingmanager_p.h b/src/location/maps/qgeocodingmanager_p.h new file mode 100644 index 0000000..de723ee --- /dev/null +++ b/src/location/maps/qgeocodingmanager_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODINGMANAGER_P_H +#define QGEOCODINGMANAGER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeocodingmanager.h" + +#include "qgeocodereply.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoCodingManagerEngine; + +class QGeoCodingManagerPrivate +{ +public: + QGeoCodingManagerPrivate(); + ~QGeoCodingManagerPrivate(); + + QGeoCodingManagerEngine *engine; + +private: + Q_DISABLE_COPY(QGeoCodingManagerPrivate) +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/location/maps/qgeocodingmanagerengine.cpp b/src/location/maps/qgeocodingmanagerengine.cpp new file mode 100644 index 0000000..b163d17 --- /dev/null +++ b/src/location/maps/qgeocodingmanagerengine.cpp @@ -0,0 +1,336 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocodingmanagerengine.h" +#include "qgeocodingmanagerengine_p.h" + +#include "qgeoaddress.h" +#include "qgeocoordinate.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoCodingManagerEngine + \inmodule QtLocation + \ingroup QtLocation-impl + \since 5.6 + + \brief The QGeoCodingManagerEngine class provides an interface and + convenience methods to implementers of QGeoServiceProvider plugins who want + to provide support for geocoding operations. + + In the default implementation, supportsGeocoding() and supportsReverseGeocoding() returns false while + geocode() and reverseGeocode() + cause QGeoCodeReply::UnsupportedOptionError to occur. + + If the service provider supports geocoding the subclass should provide an + implementation of geocode() and call setSupportsGeocoding(true) at + some point in time before geocode() is called. + + Similarly, if the service provider supports reverse geocoding the subclass + should provide an implementation reverseGeocode() and call + setSupportsReverseGeocoding(true) at some point in time before + reverseGeocode() is called. + + A subclass of QGeoCodingManagerEngine will often make use of a subclass + fo QGeoCodeReply internally, in order to add any engine-specific + data (such as a QNetworkReply object for network-based services) to the + QGeoCodeReply instances used by the engine. + + \sa QGeoCodingManager +*/ + +/*! + Constructs a new engine with the specified \a parent, using \a parameters + to pass any implementation specific data to the engine. +*/ +QGeoCodingManagerEngine::QGeoCodingManagerEngine(const QVariantMap ¶meters, QObject *parent) + : QObject(parent), + d_ptr(new QGeoCodingManagerEnginePrivate()) +{ + Q_UNUSED(parameters) +} + +/*! + Destroys this engine. +*/ +QGeoCodingManagerEngine::~QGeoCodingManagerEngine() +{ + delete d_ptr; +} + +/*! + Sets the name which this engine implementation uses to distinguish itself + from the implementations provided by other plugins to \a managerName. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +void QGeoCodingManagerEngine::setManagerName(const QString &managerName) +{ + d_ptr->managerName = managerName; +} + +/*! + Returns the name which this engine implementation uses to distinguish + itself from the implementations provided by other plugins. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +QString QGeoCodingManagerEngine::managerName() const +{ + return d_ptr->managerName; +} + +/*! + Sets the version of this engine implementation to \a managerVersion. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +void QGeoCodingManagerEngine::setManagerVersion(int managerVersion) +{ + d_ptr->managerVersion = managerVersion; +} + +/*! + Returns the version of this engine implementation. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +int QGeoCodingManagerEngine::managerVersion() const +{ + return d_ptr->managerVersion; +} + +/*! + Begins the geocoding of \a address. Geocoding is the process of finding a + coordinate that corresponds to a given address. + + A QGeoCodeReply object will be returned, which can be used to manage the + geocoding operation and to return the results of the operation. + + This engine and the returned QGeoCodeReply object will emit signals + indicating if the operation completes or if errors occur. + + If supportsGeocoding() returns false an + QGeoCodeReply::UnsupportedOptionError will occur. + + Once the operation has completed, QGeoCodeReply::locations() can be used to + retrieve the results, which will consist of a list of QGeoLocation objects. + These objects represent a combination of coordinate and address data. + + The address data returned in the results may be different from \a address. + This will usually occur if the geocoding service backend uses a different + canonical form of addresses or if \a address was only partially filled out. + + If \a bounds is non-null and a valid QGeoShape it will be used to + limit the results to those that are contained by \a bounds. This is + particularly useful if \a address is only partially filled out, as the + service will attempt to geocode all matches for the specified data. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoCodingManagerEngine::finished(), + QGeoCodingManagerEngine::error(), QGeoCodeReply::finished() or + QGeoCodeReply::error() with deleteLater(). +*/ +QGeoCodeReply *QGeoCodingManagerEngine::geocode(const QGeoAddress &address, + const QGeoShape &bounds) +{ + Q_UNUSED(address) + Q_UNUSED(bounds) + return new QGeoCodeReply(QGeoCodeReply::UnsupportedOptionError, + QLatin1String("Geocoding is not supported by this service provider."), this); +} + +/*! + Begins the reverse geocoding of \a coordinate. Reverse geocoding is the + process of finding an address that corresponds to a given coordinate. + + A QGeoCodeReply object will be returned, which can be used to manage the + reverse geocoding operation and to return the results of the operation. + + This engine and the returned QGeoCodeReply object will emit signals + indicating if the operation completes or if errors occur. + + If supportsReverseGeocoding() returns false an + QGeoCodeReply::UnsupportedOptionError will occur. + + At that point QGeoCodeReply::locations() can be used to retrieve the + results, which will consist of a list of QGeoLocation objects. These objects + represent a combination of coordinate and address data. + + The coordinate data returned in the results may be different from \a + coordinate. This will usually occur if the reverse geocoding service + backend shifts the coordinates to be closer to the matching addresses, or + if the backend returns results at multiple levels of detail. + + If multiple results are returned by the reverse geocoding service backend + they will be provided in order of specificity. This normally occurs if the + backend is configured to reverse geocode across multiple levels of detail. + As an example, some services will return address and coordinate pairs for + the street address, the city, the state and the country. + + If \a bounds is non-null and a valid QGeoShape it will be used to + limit the results to those that are contained by \a bounds. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoCodingManagerEngine::finished(), + QGeoCodingManagerEngine::error(), QGeoCodeReply::finished() or + QGeoCodeReply::error() with deleteLater(). +*/ +QGeoCodeReply *QGeoCodingManagerEngine::reverseGeocode(const QGeoCoordinate &coordinate, + const QGeoShape &bounds) +{ + Q_UNUSED(coordinate) + Q_UNUSED(bounds) + return new QGeoCodeReply(QGeoCodeReply::UnsupportedOptionError, + QLatin1String("Reverse geocoding is not supported by this service provider."), this); +} + +/*! + Begins geocoding for a location matching \a address. + + A QGeoCodeReply object will be returned, which can be used to manage the + geocoding operation and to return the results of the operation. + + This engine and the returned QGeoCodeReply object will emit signals + indicating if the operation completes or if errors occur. + + Once the operation has completed, QGeoCodeReply::locations() can be used to + retrieve the results, which will consist of a list of QGeoLocation objects. + These objects represent a combination of coordinate and address data. + + If \a limit is -1 the entire result set will be returned, otherwise at most + \a limit results will be returned. + + The \a offset parameter is used to ask the geocoding service to not return the + first \a offset results. + + The \a limit and \a offset results are used together to implement paging. + + If \a bounds is non-null and a valid QGeoShape it will be used to + limit the results to those that are contained by \a bounds. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoCodingManagerEngine::finished(), + QGeoCodingManagerEngine::error(), QGeoCodeReply::finished() or + QGeoCodeReply::error() with deleteLater(). +*/ +QGeoCodeReply *QGeoCodingManagerEngine::geocode(const QString &address, + int limit, + int offset, + const QGeoShape &bounds) +{ + Q_UNUSED(address) + Q_UNUSED(limit) + Q_UNUSED(offset) + Q_UNUSED(bounds) + + return new QGeoCodeReply(QGeoCodeReply::UnsupportedOptionError, + QLatin1String("Searching is not supported by this service provider."), this); +} + +/*! + Sets the locale to be used by this manager to \a locale. + + If this geocoding manager supports returning the results + in different languages, they will be returned in the language of \a locale. + + The locale used defaults to the system locale if this is not set. +*/ +void QGeoCodingManagerEngine::setLocale(const QLocale &locale) +{ + d_ptr->locale = locale; +} + +/*! + Returns the locale used to hint to this geocoding manager about what + language to use for the results. +*/ +QLocale QGeoCodingManagerEngine::locale() const +{ + return d_ptr->locale; +} + +/*! +\fn void QGeoCodingManagerEngine::finished(QGeoCodeReply *reply) + + This signal is emitted when \a reply has finished processing. + + If reply::error() equals QGeoCodeReply::NoError then the processing + finished successfully. + + This signal and QGeoCodeReply::finished() will be emitted at the same + time. + + \note Do not delete the \a reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ + +/*! +\fn void QGeoCodingManagerEngine::error(QGeoCodeReply *reply, QGeoCodeReply::Error error, QString errorString) + + This signal is emitted when an error has been detected in the processing of + \a reply. The QGeoCodingManagerEngine::finished() signal will probably follow. + + The error will be described by the error code \a error. If \a errorString is + not empty it will contain a textual description of the error. + + This signal and QGeoCodeReply::error() will be emitted at the same time. + + \note Do not delete the \a reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ + +/******************************************************************************* +*******************************************************************************/ + +QGeoCodingManagerEnginePrivate::QGeoCodingManagerEnginePrivate() + : managerVersion(-1) +{} + +QGeoCodingManagerEnginePrivate::~QGeoCodingManagerEnginePrivate() +{ +} + +#include "moc_qgeocodingmanagerengine.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeocodingmanagerengine.h b/src/location/maps/qgeocodingmanagerengine.h new file mode 100644 index 0000000..ce7021a --- /dev/null +++ b/src/location/maps/qgeocodingmanagerengine.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODINGMANAGERENGINE_H +#define QGEOCODINGMANAGERENGINE_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoAddress; +class QGeoShape; +class QGeoCodingManagerEnginePrivate; + +class Q_LOCATION_EXPORT QGeoCodingManagerEngine : public QObject +{ + Q_OBJECT +public: + explicit QGeoCodingManagerEngine(const QVariantMap ¶meters, QObject *parent = Q_NULLPTR); + virtual ~QGeoCodingManagerEngine(); + + QString managerName() const; + int managerVersion() const; + + virtual QGeoCodeReply *geocode(const QGeoAddress &address, const QGeoShape &bounds); + virtual QGeoCodeReply *geocode(const QString &address, + int limit, + int offset, + const QGeoShape &bounds); + virtual QGeoCodeReply *reverseGeocode(const QGeoCoordinate &coordinate, + const QGeoShape &bounds); + + + void setLocale(const QLocale &locale); + QLocale locale() const; + +Q_SIGNALS: + void finished(QGeoCodeReply *reply); + void error(QGeoCodeReply *reply, QGeoCodeReply::Error error, QString errorString = QString()); + +private: + void setManagerName(const QString &managerName); + void setManagerVersion(int managerVersion); + + QGeoCodingManagerEnginePrivate *d_ptr; + Q_DISABLE_COPY(QGeoCodingManagerEngine) + + friend class QGeoServiceProvider; + friend class QGeoServiceProviderPrivate; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeocodingmanagerengine_p.h b/src/location/maps/qgeocodingmanagerengine_p.h new file mode 100644 index 0000000..b6fcb68 --- /dev/null +++ b/src/location/maps/qgeocodingmanagerengine_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODINGMANAGERENGINE_P_H +#define QGEOCODINGMANAGERENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeocodingmanagerengine.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCodingManagerEnginePrivate +{ +public: + QGeoCodingManagerEnginePrivate(); + ~QGeoCodingManagerEnginePrivate(); + + QString managerName; + int managerVersion; + + QLocale locale; + +private: + Q_DISABLE_COPY(QGeoCodingManagerEnginePrivate) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeofiletilecache.cpp b/src/location/maps/qgeofiletilecache.cpp new file mode 100644 index 0000000..3d381dc --- /dev/null +++ b/src/location/maps/qgeofiletilecache.cpp @@ -0,0 +1,463 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeofiletilecache_p.h" + +#include "qgeotilespec_p.h" + +#include "qgeomappingmanager_p.h" + +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QSet) + +QT_BEGIN_NAMESPACE + +class QGeoCachedTileMemory +{ +public: + ~QGeoCachedTileMemory() + { + if (cache) + cache->evictFromMemoryCache(this); + } + + QGeoTileSpec spec; + QGeoFileTileCache *cache; + QByteArray bytes; + QString format; +}; + +void QCache3QTileEvictionPolicy::aboutToBeRemoved(const QGeoTileSpec &key, QSharedPointer obj) +{ + Q_UNUSED(key); + // set the cache pointer to zero so we can't call evictFromDiskCache + obj->cache = 0; +} + +void QCache3QTileEvictionPolicy::aboutToBeEvicted(const QGeoTileSpec &key, QSharedPointer obj) +{ + Q_UNUSED(key); + Q_UNUSED(obj); + // leave the pointer set if it's a real eviction +} + +QGeoCachedTileDisk::~QGeoCachedTileDisk() +{ + if (cache) + cache->evictFromDiskCache(this); +} + +QGeoFileTileCache::QGeoFileTileCache(const QString &directory, QObject *parent) + : QAbstractGeoTileCache(parent), directory_(directory), + minTextureUsage_(0), extraTextureUsage_(0) +{ + const QString basePath = baseCacheDirectory(); + + // delete old tiles from QtLocation 5.4 or prior + // Newer version use plugin-specific subdirectories so those are not affected. + // TODO Remove cache cleanup in Qt 6 + QDir baseDir(basePath); + if (baseDir.exists()) { + const QStringList oldCacheFiles = baseDir.entryList(QDir::Files); + foreach (const QString& file, oldCacheFiles) + baseDir.remove(file); + } + + if (directory_.isEmpty()) { + directory_ = basePath; + qWarning() << "Plugin uses uninitialized QGeoFileTileCache directory which was deleted during startup"; + } + + QDir::root().mkpath(directory_); + + // default values + setMaxDiskUsage(20 * 1024 * 1024); + setMaxMemoryUsage(3 * 1024 * 1024); + setExtraTextureUsage(6 * 1024 * 1024); + + loadTiles(); +} + +void QGeoFileTileCache::loadTiles() +{ + QStringList formats; + formats << QLatin1String("*.*"); + + QDir dir(directory_); + QStringList files = dir.entryList(formats, QDir::Files); + + // Method: + // 1. read each queue file then, if each file exists, deserialize the data into the appropriate + // cache queue. + for (int i = 1; i<=4; i++) { + QString filename = dir.filePath(QString::fromLatin1("queue") + QString::number(i)); + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) + continue; + QList > queue; + QList specs; + QList costs; + while (!file.atEnd()) { + QByteArray line = file.readLine().trimmed(); + QString filename = QString::fromLatin1(line.constData(), line.length()); + if (dir.exists(filename)){ + files.removeOne(filename); + QGeoTileSpec spec = filenameToTileSpec(filename); + if (spec.zoom() == -1) + continue; + QSharedPointer tileDisk(new QGeoCachedTileDisk); + tileDisk->filename = dir.filePath(filename); + tileDisk->cache = this; + tileDisk->spec = spec; + QFileInfo fi(tileDisk->filename); + specs.append(spec); + queue.append(tileDisk); + costs.append(fi.size()); + } + } + + diskCache_.deserializeQueue(i, specs, queue, costs); + file.close(); + } + + // 2. remaining tiles that aren't registered in a queue get pushed into cache here + // this is a backup, in case the queue manifest files get deleted or out of sync due to + // the application not closing down properly + for (int i = 0; i < files.size(); ++i) { + QGeoTileSpec spec = filenameToTileSpec(files.at(i)); + if (spec.zoom() == -1) + continue; + QString filename = dir.filePath(files.at(i)); + addToDiskCache(spec, filename); + } +} + +QGeoFileTileCache::~QGeoFileTileCache() +{ + // write disk cache queues to disk + QDir dir(directory_); + for (int i = 1; i<=4; i++) { + QString filename = dir.filePath(QString::fromLatin1("queue") + QString::number(i)); + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)){ + qWarning() << "Unable to write tile cache file " << filename; + continue; + } + QList > queue; + diskCache_.serializeQueue(i, queue); + foreach (const QSharedPointer &tile, queue) { + if (tile.isNull()) + continue; + + // we just want the filename here, not the full path + int index = tile->filename.lastIndexOf(QLatin1Char('/')); + QByteArray filename = tile->filename.mid(index + 1).toLatin1() + '\n'; + file.write(filename); + } + file.close(); + } +} + +void QGeoFileTileCache::printStats() +{ + textureCache_.printStats(); + memoryCache_.printStats(); + diskCache_.printStats(); +} + +void QGeoFileTileCache::setMaxDiskUsage(int diskUsage) +{ + diskCache_.setMaxCost(diskUsage); +} + +int QGeoFileTileCache::maxDiskUsage() const +{ + return diskCache_.maxCost(); +} + +int QGeoFileTileCache::diskUsage() const +{ + return diskCache_.totalCost(); +} + +void QGeoFileTileCache::setMaxMemoryUsage(int memoryUsage) +{ + memoryCache_.setMaxCost(memoryUsage); +} + +int QGeoFileTileCache::maxMemoryUsage() const +{ + return memoryCache_.maxCost(); +} + +int QGeoFileTileCache::memoryUsage() const +{ + return memoryCache_.totalCost(); +} + +void QGeoFileTileCache::setExtraTextureUsage(int textureUsage) +{ + extraTextureUsage_ = textureUsage; + textureCache_.setMaxCost(minTextureUsage_ + extraTextureUsage_); +} + +void QGeoFileTileCache::setMinTextureUsage(int textureUsage) +{ + minTextureUsage_ = textureUsage; + textureCache_.setMaxCost(minTextureUsage_ + extraTextureUsage_); +} + +int QGeoFileTileCache::maxTextureUsage() const +{ + return textureCache_.maxCost(); +} + +int QGeoFileTileCache::minTextureUsage() const +{ + return minTextureUsage_; +} + + +int QGeoFileTileCache::textureUsage() const +{ + return textureCache_.totalCost(); +} + +void QGeoFileTileCache::clearAll() +{ + textureCache_.clear(); + memoryCache_.clear(); + diskCache_.clear(); + QDir dir(directory_); + dir.setNameFilters(QStringList() << QLatin1String("*-*-*-*.*")); + dir.setFilter(QDir::Files); + foreach (QString dirFile, dir.entryList()) { + dir.remove(dirFile); + } +} + +QSharedPointer QGeoFileTileCache::get(const QGeoTileSpec &spec) +{ + QSharedPointer tt = textureCache_.object(spec); + if (tt) + return tt; + + QSharedPointer tm = memoryCache_.object(spec); + if (tm) { + QImage image; + if (!image.loadFromData(tm->bytes)) { + handleError(spec, QLatin1String("Problem with tile image")); + return QSharedPointer(0); + } + QSharedPointer tt = addToTextureCache(spec, image); + if (tt) + return tt; + } + + QSharedPointer td = diskCache_.object(spec); + if (td) { + const QString format = QFileInfo(td->filename).suffix(); + QFile file(td->filename); + file.open(QIODevice::ReadOnly); + QByteArray bytes = file.readAll(); + file.close(); + + QImage image; + if (!image.loadFromData(bytes)) { + handleError(spec, QLatin1String("Problem with tile image")); + return QSharedPointer(0); + } + + addToMemoryCache(spec, bytes, format); + QSharedPointer tt = addToTextureCache(td->spec, image); + if (tt) + return tt; + } + + return QSharedPointer(); +} + +void QGeoFileTileCache::insert(const QGeoTileSpec &spec, + const QByteArray &bytes, + const QString &format, + QGeoTiledMappingManagerEngine::CacheAreas areas) +{ + if (bytes.isEmpty()) + return; + + if (areas & QGeoTiledMappingManagerEngine::DiskCache) { + QString filename = tileSpecToFilename(spec, format, directory_); + QFile file(filename); + file.open(QIODevice::WriteOnly); + file.write(bytes); + file.close(); + + addToDiskCache(spec, filename); + } + + if (areas & QGeoTiledMappingManagerEngine::MemoryCache) { + addToMemoryCache(spec, bytes, format); + } + + /* inserts do not hit the texture cache -- this actually reduces overall + * cache hit rates because many tiles come too late to be useful + * and act as a poison */ +} + +void QGeoFileTileCache::evictFromDiskCache(QGeoCachedTileDisk *td) +{ + QFile::remove(td->filename); +} + +void QGeoFileTileCache::evictFromMemoryCache(QGeoCachedTileMemory * /* tm */) +{ +} + +QSharedPointer QGeoFileTileCache::addToDiskCache(const QGeoTileSpec &spec, const QString &filename) +{ + QSharedPointer td(new QGeoCachedTileDisk); + td->spec = spec; + td->filename = filename; + td->cache = this; + + QFileInfo fi(filename); + int diskCost = fi.size(); + diskCache_.insert(spec, td, diskCost); + return td; +} + +QSharedPointer QGeoFileTileCache::addToMemoryCache(const QGeoTileSpec &spec, const QByteArray &bytes, const QString &format) +{ + QSharedPointer tm(new QGeoCachedTileMemory); + tm->spec = spec; + tm->cache = this; + tm->bytes = bytes; + tm->format = format; + + int cost = bytes.size(); + memoryCache_.insert(spec, tm, cost); + + return tm; +} + +QSharedPointer QGeoFileTileCache::addToTextureCache(const QGeoTileSpec &spec, const QImage &image) +{ + QSharedPointer tt(new QGeoTileTexture); + tt->spec = spec; + tt->image = image; + + int textureCost = image.width() * image.height() * image.depth() / 8; + textureCache_.insert(spec, tt, textureCost); + + return tt; +} + +QString QGeoFileTileCache::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory) +{ + QString filename = spec.plugin(); + filename += QLatin1String("-"); + filename += QString::number(spec.mapId()); + filename += QLatin1String("-"); + filename += QString::number(spec.zoom()); + filename += QLatin1String("-"); + filename += QString::number(spec.x()); + filename += QLatin1String("-"); + filename += QString::number(spec.y()); + + //Append version if real version number to ensure backwards compatibility and eviction of old tiles + if (spec.version() != -1) { + filename += QLatin1String("-"); + filename += QString::number(spec.version()); + } + + filename += QLatin1String("."); + filename += format; + + QDir dir = QDir(directory); + + return dir.filePath(filename); +} + +QGeoTileSpec QGeoFileTileCache::filenameToTileSpec(const QString &filename) +{ + QGeoTileSpec emptySpec; + + QStringList parts = filename.split('.'); + + if (parts.length() != 2) + return emptySpec; + + QString name = parts.at(0); + QStringList fields = name.split('-'); + + int length = fields.length(); + if (length != 5 && length != 6) + return emptySpec; + + QList numbers; + + bool ok = false; + for (int i = 1; i < length; ++i) { + ok = false; + int value = fields.at(i).toInt(&ok); + if (!ok) + return emptySpec; + numbers.append(value); + } + + //File name without version, append default + if (numbers.length() < 5) + numbers.append(-1); + + return QGeoTileSpec(fields.at(0), + numbers.at(0), + numbers.at(1), + numbers.at(2), + numbers.at(3), + numbers.at(4)); +} + +QString QGeoFileTileCache::directory() const +{ + return directory_; +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeofiletilecache_p.h b/src/location/maps/qgeofiletilecache_p.h new file mode 100644 index 0000000..bd3e684 --- /dev/null +++ b/src/location/maps/qgeofiletilecache_p.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOFILETILECACHE_P_H +#define QGEOFILETILECACHE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include +#include +#include "qcache3q_p.h" +#include +#include +#include + +#include "qgeotilespec_p.h" +#include "qgeotiledmappingmanagerengine_p.h" +#include "qabstractgeotilecache_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoMappingManager; + +class QGeoTile; +class QGeoCachedTileMemory; +class QGeoFileTileCache; + +class QPixmap; +class QThread; + +/* This would be internal to qgeofiletilecache.cpp except that the eviction + * policy can't be defined without it being concrete here */ +class QGeoCachedTileDisk +{ +public: + ~QGeoCachedTileDisk(); + + QGeoTileSpec spec; + QString filename; + QString format; + QGeoFileTileCache *cache; +}; + +/* Custom eviction policy for the disk cache, to avoid deleting all the files + * when the application closes */ +class QCache3QTileEvictionPolicy : public QCache3QDefaultEvictionPolicy +{ +protected: + void aboutToBeRemoved(const QGeoTileSpec &key, QSharedPointer obj); + void aboutToBeEvicted(const QGeoTileSpec &key, QSharedPointer obj); +}; + +class Q_LOCATION_EXPORT QGeoFileTileCache : public QAbstractGeoTileCache +{ + Q_OBJECT +public: + QGeoFileTileCache(const QString &directory = QString(), QObject *parent = 0); + ~QGeoFileTileCache(); + + void setMaxDiskUsage(int diskUsage) Q_DECL_OVERRIDE; + int maxDiskUsage() const Q_DECL_OVERRIDE; + int diskUsage() const Q_DECL_OVERRIDE; + + void setMaxMemoryUsage(int memoryUsage) Q_DECL_OVERRIDE; + int maxMemoryUsage() const Q_DECL_OVERRIDE; + int memoryUsage() const Q_DECL_OVERRIDE; + + void setMinTextureUsage(int textureUsage) Q_DECL_OVERRIDE; + void setExtraTextureUsage(int textureUsage) Q_DECL_OVERRIDE; + int maxTextureUsage() const Q_DECL_OVERRIDE; + int minTextureUsage() const Q_DECL_OVERRIDE; + int textureUsage() const Q_DECL_OVERRIDE; + void clearAll() Q_DECL_OVERRIDE; + + QSharedPointer get(const QGeoTileSpec &spec) Q_DECL_OVERRIDE; + + // can be called without a specific tileCache pointer + static void evictFromDiskCache(QGeoCachedTileDisk *td); + static void evictFromMemoryCache(QGeoCachedTileMemory *tm); + + void insert(const QGeoTileSpec &spec, + const QByteArray &bytes, + const QString &format, + QGeoTiledMappingManagerEngine::CacheAreas areas = QGeoTiledMappingManagerEngine::AllCaches) Q_DECL_OVERRIDE; + +private: + void printStats() Q_DECL_OVERRIDE; + void loadTiles(); + + QString directory() const; + + QSharedPointer addToDiskCache(const QGeoTileSpec &spec, const QString &filename); + QSharedPointer addToMemoryCache(const QGeoTileSpec &spec, const QByteArray &bytes, const QString &format); + QSharedPointer addToTextureCache(const QGeoTileSpec &spec, const QImage &image); + + static QString tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory); + static QGeoTileSpec filenameToTileSpec(const QString &filename); + + QCache3Q diskCache_; + QCache3Q memoryCache_; + QCache3Q textureCache_; + + QString directory_; + + int minTextureUsage_; + int extraTextureUsage_; +}; + +QT_END_NAMESPACE + +#endif // QGEOFILETILECACHE_P_H diff --git a/src/location/maps/qgeomaneuver.cpp b/src/location/maps/qgeomaneuver.cpp new file mode 100644 index 0000000..f38cb29 --- /dev/null +++ b/src/location/maps/qgeomaneuver.cpp @@ -0,0 +1,318 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomaneuver.h" +#include "qgeomaneuver_p.h" + +#include "qgeocoordinate.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoManeuver + \inmodule QtLocation + \ingroup QtLocation-routing + \since 5.6 + + \brief The QGeoManeuver class represents the information relevant to the + point at which two QGeoRouteSegments meet. + + QGeoRouteSegment instances can be thought of as edges on a routing + graph, with QGeoManeuver instances as optional labels attached to the + vertices of the graph. + + The most interesting information help in a QGeoManeuver instance is + normally the textual navigation to provide and the position at which to + provide it, accessible by instructionText() and position() respectively. + + It is also possible to determine if a routing waypoint has been passed by + checking if waypoint() returns a valid QGeoCoordinate. +*/ + +/*! +\enum QGeoManeuver::InstructionDirection + +Describes the change in direction associated with the instruction text +that is associated with a QGeoManaeuver. + +\value NoDirection +There is no direction associated with the instruction text. + +\value DirectionForward +The instruction indicates that the direction of travel does not need to change. + +\value DirectionBearRight +The instruction indicates that the direction of travel should bear to the right. + +\value DirectionLightRight +The instruction indicates that a light turn to the right is required. + +\value DirectionRight +The instruction indicates that a turn to the right is required. + +\value DirectionHardRight +The instruction indicates that a hard turn to the right is required. + +\value DirectionUTurnRight +The instruction indicates that a u-turn to the right is required. + +\value DirectionUTurnLeft +The instruction indicates that a u-turn to the left is required. + +\value DirectionHardLeft +The instruction indicates that a hard turn to the left is required. + +\value DirectionLeft +The instruction indicates that a turn to the left is required. + +\value DirectionLightLeft +The instruction indicates that a light turn to the left is required. + +\value DirectionBearLeft +The instruction indicates that the direction of travel should bear to the left. + +*/ + +/*! + Constructs a invalid maneuver object. + + The maneuver will remain invalid until one of + setPosition(), setInstructionText(), setDirection(), + setTimeToNextInstruction(), setDistanceToNextInstruction() or + setWaypoint() is called. +*/ +QGeoManeuver::QGeoManeuver() + : d_ptr(new QGeoManeuverPrivate()) {} + +/*! + Constructs a maneuver object from the contents of \a other. +*/ +QGeoManeuver::QGeoManeuver(const QGeoManeuver &other) + : d_ptr(other.d_ptr) {} + +/*! + Destroys this maneuver object. +*/ +QGeoManeuver::~QGeoManeuver() {} + +/*! + Assigns \a other to this maneuver object and then returns + a reference to this maneuver object. +*/ +QGeoManeuver &QGeoManeuver::operator= (const QGeoManeuver & other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns whether this maneuver is equal to \a other. +*/ +bool QGeoManeuver::operator== (const QGeoManeuver &other) const +{ + return (*(d_ptr.constData()) == *(other.d_ptr.constData())); +} + +/*! + Returns whether this maneuver is not equal to \a other. +*/ +bool QGeoManeuver::operator!= (const QGeoManeuver &other) const +{ + return !(operator==(other)); +} + +/*! + Returns whether this maneuver is valid or not. + + Invalid maneuvers are used when there is no information + that needs to be attached to the endpoint of a QGeoRouteSegment instance. +*/ +bool QGeoManeuver::isValid() const +{ + return d_ptr->valid; +} + +/*! + Sets the position where instructionText() should be displayed to \a + position. +*/ +void QGeoManeuver::setPosition(const QGeoCoordinate &position) +{ + d_ptr->valid = true; + d_ptr->position = position; +} + +/*! + Returns the position where instructionText() should be displayed. +*/ +QGeoCoordinate QGeoManeuver::position() const +{ + return d_ptr->position; +} + +/*! + Sets the textual navigation instructions to \a instructionText. +*/ +void QGeoManeuver::setInstructionText(const QString &instructionText) +{ + d_ptr->valid = true; + d_ptr->text = instructionText; +} + +/*! + Returns the textual navigation instructions. +*/ +QString QGeoManeuver::instructionText() const +{ + return d_ptr->text; +} + +/*! + Sets the direction associated with the associated instruction to \a + direction. +*/ +void QGeoManeuver::setDirection(QGeoManeuver::InstructionDirection direction) +{ + d_ptr->valid = true; + d_ptr->direction = direction; +} + +/*! + Returns the direction associated with the associated instruction. +*/ +QGeoManeuver::InstructionDirection QGeoManeuver::direction() const +{ + return d_ptr->direction; +} + +/*! + Sets the estimated time it will take to travel from the point at which the + associated instruction was issued and the point that the next instruction + should be issued, in seconds, to \a secs. +*/ +void QGeoManeuver::setTimeToNextInstruction(int secs) +{ + d_ptr->valid = true; + d_ptr->timeToNextInstruction = secs; +} + +/*! + Returns the estimated time it will take to travel from the point at which + the associated instruction was issued and the point that the next + instruction should be issued, in seconds. +*/ +int QGeoManeuver::timeToNextInstruction() const +{ + return d_ptr->timeToNextInstruction; +} + +/*! + Sets the distance, in meters, between the point at which the associated + instruction was issued and the point that the next instruction should be + issued to \a distance. +*/ +void QGeoManeuver::setDistanceToNextInstruction(qreal distance) +{ + d_ptr->valid = true; + d_ptr->distanceToNextInstruction = distance; +} + +/*! + Returns the distance, in meters, between the point at which the associated + instruction was issued and the point that the next instruction should be + issued. +*/ +qreal QGeoManeuver::distanceToNextInstruction() const +{ + return d_ptr->distanceToNextInstruction; +} + +/*! + Sets the waypoint associated with this maneuver to \a coordinate. +*/ +void QGeoManeuver::setWaypoint(const QGeoCoordinate &coordinate) +{ + d_ptr->valid = true; + d_ptr->waypoint = coordinate; +} + +/*! + Returns the waypoint associated with this maneuver. + + If there is not waypoint associated with this maneuver an invalid + QGeoCoordinate will be returned. +*/ +QGeoCoordinate QGeoManeuver::waypoint() const +{ + return d_ptr->waypoint; +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoManeuverPrivate::QGeoManeuverPrivate() + : valid(false), + direction(QGeoManeuver::NoDirection), + timeToNextInstruction(0), + distanceToNextInstruction(0.0) {} + +QGeoManeuverPrivate::QGeoManeuverPrivate(const QGeoManeuverPrivate &other) + : QSharedData(other), + valid(other.valid), + position(other.position), + text(other.text), + direction(other.direction), + timeToNextInstruction(other.timeToNextInstruction), + distanceToNextInstruction(other.distanceToNextInstruction), + waypoint(other.waypoint) {} + +QGeoManeuverPrivate::~QGeoManeuverPrivate() {} + +bool QGeoManeuverPrivate::operator ==(const QGeoManeuverPrivate &other) const +{ + return ((valid == other.valid) + && (position == other.position) + && (text == other.text) + && (direction == other.direction) + && (timeToNextInstruction == other.timeToNextInstruction) + && (distanceToNextInstruction == other.distanceToNextInstruction) + && (waypoint == other.waypoint)); +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeomaneuver.h b/src/location/maps/qgeomaneuver.h new file mode 100644 index 0000000..9710f8f --- /dev/null +++ b/src/location/maps/qgeomaneuver.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMANEUVER_H +#define QGEOMANEUVER_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QString; + +class QGeoCoordinate; +class QGeoManeuverPrivate; + +class Q_LOCATION_EXPORT QGeoManeuver +{ + +public: + enum InstructionDirection { + NoDirection, + DirectionForward, + DirectionBearRight, + DirectionLightRight, + DirectionRight, + DirectionHardRight, + DirectionUTurnRight, + DirectionUTurnLeft, + DirectionHardLeft, + DirectionLeft, + DirectionLightLeft, + DirectionBearLeft + }; + + QGeoManeuver(); + QGeoManeuver(const QGeoManeuver &other); + ~QGeoManeuver(); + + QGeoManeuver &operator= (const QGeoManeuver &other); + + bool operator== (const QGeoManeuver &other) const; + bool operator!= (const QGeoManeuver &other) const; + + bool isValid() const; + + void setPosition(const QGeoCoordinate &position); + QGeoCoordinate position() const; + + void setInstructionText(const QString &instructionText); + QString instructionText() const; + + void setDirection(InstructionDirection direction); + InstructionDirection direction() const; + + void setTimeToNextInstruction(int secs); + int timeToNextInstruction() const; + + void setDistanceToNextInstruction(qreal distance); + qreal distanceToNextInstruction() const; + + void setWaypoint(const QGeoCoordinate &coordinate); + QGeoCoordinate waypoint() const; + +private: + QSharedDataPointer d_ptr; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeomaneuver_p.h b/src/location/maps/qgeomaneuver_p.h new file mode 100644 index 0000000..c048f13 --- /dev/null +++ b/src/location/maps/qgeomaneuver_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMANEUVER_P_H +#define QGEOMANEUVER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeomaneuver.h" +#include "qgeocoordinate.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoManeuverPrivate : public QSharedData +{ +public: + QGeoManeuverPrivate(); + QGeoManeuverPrivate(const QGeoManeuverPrivate &other); + ~QGeoManeuverPrivate(); + + bool operator== (const QGeoManeuverPrivate &other) const; + + bool valid; + QString id; + QGeoCoordinate position; + QString text; + QGeoManeuver::InstructionDirection direction; + int timeToNextInstruction; + qreal distanceToNextInstruction; + QGeoCoordinate waypoint; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeomap.cpp b/src/location/maps/qgeomap.cpp new file mode 100644 index 0000000..c3b01a3 --- /dev/null +++ b/src/location/maps/qgeomap.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomap_p.h" +#include "qgeomap_p_p.h" +#include "qgeocameracapabilities_p.h" +#include "qgeomappingmanagerengine_p.h" +#include + +QT_BEGIN_NAMESPACE + +QGeoMap::QGeoMap(QGeoMapPrivate &dd, QObject *parent) + : QObject(dd,parent) +{ +} + +QGeoMap::~QGeoMap() +{ +} + +void QGeoMap::setSize(const QSize& size) +{ + Q_D(QGeoMap); + if (size == d->m_size) + return; + d->m_size = size; + d->changeMapSize(size); +} + +QSize QGeoMap::size() const +{ + Q_D(const QGeoMap); + return d->m_size; +} + +int QGeoMap::width() const +{ + Q_D(const QGeoMap); + return d->m_size.width(); +} + +int QGeoMap::height() const +{ + Q_D(const QGeoMap); + return d->m_size.height(); +} + +void QGeoMap::setCameraData(const QGeoCameraData &cameraData) +{ + Q_D(QGeoMap); + if (cameraData == d->m_cameraData) + return; + d->m_cameraData = cameraData; + d->changeCameraData(cameraData); + emit cameraDataChanged(d->m_cameraData); +} + +QGeoCameraData QGeoMap::cameraData() const +{ + Q_D(const QGeoMap); + return d->m_cameraData; +} + +void QGeoMap::setActiveMapType(const QGeoMapType type) +{ + Q_D(QGeoMap); + if (type == d->m_activeMapType) + return; + d->m_activeMapType = type; + d->changeActiveMapType(type); + emit activeMapTypeChanged(); +} + +const QGeoMapType QGeoMap::activeMapType() const +{ + Q_D(const QGeoMap); + return d->m_activeMapType; +} + + +QGeoCameraCapabilities QGeoMap::cameraCapabilities() const +{ + Q_D(const QGeoMap); + if (!d->m_engine.isNull()) + return d->m_engine->cameraCapabilities(); + else + return QGeoCameraCapabilities(); +} + +void QGeoMap::prefetchData() +{ + +} + +void QGeoMap::clearData() +{ + +} + +QGeoMapPrivate::QGeoMapPrivate(QGeoMappingManagerEngine *engine) + : QObjectPrivate(), + m_engine(engine), + m_activeMapType(QGeoMapType()) +{ +} + +QGeoMapPrivate::~QGeoMapPrivate() +{ +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeomap_p.h b/src/location/maps/qgeomap_p.h new file mode 100644 index 0000000..021c440 --- /dev/null +++ b/src/location/maps/qgeomap_p.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOMAP_P_H +#define QGEOMAP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeocameradata_p.h" +#include "qgeomaptype_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoMappingManagerEngine; +class QGeoMapPrivate; +class QGeoMapController; +class QGeoCameraCapabilities; +class QGeoCoordinate; +class QSGNode; +class QQuickWindow; + +class Q_LOCATION_EXPORT QGeoMap : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoMap) + +public: + virtual ~QGeoMap(); + + void setSize(const QSize& size); + QSize size() const; + int width() const; + int height() const; + + + QGeoCameraData cameraData() const; + QGeoCameraCapabilities cameraCapabilities() const; + + void setActiveMapType(const QGeoMapType mapType); + const QGeoMapType activeMapType() const; + + virtual QGeoCoordinate itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport = true) const = 0; + virtual QDoubleVector2D coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport = true) const = 0; + + virtual double minimumZoomForMapSize(int width, int height) const = 0; + virtual double maximumLatitudeForZoom(double zoomLevel) const = 0; + + virtual QDoubleVector2D referenceCoordinateToItemPosition(const QGeoCoordinate &coordinate) const = 0; + virtual QGeoCoordinate referenceItemPositionToCoordinate(const QDoubleVector2D &pos) const = 0; + + virtual void prefetchData(); + virtual void clearData(); + +protected: + QGeoMap(QGeoMapPrivate &dd, QObject *parent = 0); + void setCameraData(const QGeoCameraData &cameraData); + virtual QSGNode *updateSceneGraph(QSGNode *node, QQuickWindow *window) = 0; + +Q_SIGNALS: + void cameraDataChanged(const QGeoCameraData &cameraData); + void sgNodeChanged(); + void activeMapTypeChanged(); + void copyrightsChanged(const QImage ©rightsImage); + void copyrightsChanged(const QString ©rightsHtml); + +private: + Q_DISABLE_COPY(QGeoMap) + friend class QGeoMapController; //setCameraData + friend class QDeclarativeGeoMap; //updateSceneGraph +}; + +QT_END_NAMESPACE + +#endif // QGEOMAP_P_H diff --git a/src/location/maps/qgeomap_p_p.h b/src/location/maps/qgeomap_p_p.h new file mode 100644 index 0000000..f09d55c --- /dev/null +++ b/src/location/maps/qgeomap_p_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOMAP_P_P_H +#define QGEOMAP_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeocameradata_p.h" +#include "qgeomaptype_p.h" +#include +#include + + +QT_BEGIN_NAMESPACE + +class QGeoMappingManagerEngine; +class QGeoMap; +class QGeoMapController; + +class QGeoMapPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGeoMap) +public: + QGeoMapPrivate(QGeoMappingManagerEngine *engine); + virtual ~QGeoMapPrivate(); + +protected: + virtual void changeMapSize(const QSize &size) = 0; + virtual void changeCameraData(const QGeoCameraData &oldCameraData) = 0; + virtual void changeActiveMapType(const QGeoMapType mapType) = 0; + +protected: + QSize m_size; + QPointer m_engine; + QGeoMapController *m_controller; + QGeoCameraData m_cameraData; + QGeoMapType m_activeMapType; +}; + +QT_END_NAMESPACE + +#endif // QGEOMAP_P_P_H diff --git a/src/location/maps/qgeomappingmanager.cpp b/src/location/maps/qgeomappingmanager.cpp new file mode 100644 index 0000000..12e61c7 --- /dev/null +++ b/src/location/maps/qgeomappingmanager.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomappingmanager_p.h" +#include "qgeomappingmanager_p_p.h" +#include "qgeomappingmanagerengine_p.h" +#include "qgeotiledmapreply_p.h" +#include "qgeocameracapabilities_p.h" + + +#include "qgeomap_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoMappingManager + \inmodule QtLocation + \ingroup QtLocation-maps + \since 5.6 + \internal + + \brief The QGeoMappingManager class provides support for displaying + and interacting with maps. +*/ + +/*! + Constructs a new manager with the specified \a parent and with the + implementation provided by \a engine. + + This constructor is used internally by QGeoServiceProviderFactory. Regular + users should acquire instances of QGeoMappingManager with + QGeoServiceProvider::mappingManager() +*/ +QGeoMappingManager::QGeoMappingManager(QGeoMappingManagerEngine *engine, QObject *parent) + : QObject(parent), + d_ptr(new QGeoMappingManagerPrivate) +{ + d_ptr->engine = engine; + if (!d_ptr->engine) { + qFatal("The mapping manager engine that was set for this mapping manager was NULL."); + } + + connect(d_ptr->engine, + SIGNAL(initialized()), + this, + SIGNAL(initialized()), + Qt::QueuedConnection); + + connect(d_ptr->engine, + SIGNAL(supportedMapTypesChanged()), + this, + SIGNAL(supportedMapTypesChanged()), + Qt::QueuedConnection); +} + +/*! + Destroys this mapping manager. +*/ +QGeoMappingManager::~QGeoMappingManager() +{ + delete d_ptr; +} + +/*! + \fn void QGeoMappingManager::initialized() + + This signal is emitted when the mapping manager has been initialized + and is ready to be used. +*/ + +/*! + Returns the name of the engine which implements the behaviour of this + mapping manager. + + The combination of managerName() and managerVersion() should be unique + amongst the plugin implementations. +*/ +QString QGeoMappingManager::managerName() const +{ + return d_ptr->engine->managerName(); +} + +/*! + Returns the version of the engine which implements the behaviour of this + mapping manager. + + The combination of managerName() and managerVersion() should be unique + amongst the plugin implementations. +*/ +int QGeoMappingManager::managerVersion() const +{ + return d_ptr->engine->managerVersion(); +} + +QGeoCameraCapabilities QGeoMappingManager::cameraCapabilities() const +{ + return d_ptr->engine->cameraCapabilities(); +} + +/*! + Returns a new QGeoMap instance which will be managed by this manager. +*/ +QGeoMap *QGeoMappingManager::createMap(QObject *parent) +{ + QGeoMap * map = d_ptr->engine->createMap(); + if (map) + connect(parent, &QObject::destroyed,map, &QGeoMap::deleteLater); + return map; +} + +QList QGeoMappingManager::supportedMapTypes() const +{ + return d_ptr->engine->supportedMapTypes(); +} + +/*! + Return whether the manager has been initialized + (will be done automatically but may take some time). + +*/ +bool QGeoMappingManager::isInitialized() const +{ + return d_ptr->engine->isInitialized(); +} + +/*! + Sets the locale to be used by the this manager to \a locale. + + If this mapping manager supports returning map labels + in different languages, they will be returned in the language of \a locale. + + The locale used defaults to the system locale if this is not set. +*/ +void QGeoMappingManager::setLocale(const QLocale &locale) +{ + d_ptr->engine->setLocale(locale); +} + +/*! + Returns the locale used to hint to this mapping manager about what + language to use for map labels. +*/ +QLocale QGeoMappingManager::locale() const +{ + return d_ptr->engine->locale(); +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoMappingManagerPrivate::QGeoMappingManagerPrivate() + : engine(0) {} + +QGeoMappingManagerPrivate::~QGeoMappingManagerPrivate() +{ + delete engine; + engine = 0; +} + +#include "moc_qgeomappingmanager_p.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeomappingmanager_p.h b/src/location/maps/qgeomappingmanager_p.h new file mode 100644 index 0000000..2ec1e8b --- /dev/null +++ b/src/location/maps/qgeomappingmanager_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPPINGMANAGER_H +#define QGEOMAPPINGMANAGER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include "qgeomaptype_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoMap; +class QLocale; +class QGeoRectangle; +class QGeoCoordinate; +class QGeoMappingManagerPrivate; +class QGeoMapRequestOptions; +class QGeoMappingManagerEngine; +class QGeoCameraCapabilities; + + +class Q_LOCATION_EXPORT QGeoMappingManager : public QObject +{ + Q_OBJECT + +public: + ~QGeoMappingManager(); + + QString managerName() const; + int managerVersion() const; + + QGeoMap *createMap(QObject *parent); + + QList supportedMapTypes() const; + + bool isInitialized() const; + + QGeoCameraCapabilities cameraCapabilities() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + +Q_SIGNALS: + void initialized(); + void supportedMapTypesChanged(); + +protected: + QGeoMappingManager(QGeoMappingManagerEngine *engine, QObject *parent = 0); + +private: + QGeoMappingManagerPrivate *d_ptr; + Q_DISABLE_COPY(QGeoMappingManager) + + friend class QGeoServiceProvider; + friend class QGeoServiceProviderPrivate; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeomappingmanager_p_p.h b/src/location/maps/qgeomappingmanager_p_p.h new file mode 100644 index 0000000..65d1b5e --- /dev/null +++ b/src/location/maps/qgeomappingmanager_p_p.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPPINGMANAGER_P_H +#define QGEOMAPPINGMANAGER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoMappingManagerPrivate +{ +public: + QGeoMappingManagerPrivate(); + ~QGeoMappingManagerPrivate(); + + QGeoMappingManagerEngine *engine; + +private: + Q_DISABLE_COPY(QGeoMappingManagerPrivate) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeomappingmanagerengine.cpp b/src/location/maps/qgeomappingmanagerengine.cpp new file mode 100644 index 0000000..0eeadf7 --- /dev/null +++ b/src/location/maps/qgeomappingmanagerengine.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomappingmanagerengine_p.h" +#include "qgeomappingmanagerengine_p_p.h" +#include "qgeotiledmapreply_p.h" +#include "qgeotilespec_p.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoMappingManagerEngine + \inmodule QtLocation + \ingroup QtLocation-impl + \since 5.6 + \internal + + \brief Provides support functionality for map display with QGeoServiceProvider. + + The QGeoMappingManagerEngine class provides an interface and convenience + methods to implementors of QGeoServiceProvider plugins who want to + provide support for displaying and interacting with maps. +*/ + +/*! + Constructs a new engine with the specified \a parent. +*/ +QGeoMappingManagerEngine::QGeoMappingManagerEngine(QObject *parent) + : QObject(parent), + d_ptr(new QGeoMappingManagerEnginePrivate()) {} + +/*! + Destroys this engine. +*/ +QGeoMappingManagerEngine::~QGeoMappingManagerEngine() +{ + Q_D(QGeoMappingManagerEngine); + delete d; +} + +/*! + Marks the engine as initialized. Subclasses of QGeoMappingManagerEngine are to + call this method after performing implementation-specific initializatioin within + the constructor. +*/ +void QGeoMappingManagerEngine::engineInitialized() +{ + Q_D(QGeoMappingManagerEngine); + d->initialized = true; + emit initialized(); +} + +/*! + Sets the name which this engine implementation uses to distinguish itself + from the implementations provided by other plugins to \a managerName. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +void QGeoMappingManagerEngine::setManagerName(const QString &managerName) +{ + d_ptr->managerName = managerName; +} + +/*! + Returns the name which this engine implementation uses to distinguish + itself from the implementations provided by other plugins. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +QString QGeoMappingManagerEngine::managerName() const +{ + return d_ptr->managerName; +} + +/*! + Sets the version of this engine implementation to \a managerVersion. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +void QGeoMappingManagerEngine::setManagerVersion(int managerVersion) +{ + d_ptr->managerVersion = managerVersion; +} + +/*! + Returns the version of this engine implementation. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +int QGeoMappingManagerEngine::managerVersion() const +{ + return d_ptr->managerVersion; +} + +QList QGeoMappingManagerEngine::supportedMapTypes() const +{ + Q_D(const QGeoMappingManagerEngine); + return d->supportedMapTypes; +} + +/*! + Sets the list of map types supported by this engine to \a mapTypes. + + Subclasses of QGeoMappingManagerEngine should use this function to ensure + that supportedMapTypes() provides accurate information. +*/ +void QGeoMappingManagerEngine::setSupportedMapTypes(const QList &supportedMapTypes) +{ + Q_D(QGeoMappingManagerEngine); + d->supportedMapTypes = supportedMapTypes; + emit supportedMapTypesChanged(); +} + +QGeoCameraCapabilities QGeoMappingManagerEngine::cameraCapabilities() const +{ + Q_D(const QGeoMappingManagerEngine); + return d->capabilities_; +} + +void QGeoMappingManagerEngine::setCameraCapabilities(const QGeoCameraCapabilities &capabilities) +{ + Q_D(QGeoMappingManagerEngine); + d->capabilities_ = capabilities; +} + +/*! + Return whether the engine has been initialized and is ready to be used. +*/ + +bool QGeoMappingManagerEngine::isInitialized() const +{ + Q_D(const QGeoMappingManagerEngine); + return d->initialized; +} + +/*! + Sets the locale to be used by the this manager to \a locale. + + If this mapping manager supports returning map labels + in different languages, they will be returned in the language of \a locale. + + The locale used defaults to the system locale if this is not set. +*/ +void QGeoMappingManagerEngine::setLocale(const QLocale &locale) +{ + d_ptr->locale = locale; +} + +/*! + Returns the locale used to hint to this mapping manager about what + language to use for map labels. +*/ +QLocale QGeoMappingManagerEngine::locale() const +{ + return d_ptr->locale; +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoMappingManagerEnginePrivate::QGeoMappingManagerEnginePrivate() + : managerVersion(-1), + initialized(false) {} + +QGeoMappingManagerEnginePrivate::~QGeoMappingManagerEnginePrivate() {} + +#include "moc_qgeomappingmanagerengine_p.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeomappingmanagerengine_p.h b/src/location/maps/qgeomappingmanagerengine_p.h new file mode 100644 index 0000000..fd48d44 --- /dev/null +++ b/src/location/maps/qgeomappingmanagerengine_p.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPPINGMANAGERENGINE_H +#define QGEOMAPPINGMANAGERENGINE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qgeomaptype_p.h" +#include "qgeomappingmanager_p.h" + +QT_BEGIN_NAMESPACE + +class QLocale; + +class QGeoRectangle; +class QGeoCoordinate; +class QGeoMappingManagerPrivate; +class QGeoMapRequestOptions; + +class QGeoMappingManagerEnginePrivate; +class QGeoMap; + +class Q_LOCATION_EXPORT QGeoMappingManagerEngine : public QObject +{ + Q_OBJECT + +public: + explicit QGeoMappingManagerEngine(QObject *parent = 0); + virtual ~QGeoMappingManagerEngine(); + + virtual QGeoMap *createMap() = 0; + + QVariantMap parameters() const; + + QString managerName() const; + int managerVersion() const; + + QList supportedMapTypes() const; + + QGeoCameraCapabilities cameraCapabilities() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + + bool isInitialized() const; + +Q_SIGNALS: + void initialized(); + void supportedMapTypesChanged(); + +protected: + void setSupportedMapTypes(const QList &supportedMapTypes); + void setCameraCapabilities(const QGeoCameraCapabilities &capabilities); + + void engineInitialized(); + +private: + QGeoMappingManagerEnginePrivate *d_ptr; + + void setManagerName(const QString &managerName); + void setManagerVersion(int managerVersion); + + Q_DECLARE_PRIVATE(QGeoMappingManagerEngine) + Q_DISABLE_COPY(QGeoMappingManagerEngine) + + friend class QGeoServiceProvider; + friend class QGeoServiceProviderPrivate; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeomappingmanagerengine_p_p.h b/src/location/maps/qgeomappingmanagerengine_p_p.h new file mode 100644 index 0000000..5442686 --- /dev/null +++ b/src/location/maps/qgeomappingmanagerengine_p_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPPINGMANAGERENGINE_P_H +#define QGEOMAPPINGMANAGERENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include "qgeomaptype_p.h" +#include "qgeomappingmanager_p.h" +#include "qgeocameracapabilities_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoTileSpec; +class QGeoTiledMapReply; + +class QGeoMappingManagerEnginePrivate +{ +public: + QGeoMappingManagerEnginePrivate(); + ~QGeoMappingManagerEnginePrivate(); + + QString managerName; + int managerVersion; + + QList supportedMapTypes; + QGeoCameraCapabilities capabilities_; + + QLocale locale; + bool initialized; + +private: + Q_DISABLE_COPY(QGeoMappingManagerEnginePrivate) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeomaptype.cpp b/src/location/maps/qgeomaptype.cpp new file mode 100644 index 0000000..b4efa1d --- /dev/null +++ b/src/location/maps/qgeomaptype.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomaptype_p.h" +#include "qgeomaptype_p_p.h" + +QT_BEGIN_NAMESPACE + +QGeoMapType::QGeoMapType() + : d_ptr(new QGeoMapTypePrivate()) {} + +QGeoMapType::QGeoMapType(const QGeoMapType &other) + : d_ptr(other.d_ptr) {} + +QGeoMapType::QGeoMapType(QGeoMapType::MapStyle style, const QString &name, + const QString &description, bool mobile, bool night, int mapId) +: d_ptr(new QGeoMapTypePrivate(style, name, description, mobile, night, mapId)) +{ +} + +QGeoMapType::~QGeoMapType() {} + +QGeoMapType &QGeoMapType::operator = (const QGeoMapType &other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +bool QGeoMapType::operator == (const QGeoMapType &other) const +{ + return (*d_ptr.constData() == *other.d_ptr.constData()); +} + +bool QGeoMapType::operator != (const QGeoMapType &other) const +{ + return !(operator ==(other)); +} + +QGeoMapType::MapStyle QGeoMapType::style() const +{ + return d_ptr->style_; +} + +QString QGeoMapType::name() const +{ + return d_ptr->name_; +} + +QString QGeoMapType::description() const +{ + return d_ptr->description_; +} + +bool QGeoMapType::mobile() const +{ + return d_ptr->mobile_; +} + +bool QGeoMapType::night() const +{ + return d_ptr->night_; +} + +int QGeoMapType::mapId() const +{ + return d_ptr->mapId_; +} + +QGeoMapTypePrivate::QGeoMapTypePrivate() +: style_(QGeoMapType::NoMap), mobile_(false), night_(false), mapId_(0) +{ +} + +QGeoMapTypePrivate::QGeoMapTypePrivate(const QGeoMapTypePrivate &other) +: QSharedData(other), style_(other.style_), name_(other.name_), description_(other.description_), + mobile_(other.mobile_), night_(other.night_), mapId_(other.mapId_) +{ +} + +QGeoMapTypePrivate::QGeoMapTypePrivate(QGeoMapType::MapStyle style, const QString &name, + const QString &description, bool mobile, bool night, + int mapId) +: style_(style), name_(name), description_(description), mobile_(mobile), night_(night), + mapId_(mapId) +{ +} + +QGeoMapTypePrivate::~QGeoMapTypePrivate() +{ +} + +bool QGeoMapTypePrivate::operator==(const QGeoMapTypePrivate &other) const +{ + return style_ == other.style_ && name_ == other.name_ && description_ == other.description_ && + mobile_ == other.mobile_ && night_ == other.night_ && mapId_ == other.mapId_; +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeomaptype_p.h b/src/location/maps/qgeomaptype_p.h new file mode 100644 index 0000000..78c7416 --- /dev/null +++ b/src/location/maps/qgeomaptype_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPTYPE_H +#define QGEOMAPTYPE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoMapTypePrivate; + +class Q_LOCATION_EXPORT QGeoMapType +{ +public: + enum MapStyle { + NoMap = 0, + StreetMap, + SatelliteMapDay, + SatelliteMapNight, + TerrainMap, + HybridMap, + TransitMap, + GrayStreetMap, + PedestrianMap, + CarNavigationMap, + CycleMap, + CustomMap = 100 + }; + + QGeoMapType(); + QGeoMapType(const QGeoMapType &other); + QGeoMapType(MapStyle style, const QString &name, const QString &description, bool mobile, + bool night, int mapId); + ~QGeoMapType(); + + QGeoMapType &operator = (const QGeoMapType &other); + + bool operator == (const QGeoMapType &other) const; + bool operator != (const QGeoMapType &other) const; + + MapStyle style() const; + QString name() const; + QString description() const; + bool mobile() const; + bool night() const; + int mapId() const; + +private: + QSharedDataPointer d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QGEOMAPTYPE_H diff --git a/src/location/maps/qgeomaptype_p_p.h b/src/location/maps/qgeomaptype_p_p.h new file mode 100644 index 0000000..2aafd37 --- /dev/null +++ b/src/location/maps/qgeomaptype_p_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPTYPE_P_H +#define QGEOMAPTYPE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include "qgeomaptype_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoMapTypePrivate : public QSharedData +{ +public: + QGeoMapTypePrivate(); + QGeoMapTypePrivate(QGeoMapType::MapStyle style, const QString &name, const QString &description, bool mobile, bool night, int mapId); + QGeoMapTypePrivate(const QGeoMapTypePrivate &other); + ~QGeoMapTypePrivate(); + + QGeoMapTypePrivate &operator = (const QGeoMapTypePrivate &other); + + bool operator == (const QGeoMapTypePrivate &other) const; + + QGeoMapType::MapStyle style_; + QString name_; + QString description_; + bool mobile_; + bool night_; + int mapId_; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoMapTypePrivate) + +#endif // QGEOMAPTYPE_P_H diff --git a/src/location/maps/qgeoroute.cpp b/src/location/maps/qgeoroute.cpp new file mode 100644 index 0000000..52fa4a5 --- /dev/null +++ b/src/location/maps/qgeoroute.cpp @@ -0,0 +1,320 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroute.h" +#include "qgeoroute_p.h" + +#include "qgeorectangle.h" +#include "qgeoroutesegment.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoRoute + \inmodule QtLocation + \ingroup QtLocation-routing + \since 5.6 + + \brief The QGeoRoute class represents a route between two points. + + A QGeoRoute object contains high level information about a route, such + as the length the route, the estimated travel time for the route, + and enough information to render a basic image of the route on a map. + + The QGeoRoute object also contains a list of QGeoRouteSegment objecs which + describe subsections of the route in greater detail. + + Routing information is normally requested using + QGeoRoutingManager::calculateRoute(), which returns a QGeoRouteReply + instance. If the operation is completed successfully the routing + information can be accessed with QGeoRouteReply::routes() + + \sa QGeoRoutingManager +*/ + +/*! + Constructs a route object. +*/ +QGeoRoute::QGeoRoute() + : d_ptr(new QGeoRoutePrivate()) {} + +/*! + Constructs a route object from the contents of \a other. +*/ +QGeoRoute::QGeoRoute(const QGeoRoute &other) + : d_ptr(other.d_ptr) {} + +/*! + Destroys this route object. +*/ +QGeoRoute::~QGeoRoute() +{ +} + +/*! + Assigns the contents of \a other to this route and returns a reference to + this route. +*/ +QGeoRoute &QGeoRoute::operator= (const QGeoRoute & other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns whether this route and \a other are equal. +*/ +bool QGeoRoute::operator ==(const QGeoRoute &other) const +{ + return (d_ptr.constData() == other.d_ptr.constData()); +} + +/*! + Returns whether this route and \a other are not equal. +*/ +bool QGeoRoute::operator !=(const QGeoRoute &other) const +{ + return (d_ptr.constData() != other.d_ptr.constData()); +} + +/*! + Sets the identifier of this route to \a id. + + Service providers which support the updating of routes commonly assign + identifiers to routes. If this route came from such a service provider changing + the identifier will probably cause route updates to stop working. +*/ +void QGeoRoute::setRouteId(const QString &id) +{ + d_ptr->id = id; +} + +/*! + Returns the identifier of this route. + + Service providers which support the updating of routes commonly assign + identifiers to routes. If this route did not come from such a service provider + the function will return an empty string. +*/ +QString QGeoRoute::routeId() const +{ + return d_ptr->id; +} + +/*! + Sets the route request which describes the criteria used in the + calculcation of this route to \a request. +*/ +void QGeoRoute::setRequest(const QGeoRouteRequest &request) +{ + d_ptr->request = request; +} + +/*! + Returns the route request which describes the criteria used in + the calculation of this route. +*/ +QGeoRouteRequest QGeoRoute::request() const +{ + return d_ptr->request; +} + +/*! + Sets the bounding box which encompasses the entire route to \a bounds. +*/ +void QGeoRoute::setBounds(const QGeoRectangle &bounds) +{ + d_ptr->bounds = bounds; +} + +/*! + Returns a bounding box which encompasses the entire route. +*/ +QGeoRectangle QGeoRoute::bounds() const +{ + return d_ptr->bounds; +} + +/*! + Sets the first route segment in the route to \a routeSegment. +*/ +void QGeoRoute::setFirstRouteSegment(const QGeoRouteSegment &routeSegment) +{ + d_ptr->firstSegment = routeSegment; +} + +/*! + Returns the first route segment in the route. + + Will return an invalid route segment if there are no route segments + associated with the route. + + The remaining route segments can be accessed sequentially with + QGeoRouteSegment::nextRouteSegment. +*/ +QGeoRouteSegment QGeoRoute::firstRouteSegment() const +{ + return d_ptr->firstSegment; +} + +/*! + Sets the estimated amount of time it will take to traverse this route, + in seconds, to \a secs. +*/ +void QGeoRoute::setTravelTime(int secs) +{ + d_ptr->travelTime = secs; +} + +/*! + Returns the estimated amount of time it will take to traverse this route, + in seconds. +*/ +int QGeoRoute::travelTime() const +{ + return d_ptr->travelTime; +} + +/*! + Sets the distance covered by this route, in meters, to \a distance. +*/ +void QGeoRoute::setDistance(qreal distance) +{ + d_ptr->distance = distance; +} + +/*! + Returns the distance covered by this route, in meters. +*/ +qreal QGeoRoute::distance() const +{ + return d_ptr->distance; +} + +/*! + Sets the travel mode for this route to \a mode. + + This should be one of the travel modes returned by request().travelModes(). +*/ +void QGeoRoute::setTravelMode(QGeoRouteRequest::TravelMode mode) +{ + d_ptr->travelMode = mode; +} + +/*! + Returns the travel mode for the this route. + + This should be one of the travel modes returned by request().travelModes(). +*/ +QGeoRouteRequest::TravelMode QGeoRoute::travelMode() const +{ + return d_ptr->travelMode; +} + +/*! + Sets the geometric shape of the route to \a path. + + The coordinates in \a path should be listed in the order in which they + would be traversed by someone traveling along this segment of the route. +*/ +void QGeoRoute::setPath(const QList &path) +{ + d_ptr->path = path; +} + +/*! + Returns the geometric shape of the route. + + The coordinates should be listed in the order in which they + would be traversed by someone traveling along this segment of the route. +*/ +QList QGeoRoute::path() const +{ + return d_ptr->path; +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoRoutePrivate::QGeoRoutePrivate() + : travelTime(0), + distance(0.0), + travelMode(QGeoRouteRequest::CarTravel) {} + +QGeoRoutePrivate::QGeoRoutePrivate(const QGeoRoutePrivate &other) + : QSharedData(other), + id(other.id), + request(other.request), + bounds(other.bounds), + travelTime(other.travelTime), + distance(other.distance), + travelMode(other.travelMode), + path(other.path), + firstSegment(other.firstSegment) {} + +QGeoRoutePrivate::~QGeoRoutePrivate() {} + +bool QGeoRoutePrivate::operator ==(const QGeoRoutePrivate &other) const +{ + QGeoRouteSegment s1 = firstSegment; + QGeoRouteSegment s2 = other.firstSegment; + + while (true) { + if (s1.isValid() != s2.isValid()) + return false; + if (!s1.isValid()) + break; + if (s1 != s2) + return false; + s1 = s1.nextRouteSegment(); + s2 = s2.nextRouteSegment(); + } + + return ((id == other.id) + && (request == other.request) + && (bounds == other.bounds) + && (travelTime == other.travelTime) + && (distance == other.distance) + && (travelMode == other.travelMode) + && (path == other.path)); +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeoroute.h b/src/location/maps/qgeoroute.h new file mode 100644 index 0000000..68e73c0 --- /dev/null +++ b/src/location/maps/qgeoroute.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTE_H +#define QGEOROUTE_H + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRectangle; +class QGeoRouteSegment; + +class QGeoRoutePrivate; + +class Q_LOCATION_EXPORT QGeoRoute +{ +public: + QGeoRoute(); + QGeoRoute(const QGeoRoute &other); + ~QGeoRoute(); + + QGeoRoute &operator = (const QGeoRoute &other); + + bool operator == (const QGeoRoute &other) const; + bool operator != (const QGeoRoute &other) const; + + void setRouteId(const QString &id); + QString routeId() const; + + void setRequest(const QGeoRouteRequest &request); + QGeoRouteRequest request() const; + + void setBounds(const QGeoRectangle &bounds); + QGeoRectangle bounds() const; + + void setFirstRouteSegment(const QGeoRouteSegment &routeSegment); + QGeoRouteSegment firstRouteSegment() const; + + void setTravelTime(int secs); + int travelTime() const; + + void setDistance(qreal distance); + qreal distance() const; + + void setTravelMode(QGeoRouteRequest::TravelMode mode); + QGeoRouteRequest::TravelMode travelMode() const; + + void setPath(const QList &path); + QList path() const; + +private: + QExplicitlySharedDataPointer d_ptr; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoroute_p.h b/src/location/maps/qgeoroute_p.h new file mode 100644 index 0000000..66ef3c6 --- /dev/null +++ b/src/location/maps/qgeoroute_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTE_P_H +#define QGEOROUTE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeoroute.h" +#include "qgeorouterequest.h" +#include "qgeorectangle.h" +#include "qgeoroutesegment.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoCoordinate; + +class QGeoRoutePrivate : public QSharedData +{ +public: + QGeoRoutePrivate(); + QGeoRoutePrivate(const QGeoRoutePrivate &other); + ~QGeoRoutePrivate(); + + bool operator == (const QGeoRoutePrivate &other) const; + + QString id; + QGeoRouteRequest request; + + QGeoRectangle bounds; +// QList routeSegments; + + int travelTime; + qreal distance; + + QGeoRouteRequest::TravelMode travelMode; + + QList path; + + QGeoRouteSegment firstSegment; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeorouteparser.cpp b/src/location/maps/qgeorouteparser.cpp new file mode 100644 index 0000000..646902e --- /dev/null +++ b/src/location/maps/qgeorouteparser.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeorouteparser_p.h" +#include "qgeorouteparser_p_p.h" +#include "qgeoroutesegment.h" +#include "qgeomaneuver.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/* + Private class implementations +*/ + +QGeoRouteParserPrivate::QGeoRouteParserPrivate() : QObjectPrivate() +{ +} + +QGeoRouteParserPrivate::~QGeoRouteParserPrivate() +{ +} + +/* + Public class implementations +*/ + +QGeoRouteParser::~QGeoRouteParser() +{ + +} + +QGeoRouteParser::QGeoRouteParser(QGeoRouteParserPrivate &dd, QObject *parent) : QObject(dd, parent) +{ + +} + +QGeoRouteReply::Error QGeoRouteParser::parseReply(QList &routes, QString &errorString, const QByteArray &reply) const +{ + Q_D(const QGeoRouteParser); + return d->parseReply(routes, errorString, reply); +} + +QUrl QGeoRouteParser::requestUrl(const QGeoRouteRequest &request, const QString &prefix) const +{ + Q_D(const QGeoRouteParser); + return d->requestUrl(request, prefix); +} + +QT_END_NAMESPACE + + diff --git a/src/location/maps/qgeorouteparser_p.h b/src/location/maps/qgeorouteparser_p.h new file mode 100644 index 0000000..da1c09f --- /dev/null +++ b/src/location/maps/qgeorouteparser_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOSRMROUTEPARSER_P_H +#define QOSRMROUTEPARSER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRouteParserPrivate; +class Q_LOCATION_EXPORT QGeoRouteParser : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoRouteParser) + +public: + virtual ~QGeoRouteParser(); + QGeoRouteReply::Error parseReply(QList &routes, QString &errorString, const QByteArray &reply) const; + QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const; + +protected: + QGeoRouteParser(QGeoRouteParserPrivate &dd, QObject *parent = Q_NULLPTR); + +private: + Q_DISABLE_COPY(QGeoRouteParser) +}; + +QT_END_NAMESPACE + +#endif // QOSRMROUTEPARSER_P_H diff --git a/src/location/maps/qgeorouteparser_p_p.h b/src/location/maps/qgeorouteparser_p_p.h new file mode 100644 index 0000000..7bf41f8 --- /dev/null +++ b/src/location/maps/qgeorouteparser_p_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEPARSER_P_P_H +#define QGEOROUTEPARSER_P_P_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRouteParserPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGeoRouteParser) +public: + QGeoRouteParserPrivate(); + virtual ~QGeoRouteParserPrivate(); + + virtual QGeoRouteReply::Error parseReply(QList &routes, QString &errorString, const QByteArray &reply) const = 0; + virtual QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const = 0; +}; + +QT_END_NAMESPACE + +#endif // QGEOROUTEPARSER_P_P_H diff --git a/src/location/maps/qgeorouteparserosrmv4.cpp b/src/location/maps/qgeorouteparserosrmv4.cpp new file mode 100644 index 0000000..7321fb6 --- /dev/null +++ b/src/location/maps/qgeorouteparserosrmv4.cpp @@ -0,0 +1,404 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeorouteparserosrmv4_p.h" +#include "qgeorouteparser_p_p.h" +#include "qgeoroutesegment.h" +#include "qgeomaneuver.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static QList parsePolyline(const QByteArray &data) +{ + QList path; + + bool parsingLatitude = true; + + int shift = 0; + int value = 0; + + QGeoCoordinate coord(0, 0); + + for (int i = 0; i < data.length(); ++i) { + unsigned char c = data.at(i) - 63; + + value |= (c & 0x1f) << shift; + shift += 5; + + // another chunk + if (c & 0x20) + continue; + + int diff = (value & 1) ? ~(value >> 1) : (value >> 1); + + if (parsingLatitude) { + coord.setLatitude(coord.latitude() + (double)diff/1e6); + } else { + coord.setLongitude(coord.longitude() + (double)diff/1e6); + path.append(coord); + } + + parsingLatitude = !parsingLatitude; + + value = 0; + shift = 0; + } + + return path; +} + +static QGeoManeuver::InstructionDirection osrmInstructionDirection(const QString &instructionCode) +{ + if (instructionCode == QLatin1String("0")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("1")) + return QGeoManeuver::DirectionForward; + else if (instructionCode == QLatin1String("2")) + return QGeoManeuver::DirectionBearRight; + else if (instructionCode == QLatin1String("3")) + return QGeoManeuver::DirectionRight; + else if (instructionCode == QLatin1String("4")) + return QGeoManeuver::DirectionHardRight; + else if (instructionCode == QLatin1String("5")) + return QGeoManeuver::DirectionUTurnLeft; + else if (instructionCode == QLatin1String("6")) + return QGeoManeuver::DirectionHardLeft; + else if (instructionCode == QLatin1String("7")) + return QGeoManeuver::DirectionLeft; + else if (instructionCode == QLatin1String("8")) + return QGeoManeuver::DirectionBearLeft; + else if (instructionCode == QLatin1String("9")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("10")) + return QGeoManeuver::DirectionForward; + else if (instructionCode == QLatin1String("11")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("12")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("13")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("14")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("15")) + return QGeoManeuver::NoDirection; + else + return QGeoManeuver::NoDirection; +} + +static QString osrmInstructionText(const QString &instructionCode, const QString &wayname) +{ + if (instructionCode == QLatin1String("0")) { + return QString(); + } else if (instructionCode == QLatin1String("1")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Go straight."); + else + return QGeoRouteParserOsrmV4::tr("Go straight onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("2")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Turn slightly right."); + else + return QGeoRouteParserOsrmV4::tr("Turn slightly right onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("3")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Turn right."); + else + return QGeoRouteParserOsrmV4::tr("Turn right onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("4")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Make a sharp right."); + else + return QGeoRouteParserOsrmV4::tr("Make a sharp right onto %1.").arg(wayname); + } + else if (instructionCode == QLatin1String("5")) { + return QGeoRouteParserOsrmV4::tr("When it is safe to do so, perform a U-turn."); + } else if (instructionCode == QLatin1String("6")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Make a sharp left."); + else + return QGeoRouteParserOsrmV4::tr("Make a sharp left onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("7")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Turn left."); + else + return QGeoRouteParserOsrmV4::tr("Turn left onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("8")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Turn slightly left."); + else + return QGeoRouteParserOsrmV4::tr("Turn slightly left onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("9")) { + return QGeoRouteParserOsrmV4::tr("Reached waypoint."); + } else if (instructionCode == QLatin1String("10")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Head on."); + else + return QGeoRouteParserOsrmV4::tr("Head onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11")) { + return QGeoRouteParserOsrmV4::tr("Enter the roundabout."); + } else if (instructionCode == QLatin1String("11-1")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the first exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the first exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-2")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the second exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the second exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-3")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the third exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the third exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-4")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the fourth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the fourth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-5")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the fifth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the fifth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-6")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the sixth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the sixth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-7")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the seventh exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the seventh exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-8")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the eighth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the eighth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-9")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the ninth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the ninth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("12")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Leave the roundabout."); + else + return QGeoRouteParserOsrmV4::tr("Leave the roundabout onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("13")) { + return QGeoRouteParserOsrmV4::tr("Stay on the roundabout."); + } else if (instructionCode == QLatin1String("14")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Start at the end of the street."); + else + return QGeoRouteParserOsrmV4::tr("Start at the end of %1.").arg(wayname); + } else if (instructionCode == QLatin1String("15")) { + return QGeoRouteParserOsrmV4::tr("You have reached your destination."); + } else { + return QGeoRouteParserOsrmV4::tr("Don't know what to say for '%1'").arg(instructionCode); + } +} + +static QGeoRoute constructRoute(const QByteArray &geometry, const QJsonArray &instructions, + const QJsonObject &summary) +{ + QGeoRoute route; + + QList path = parsePolyline(geometry); + + QGeoRouteSegment firstSegment; + int firstPosition = -1; + + int segmentPathLengthCount = 0; + + for (int i = instructions.count() - 1; i >= 0; --i) { + QJsonArray instruction = instructions.at(i).toArray(); + + if (instruction.count() < 8) { + qWarning("Instruction does not contain enough fields."); + continue; + } + + const QString instructionCode = instruction.at(0).toString(); + const QString wayname = instruction.at(1).toString(); + double segmentLength = instruction.at(2).toDouble(); + int position = instruction.at(3).toDouble(); + int time = instruction.at(4).toDouble(); + //const QString segmentLengthString = instruction.at(5).toString(); + //const QString direction = instruction.at(6).toString(); + //double azimuth = instruction.at(7).toDouble(); + + QGeoRouteSegment segment; + segment.setDistance(segmentLength); + + QGeoManeuver maneuver; + maneuver.setDirection(osrmInstructionDirection(instructionCode)); + maneuver.setDistanceToNextInstruction(segmentLength); + maneuver.setInstructionText(osrmInstructionText(instructionCode, wayname)); + maneuver.setPosition(path.at(position)); + maneuver.setTimeToNextInstruction(time); + + segment.setManeuver(maneuver); + + if (firstPosition == -1) + segment.setPath(path.mid(position)); + else + segment.setPath(path.mid(position, firstPosition - position)); + + segmentPathLengthCount += segment.path().length(); + + segment.setTravelTime(time); + + segment.setNextRouteSegment(firstSegment); + + firstSegment = segment; + firstPosition = position; + } + + route.setDistance(summary.value(QStringLiteral("total_distance")).toDouble()); + route.setTravelTime(summary.value(QStringLiteral("total_time")).toDouble()); + route.setFirstRouteSegment(firstSegment); + route.setPath(path); + + return route; +} + +class QGeoRouteParserOsrmV4Private : public QGeoRouteParserPrivate +{ + Q_DECLARE_PUBLIC(QGeoRouteParserOsrmV4) +public: + QGeoRouteParserOsrmV4Private(); + virtual ~QGeoRouteParserOsrmV4Private(); + + QGeoRouteReply::Error parseReply(QList &routes, QString &errorString, const QByteArray &reply) const Q_DECL_OVERRIDE; + QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const Q_DECL_OVERRIDE; +}; + +QGeoRouteParserOsrmV4Private::QGeoRouteParserOsrmV4Private() : QGeoRouteParserPrivate() +{ +} + +QGeoRouteParserOsrmV4Private::~QGeoRouteParserOsrmV4Private() +{ +} + +QGeoRouteReply::Error QGeoRouteParserOsrmV4Private::parseReply(QList &routes, QString &errorString, const QByteArray &reply) const +{ + // OSRM v4 specs: https://github.com/Project-OSRM/osrm-backend/wiki/Server-API---v4,-old + QJsonDocument document = QJsonDocument::fromJson(reply); + + if (document.isObject()) { + QJsonObject object = document.object(); + + //double version = object.value(QStringLiteral("version")).toDouble(); + int status = object.value(QStringLiteral("status")).toDouble(); + QString statusMessage = object.value(QStringLiteral("status_message")).toString(); + + // status code 0 or 200 are case of success + // status code is 207 if no route was found + // an error occurred when trying to find a route + if (0 != status && 200 != status) { + errorString = statusMessage; + return QGeoRouteReply::UnknownError; + } + + QJsonObject routeSummary = object.value(QStringLiteral("route_summary")).toObject(); + + QByteArray routeGeometry = + object.value(QStringLiteral("route_geometry")).toString().toLatin1(); + + QJsonArray routeInstructions = object.value(QStringLiteral("route_instructions")).toArray(); + + QGeoRoute route = constructRoute(routeGeometry, routeInstructions, routeSummary); + + routes.append(route); + + QJsonArray alternativeSummaries = + object.value(QStringLiteral("alternative_summaries")).toArray(); + QJsonArray alternativeGeometries = + object.value(QStringLiteral("alternative_geometries")).toArray(); + QJsonArray alternativeInstructions = + object.value(QStringLiteral("alternative_instructions")).toArray(); + + if (alternativeSummaries.count() == alternativeGeometries.count() && + alternativeSummaries.count() == alternativeInstructions.count()) { + for (int i = 0; i < alternativeSummaries.count(); ++i) { + route = constructRoute(alternativeGeometries.at(i).toString().toLatin1(), + alternativeInstructions.at(i).toArray(), + alternativeSummaries.at(i).toObject()); + //routes.append(route); + } + } + + return QGeoRouteReply::NoError; + } else { + errorString = QStringLiteral("Couldn't parse json."); + return QGeoRouteReply::ParseError; + } +} + +QUrl QGeoRouteParserOsrmV4Private::requestUrl(const QGeoRouteRequest &request, const QString &prefix) const +{ + QUrl url(prefix); + QUrlQuery query; + + query.addQueryItem(QStringLiteral("instructions"), QStringLiteral("true")); + + foreach (const QGeoCoordinate &c, request.waypoints()) { + query.addQueryItem(QStringLiteral("loc"), QString::number(c.latitude()) + QLatin1Char(',') + + QString::number(c.longitude())); + } + + url.setQuery(query); + return url; +} + +QGeoRouteParserOsrmV4::QGeoRouteParserOsrmV4(QObject *parent) : QGeoRouteParser(*new QGeoRouteParserOsrmV4Private(), parent) +{ +} + +QGeoRouteParserOsrmV4::~QGeoRouteParserOsrmV4() +{ +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeorouteparserosrmv4_p.h b/src/location/maps/qgeorouteparserosrmv4_p.h new file mode 100644 index 0000000..3d38cbf --- /dev/null +++ b/src/location/maps/qgeorouteparserosrmv4_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEPARSEROSRMV4_H +#define QGEOROUTEPARSEROSRMV4_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + + +#include + +QT_BEGIN_NAMESPACE + +class QGeoRouteParserOsrmV4Private; +class Q_LOCATION_EXPORT QGeoRouteParserOsrmV4 : public QGeoRouteParser +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoRouteParserOsrmV4) + +public: + QGeoRouteParserOsrmV4(QObject *parent = Q_NULLPTR); + virtual ~QGeoRouteParserOsrmV4(); + +private: + Q_DISABLE_COPY(QGeoRouteParserOsrmV4) +}; + +QT_END_NAMESPACE + +#endif // QGEOROUTEPARSEROSRMV4_H diff --git a/src/location/maps/qgeorouteparserosrmv5.cpp b/src/location/maps/qgeorouteparserosrmv5.cpp new file mode 100644 index 0000000..cbbf7fc --- /dev/null +++ b/src/location/maps/qgeorouteparserosrmv5.cpp @@ -0,0 +1,979 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeorouteparserosrmv5_p.h" +#include "qgeorouteparser_p_p.h" +#include "qgeoroutesegment.h" +#include "qgeomaneuver.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static QList decodePolyline(const QString &polylineString) +{ + QList path; + if (polylineString.isEmpty()) + return path; + + QByteArray data = polylineString.toLatin1(); + + bool parsingLatitude = true; + + int shift = 0; + int value = 0; + + QGeoCoordinate coord(0, 0); + + for (int i = 0; i < data.length(); ++i) { + unsigned char c = data.at(i) - 63; + + value |= (c & 0x1f) << shift; + shift += 5; + + // another chunk + if (c & 0x20) + continue; + + int diff = (value & 1) ? ~(value >> 1) : (value >> 1); + + if (parsingLatitude) { + coord.setLatitude(coord.latitude() + (double)diff/1e5); + } else { + coord.setLongitude(coord.longitude() + (double)diff/1e5); + path.append(coord); + } + + parsingLatitude = !parsingLatitude; + + value = 0; + shift = 0; + } + + return path; +} + +static QString cardinalDirection4(QLocationUtils::CardinalDirection direction) +{ + switch (direction) { + case QLocationUtils::CardinalN: + //: Always used in "Head %1 [onto ]" + return QGeoRouteParserOsrmV5::tr("North"); + case QLocationUtils::CardinalE: + return QGeoRouteParserOsrmV5::tr("East"); + case QLocationUtils::CardinalS: + return QGeoRouteParserOsrmV5::tr("South"); + case QLocationUtils::CardinalW: + return QGeoRouteParserOsrmV5::tr("West"); + default: + return QString(); + } +} + +static QString exitOrdinal(int exit) +{ + static QList ordinals; + + if (!ordinals.size()) { + ordinals.append(QStringLiteral("")); + //: always used in " and take the %1 exit [onto ]" + ordinals.append(QGeoRouteParserOsrmV5::tr("first", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("second", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("third", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("fourth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("fifth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("sixth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("seventh", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("eighth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("ninth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("tenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("eleventh", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("twelfth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("thirteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("fourteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("fifteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("sixteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("seventeenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("eighteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("nineteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("twentieth", "roundabout exit")); + }; + + if (exit < 1 || exit > ordinals.size()) + return QString(); + return ordinals[exit]; +} + +static QString exitDirection(int exit, const QString &wayName) +{ + /*: Always appended to one of the following strings: + - "Enter the roundabout" + - "Enter the rotary" + - "Enter the rotary " + */ + static QString directionExit = QGeoRouteParserOsrmV5::tr(" and take the %1 exit"); + static QString directionExitOnto = QGeoRouteParserOsrmV5::tr(" and take the %1 exit onto %2"); + + if (exit < 1 || exit > 20) + return QString(); + if (wayName.isEmpty()) + return directionExit.arg(exitOrdinal(exit)); + else + return directionExitOnto.arg(exitOrdinal(exit), wayName); +} + +static QString instructionArrive(QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionForward: + return QGeoRouteParserOsrmV5::tr("You have arrived at your destination, straight ahead"); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + return QGeoRouteParserOsrmV5::tr("You have arrived at your destination, on the left"); + case QGeoManeuver::DirectionUTurnRight: + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + return QGeoRouteParserOsrmV5::tr("You have arrived at your destination, on the right"); + default: + return QGeoRouteParserOsrmV5::tr("You have arrived at your destination"); + } +} + +static QString instructionContinue(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue straight"); + else + return QGeoRouteParserOsrmV5::tr("Continue straight on %1").arg(wayName); + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue left"); + else + return QGeoRouteParserOsrmV5::tr("Continue left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue slightly left"); + else + return QGeoRouteParserOsrmV5::tr("Continue slightly left on %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue right"); + else + return QGeoRouteParserOsrmV5::tr("Continue right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue slightly right"); + else + return QGeoRouteParserOsrmV5::tr("Continue slightly right on %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("Make a U-turn onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue"); + else + return QGeoRouteParserOsrmV5::tr("Continue on %1").arg(wayName); + } +} + +static QString instructionDepart(const QJsonObject &maneuver, const QString &wayName) +{ + double bearing = maneuver.value(QLatin1String("bearing_after")).toDouble(-1.0); + if (bearing >= 0.0) { + if (wayName.isEmpty()) + //: %1 is "North", "South", "East" or "West" + return QGeoRouteParserOsrmV5::tr("Head %1").arg(cardinalDirection4(QLocationUtils::azimuthToCardinalDirection4(bearing))); + else + return QGeoRouteParserOsrmV5::tr("Head %1 onto %2").arg(cardinalDirection4(QLocationUtils::azimuthToCardinalDirection4(bearing)), wayName); + } else { + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Depart"); + else + return QGeoRouteParserOsrmV5::tr("Depart onto %1").arg(wayName); + } +} + +static QString instructionEndOfRoad(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, turn left"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, turn right"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, make a U-turn onto %1").arg(wayName); + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, continue straight"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, continue straight onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, continue"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, continue onto %1").arg(wayName); + } +} + +static QString instructionFerry(const QString &wayName) +{ + QString instruction = QGeoRouteParserOsrmV5::tr("Take the ferry"); + if (!wayName.isEmpty()) + instruction += QLatin1String(" [") + wayName + QLatin1Char(']'); + + return instruction; +} + +static QString instructionFork(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionHardLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, take a sharp left"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, take a sharp left onto %1").arg(wayName); + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, turn left"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, keep left"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, keep left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, take a sharp right"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, take a sharp right onto %1").arg(wayName); + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, turn right"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, keep right"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, keep right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("Make a U-turn onto %1").arg(wayName); + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, continue straight ahead"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, continue straight ahead onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, continue"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, continue onto %1").arg(wayName); + } +} + +static QString instructionMerge(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionHardLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge sharply left"); + else + return QGeoRouteParserOsrmV5::tr("Merge sharply left onto %1").arg(wayName); + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge left"); + else + return QGeoRouteParserOsrmV5::tr("Merge left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge slightly left"); + else + return QGeoRouteParserOsrmV5::tr("Merge slightly left on %1").arg(wayName); + case QGeoManeuver::DirectionUTurnRight: + case QGeoManeuver::DirectionHardRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge sharply right"); + else + return QGeoRouteParserOsrmV5::tr("Merge sharply right onto %1").arg(wayName); + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge right"); + else + return QGeoRouteParserOsrmV5::tr("Merge right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge slightly right"); + else + return QGeoRouteParserOsrmV5::tr("Merge slightly right on %1").arg(wayName); + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge straight"); + else + return QGeoRouteParserOsrmV5::tr("Merge straight on %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge"); + else + return QGeoRouteParserOsrmV5::tr("Merge onto %1").arg(wayName); + } +} + +static QString instructionNewName(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionHardLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take a sharp left"); + else + return QGeoRouteParserOsrmV5::tr("Take a sharp left onto %1").arg(wayName); + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn left"); + else + return QGeoRouteParserOsrmV5::tr("Turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue slightly left"); + else + return QGeoRouteParserOsrmV5::tr("Continue slightly left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take a sharp right"); + else + return QGeoRouteParserOsrmV5::tr("Take a sharp right onto %1").arg(wayName); + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn right"); + else + return QGeoRouteParserOsrmV5::tr("Turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Contine slightly right"); + else + return QGeoRouteParserOsrmV5::tr("Contine slightly right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("Make a U-turn onto %1").arg(wayName); + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue straight"); + else + return QGeoRouteParserOsrmV5::tr("Continue straight onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue"); + else + return QGeoRouteParserOsrmV5::tr("Continue onto %1").arg(wayName); + } +} + +static QString instructionNotification(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue on the left"); + else + return QGeoRouteParserOsrmV5::tr("Continue on the left on %1").arg(wayName); + case QGeoManeuver::DirectionUTurnRight: + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Contine on the right"); + else + return QGeoRouteParserOsrmV5::tr("Contine on the right on %1").arg(wayName); + case QGeoManeuver::DirectionForward: + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue"); + else + return QGeoRouteParserOsrmV5::tr("Continue on %1").arg(wayName); + } +} + +static QString instructionOffRamp(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take the ramp on the left"); + else + return QGeoRouteParserOsrmV5::tr("Take the ramp on the left onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnRight: + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take the ramp on the right"); + else + return QGeoRouteParserOsrmV5::tr("Take the ramp on the right onto %1").arg(wayName); + case QGeoManeuver::DirectionForward: + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take the ramp"); + else + return QGeoRouteParserOsrmV5::tr("Take the ramp onto %1").arg(wayName); + } +} + +static QString instructionOnRamp(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + return instructionOffRamp(wayName, direction); +} + +static QString instructionPushingBike(const QString &wayName) +{ + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Get off the bike and push"); + else + return QGeoRouteParserOsrmV5::tr("Get off the bike and push onto %1").arg(wayName); +} + +static QString instructionRotary(const QJsonObject &step, const QJsonObject &maneuver, const QString &wayName) +{ + QString instruction; + QString rotaryName = step.value(QLatin1String("rotary_name")).toString(); + //QString modifier = maneuver.value(QLatin1String("modifier")).toString(); // Apparently not used for rotaries + int exit = maneuver.value(QLatin1String("exit")).toInt(0); + + //: This string will be prepended to " and take the exit [onto ] + instruction += QGeoRouteParserOsrmV5::tr("Enter the rotary"); + if (!rotaryName.isEmpty()) + instruction += QLatin1Char(' ') + rotaryName; + instruction += exitDirection(exit, wayName); + return instruction; +} + +static QString instructionRoundabout(const QJsonObject &maneuver, const QString &wayName) +{ + QString instruction; + //QString modifier = maneuver.value(QLatin1String("modifier")).toString(); // Apparently not used for rotaries + int exit = maneuver.value(QLatin1String("exit")).toInt(0); + + //: This string will be prepended to " and take the exit [onto ] + instruction += QGeoRouteParserOsrmV5::tr("Enter the roundabout"); + instruction += exitDirection(exit, wayName); + return instruction; +} + +static QString instructionRoundaboutTurn(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, continue straight"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, continue straight on %1").arg(wayName); + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn left"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn right"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn around"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn around onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, continue"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, continue onto %1").arg(wayName); + } +} + +static QString instructionTrain(const QString &wayName) +{ + QString instruction = QGeoRouteParserOsrmV5::tr("Take the train"); + if (!wayName.isEmpty()) + instruction += QLatin1String(" [") + wayName + QLatin1Char(']'); + + return instruction; +} + +static QString instructionTurn(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Go straight"); + else + return QGeoRouteParserOsrmV5::tr("Go straight onto %1").arg(wayName); + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn left"); + else + return QGeoRouteParserOsrmV5::tr("Turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn slightly left"); + else + return QGeoRouteParserOsrmV5::tr("Turn slightly left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn right"); + else + return QGeoRouteParserOsrmV5::tr("Turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn slightly right"); + else + return QGeoRouteParserOsrmV5::tr("Turn slightly right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("Make a U-turn onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn"); + else + return QGeoRouteParserOsrmV5::tr("Turn onto %1").arg(wayName); + } +} + +static QString instructionUseLane(const QJsonObject &maneuver, const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + QString laneTypes = maneuver.value(QLatin1String("laneTypes")).toString(); + QString laneInstruction; + if (laneTypes == QLatin1String("xo") || laneTypes == QLatin1String("xoo") || laneTypes == QLatin1String("xxo")) + //: "and [onto ] will be appended to this string. E.g., "Keep right and make a sharp left" + laneInstruction = QLatin1String("Keep right"); + else if (laneTypes == QLatin1String("ox") || laneTypes == QLatin1String("oox") || laneTypes == QLatin1String("oxx")) + laneInstruction = QLatin1String("Keep left"); + else if (laneTypes == QLatin1String("xox")) + laneInstruction = QLatin1String("Use the middle lane"); + else if (laneTypes == QLatin1String("oxo")) + laneInstruction = QLatin1String("Use the left or the right lane"); + + if (laneInstruction.isEmpty()) { + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue straight"); + else + return QGeoRouteParserOsrmV5::tr("Continue straight onto %1").arg(wayName); + } + + switch (direction) { + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + //: This string will be prepended with lane instructions. E.g., "Use the left or the right lane and continue straight" + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and continue straight"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and continue straight onto %1").arg(wayName); + case QGeoManeuver::DirectionHardLeft: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a sharp left"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a sharp left onto %1").arg(wayName); + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and turn left"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a slight left"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a slight left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a sharp right"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a sharp right onto %1").arg(wayName); + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and turn right"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a slight right"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a slight right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a U-turn"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a U-turn onto %1").arg(wayName); + default: + return laneInstruction; + } +} + +static QString instructionText(const QJsonObject &step, const QJsonObject &maneuver, QGeoManeuver::InstructionDirection direction) { + QString modifier; + if (maneuver.value(QLatin1String("modifier")).isString()) + modifier = maneuver.value(QLatin1String("modifier")).toString(); + QString maneuverType; + if (maneuver.value(QLatin1String("type")).isString()) + maneuverType = maneuver.value(QLatin1String("type")).toString(); + QString wayName = QStringLiteral("unknown street"); + if (step.value(QLatin1String("name")).isString()) + wayName = step.value(QLatin1String("name")).toString(); + + + if (maneuverType == QLatin1String("arrive")) + return instructionArrive(direction); + else if (maneuverType == QLatin1String("continue")) + return instructionContinue(wayName, direction); + else if (maneuverType == QLatin1String("depart")) + return instructionDepart(maneuver, wayName); + else if (maneuverType == QLatin1String("end of road")) + return instructionEndOfRoad(wayName, direction); + else if (maneuverType == QLatin1String("ferry")) + return instructionFerry(wayName); + else if (maneuverType == QLatin1String("fork")) + return instructionFork(wayName, direction); + else if (maneuverType == QLatin1String("merge")) + return instructionMerge(wayName, direction); + else if (maneuverType == QLatin1String("new name")) + return instructionNewName(wayName, direction); + else if (maneuverType == QLatin1String("notification")) + return instructionNotification(wayName, direction); + else if (maneuverType == QLatin1String("off ramp")) + return instructionOffRamp(wayName, direction); + else if (maneuverType == QLatin1String("on ramp")) + return instructionOnRamp(wayName, direction); + else if (maneuverType == QLatin1String("pushing bike")) + return instructionPushingBike(wayName); + else if (maneuverType == QLatin1String("rotary")) + return instructionRotary(step, maneuver, wayName); + else if (maneuverType == QLatin1String("roundabout")) + return instructionRoundabout(maneuver, wayName); + else if (maneuverType == QLatin1String("roundabout turn")) + return instructionRoundaboutTurn(wayName, direction); + else if (maneuverType == QLatin1String("train")) + return instructionTrain(wayName); + else if (maneuverType == QLatin1String("turn")) + return instructionTurn(wayName, direction); + else if (maneuverType == QLatin1String("use lane")) + return instructionUseLane(maneuver, wayName, direction); + else + return maneuverType + QLatin1String(" to/onto ") + wayName; +} + +static QGeoManeuver::InstructionDirection instructionDirection(const QJsonObject &maneuver) +{ + QString modifier; + if (maneuver.value(QLatin1String("modifier")).isString()) + modifier = maneuver.value(QLatin1String("modifier")).toString(); + + if (modifier.isEmpty()) + return QGeoManeuver::NoDirection; + else if (modifier == QLatin1String("straight")) + return QGeoManeuver::DirectionForward; + else if (modifier == QLatin1String("right")) + return QGeoManeuver::DirectionRight; + else if (modifier == QLatin1String("sharp right")) + return QGeoManeuver::DirectionHardRight; + else if (modifier == QLatin1String("slight right")) + return QGeoManeuver::DirectionLightRight; + else if (modifier == QLatin1String("uturn")) + return QGeoManeuver::DirectionUTurnRight; + else if (modifier == QLatin1String("left")) + return QGeoManeuver::DirectionLeft; + else if (modifier == QLatin1String("sharp left")) + return QGeoManeuver::DirectionHardLeft; + else if (modifier == QLatin1String("slight left")) + return QGeoManeuver::DirectionLightLeft; + else + return QGeoManeuver::NoDirection; +} + +static QGeoRouteSegment parseStep(const QJsonObject &step) { + // OSRM Instructions documentation: https://github.com/Project-OSRM/osrm-text-instructions/blob/master/instructions.json + QGeoRouteSegment segment; + if (!step.value(QLatin1String("maneuver")).isObject()) + return segment; + QJsonObject maneuver = step.value(QLatin1String("maneuver")).toObject(); + if (!step.value(QLatin1String("duration")).isDouble()) + return segment; + if (!step.value(QLatin1String("distance")).isDouble()) + return segment; + if (!step.value(QLatin1String("intersections")).isArray()) + return segment; + if (!maneuver.value(QLatin1String("location")).isArray()) + return segment; + + double time = step.value(QLatin1String("duration")).toDouble(); + double distance = step.value(QLatin1String("distance")).toDouble(); + + QJsonArray position = maneuver.value(QLatin1String("location")).toArray(); + if (position.isEmpty()) + return segment; + double latitude = position[1].toDouble(); + double longitude = position[0].toDouble(); + QGeoCoordinate coord(latitude, longitude); + + QString geometry = step.value(QLatin1String("geometry")).toString(); + QList path = decodePolyline(geometry); + + QGeoManeuver geoManeuver; + geoManeuver.setDirection(instructionDirection(maneuver)); + geoManeuver.setDistanceToNextInstruction(distance); + geoManeuver.setTimeToNextInstruction(time); + geoManeuver.setInstructionText(instructionText(step, maneuver, geoManeuver.direction())); + geoManeuver.setPosition(coord); + geoManeuver.setWaypoint(coord); + + segment.setDistance(distance); + segment.setPath(path); + segment.setTravelTime(time); + segment.setManeuver(geoManeuver); + return segment; +} + +class QGeoRouteParserOsrmV5Private : public QGeoRouteParserPrivate +{ + Q_DECLARE_PUBLIC(QGeoRouteParserOsrmV5) +public: + QGeoRouteParserOsrmV5Private(); + virtual ~QGeoRouteParserOsrmV5Private(); + + QGeoRouteReply::Error parseReply(QList &routes, QString &errorString, const QByteArray &reply) const Q_DECL_OVERRIDE; + QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const Q_DECL_OVERRIDE; +}; + +QGeoRouteParserOsrmV5Private::QGeoRouteParserOsrmV5Private() : QGeoRouteParserPrivate() +{ +} + +QGeoRouteParserOsrmV5Private::~QGeoRouteParserOsrmV5Private() +{ +} + +QGeoRouteReply::Error QGeoRouteParserOsrmV5Private::parseReply(QList &routes, QString &errorString, const QByteArray &reply) const +{ + // OSRM v5 specs: https://github.com/Project-OSRM/osrm-backend/blob/master/docs/http.md + QJsonDocument document = QJsonDocument::fromJson(reply); + if (document.isObject()) { + QJsonObject object = document.object(); + + QString status = object.value(QStringLiteral("code")).toString(); + if (status != QLatin1String("Ok")) { + errorString = status; + return QGeoRouteReply::UnknownError; + } + if (!object.value(QLatin1String("routes")).isArray()) { + errorString = QLatin1String("No routes found"); + return QGeoRouteReply::ParseError; + } + + QJsonArray osrmRoutes = object.value(QLatin1String("routes")).toArray(); + foreach (const QJsonValue &r, osrmRoutes) { + if (!r.isObject()) + continue; + QJsonObject route = r.toObject(); + if (!route.value(QLatin1String("legs")).isArray()) + continue; + if (!route.value(QLatin1String("duration")).isDouble()) + continue; + if (!route.value(QLatin1String("distance")).isDouble()) + continue; + + double distance = route.value(QLatin1String("distance")).toDouble(); + double travelTime = route.value(QLatin1String("duration")).toDouble(); + bool error = false; + QList segments; + + QJsonArray legs = route.value(QLatin1String("legs")).toArray(); + foreach (const QJsonValue &l, legs) { + if (!l.isObject()) { // invalid leg record + error = true; + break; + } + QJsonObject leg = l.toObject(); + if (!leg.value(QLatin1String("steps")).isArray()) { // Invalid steps field + error = true; + break; + } + QJsonArray steps = leg.value(QLatin1String("steps")).toArray(); + foreach (const QJsonValue &s, steps) { + if (!s.isObject()) { + error = true; + break; + } + QGeoRouteSegment segment = parseStep(s.toObject()); + if (segment.isValid()) { + segments.append(segment); + } else { + error = true; + break; + } + } + if (error) + break; + } + + if (!error) { + QList path; + foreach (const QGeoRouteSegment &s, segments) + path.append(s.path()); + + for (int i = segments.size() - 1; i > 0; --i) + segments[i-1].setNextRouteSegment(segments[i]); + + QGeoRoute r; + r.setDistance(distance); + r.setTravelTime(travelTime); + if (!path.isEmpty()) { + r.setPath(path); + r.setFirstRouteSegment(segments.first()); + } + //r.setTravelMode(QGeoRouteRequest::CarTravel); // The only one supported by OSRM demo service, but other OSRM servers might do cycle or pedestrian too + routes.append(r); + } + } + + // setError(QGeoRouteReply::NoError, status); // can't do this, or NoError is emitted and does damages + return QGeoRouteReply::NoError; + } else { + errorString = QStringLiteral("Couldn't parse json."); + return QGeoRouteReply::ParseError; + } +} + +QUrl QGeoRouteParserOsrmV5Private::requestUrl(const QGeoRouteRequest &request, const QString &prefix) const +{ + QString routingUrl = prefix; + int notFirst = 0; + foreach (const QGeoCoordinate &c, request.waypoints()) { + if (notFirst) + routingUrl.append(QLatin1Char(';')); + routingUrl.append(QString::number(c.longitude())).append(QLatin1Char(',')).append(QString::number(c.latitude())); + ++notFirst; + } + + QUrl url(routingUrl); + QUrlQuery query; + query.addQueryItem(QStringLiteral("overview"), QStringLiteral("full")); + query.addQueryItem(QStringLiteral("steps"), QStringLiteral("true")); + query.addQueryItem(QStringLiteral("geometries"), QStringLiteral("polyline")); + query.addQueryItem(QStringLiteral("alternatives"), QStringLiteral("true")); + url.setQuery(query); + return url; +} + +QGeoRouteParserOsrmV5::QGeoRouteParserOsrmV5(QObject *parent) : QGeoRouteParser(*new QGeoRouteParserOsrmV5Private(), parent) +{ +} + +QGeoRouteParserOsrmV5::~QGeoRouteParserOsrmV5() +{ +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeorouteparserosrmv5_p.h b/src/location/maps/qgeorouteparserosrmv5_p.h new file mode 100644 index 0000000..47c6891 --- /dev/null +++ b/src/location/maps/qgeorouteparserosrmv5_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEPARSEROSRMV5_H +#define QGEOROUTEPARSEROSRMV5_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + + +#include + +QT_BEGIN_NAMESPACE + +class QGeoRouteParserOsrmV5Private; +class Q_LOCATION_EXPORT QGeoRouteParserOsrmV5 : public QGeoRouteParser +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoRouteParserOsrmV5) + +public: + QGeoRouteParserOsrmV5(QObject *parent = Q_NULLPTR); + virtual ~QGeoRouteParserOsrmV5(); + +private: + Q_DISABLE_COPY(QGeoRouteParserOsrmV5) +}; + +QT_END_NAMESPACE + +#endif // QGEOROUTEPARSEROSRMV5_H diff --git a/src/location/maps/qgeoroutereply.cpp b/src/location/maps/qgeoroutereply.cpp new file mode 100644 index 0000000..ab869d3 --- /dev/null +++ b/src/location/maps/qgeoroutereply.cpp @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroutereply.h" +#include "qgeoroutereply_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoRouteReply + \inmodule QtLocation + \ingroup QtLocation-routing + \since 5.6 + + \brief The QGeoRouteReply class manages an operation started by an instance + of QGeoRoutingManager. + + Instances of QGeoRouteReply manage the state and results of these + operations. + + The isFinished(), error() and errorString() methods provide information + on whether the operation has completed and if it completed successfully. + + The finished() and error(QGeoRouteReply::Error,QString) + signals can be used to monitor the progress of the operation. + + It is possible that a newly created QGeoRouteReply may be in a finished + state, most commonly because an error has occurred. Since such an instance + will never emit the finished() or + error(QGeoRouteReply::Error,QString) signals, it is + important to check the result of isFinished() before making the connections + to the signals. The documentation for QGeoRoutingManager demonstrates how + this might be carried out. + + If the operation completes successfully the results will be able to be + accessed with routes(). +*/ + +/*! + \enum QGeoRouteReply::Error + + Describes an error which prevented the completion of the operation. + + \value NoError + No error has occurred. + \value EngineNotSetError + The routing manager that was used did not have a QGeoRoutingManagerEngine instance associated with it. + \value CommunicationError + An error occurred while communicating with the service provider. + \value ParseError + The response from the service provider was in an unrecognizable format. + \value UnsupportedOptionError + The requested operation or one of the options for the operation are not + supported by the service provider. + \value UnknownError + An error occurred which does not fit into any of the other categories. +*/ + +/*! + Constructs a route reply object based on \a request, with the specified \a parent. +*/ +QGeoRouteReply::QGeoRouteReply(const QGeoRouteRequest &request, QObject *parent) + : QObject(parent), + d_ptr(new QGeoRouteReplyPrivate(request)) +{ +} + +/*! + Constructs a route reply with a given \a error and \a errorString and the specified \a parent. +*/ +QGeoRouteReply::QGeoRouteReply(Error error, const QString &errorString, QObject *parent) + : QObject(parent), + d_ptr(new QGeoRouteReplyPrivate(error, errorString)) {} + +/*! + Destroys this route reply object. +*/ +QGeoRouteReply::~QGeoRouteReply() +{ + delete d_ptr; +} + +/*! + Sets whether or not this reply has finished to \a finished. + + If \a finished is true, this will cause the finished() signal to be + emitted. + + If the operation completed successfully, QGeoRouteReply::setRoutes() should + be called before this function. If an error occurred, + QGeoRouteReply::setError() should be used instead. +*/ +void QGeoRouteReply::setFinished(bool finished) +{ + d_ptr->isFinished = finished; + if (d_ptr->isFinished) + emit this->finished(); +} + +/*! + Return true if the operation completed successfully or encountered an + error which cause the operation to come to a halt. +*/ +bool QGeoRouteReply::isFinished() const +{ + return d_ptr->isFinished; +} + +/*! + Sets the error state of this reply to \a error and the textual + representation of the error to \a errorString. + + This will also cause error() and finished() signals to be emitted, in that + order. +*/ +void QGeoRouteReply::setError(QGeoRouteReply::Error error, const QString &errorString) +{ + d_ptr->error = error; + d_ptr->errorString = errorString; + emit this->error(error, errorString); + setFinished(true); +} + +/*! + Returns the error state of this reply. + + If the result is QGeoRouteReply::NoError then no error has occurred. +*/ +QGeoRouteReply::Error QGeoRouteReply::error() const +{ + return d_ptr->error; +} + +/*! + Returns the textual representation of the error state of this reply. + + If no error has occurred this will return an empty string. It is possible + that an error occurred which has no associated textual representation, in + which case this will also return an empty string. + + To determine whether an error has occurred, check to see if + QGeoRouteReply::error() is equal to QGeoRouteReply::NoError. +*/ +QString QGeoRouteReply::errorString() const +{ + return d_ptr->errorString; +} + +/*! + Returns the route request which specified the route. +*/ +QGeoRouteRequest QGeoRouteReply::request() const +{ + return d_ptr->request; +} + +/*! + Returns the list of routes which were requested. +*/ +QList QGeoRouteReply::routes() const +{ + return d_ptr->routes; +} + +/*! + Sets the list of routes in the reply to \a routes. +*/ +void QGeoRouteReply::setRoutes(const QList &routes) +{ + d_ptr->routes = routes; +} + +/*! + Appends the list of \a routes to the existing list. +*/ +void QGeoRouteReply::addRoutes(const QList &routes) +{ + d_ptr->routes.append(routes); +} + +/*! + Cancels the operation immediately. + + This will do nothing if the reply is finished. +*/ +void QGeoRouteReply::abort() +{ + if (!isFinished()) + setFinished(true); +} + +/*! + \fn void QGeoRouteReply::finished() + + This signal is emitted when this reply has finished processing. + + If error() equals QGeoRouteReply::NoError then the processing + finished successfully. + + This signal and QGeoRoutingManager::finished() will be + emitted at the same time. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ +/*! + \fn void QGeoRouteReply::error(QGeoRouteReply::Error error, const QString &errorString) + + This signal is emitted when an error has been detected in the processing of + this reply. The finished() signal will probably follow. + + The error will be described by the error code \a error. If \a errorString is + not empty it will contain a textual description of the error. + + This signal and QGeoRoutingManager::error() will be emitted at the same time. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ + +/******************************************************************************* +*******************************************************************************/ + +QGeoRouteReplyPrivate::QGeoRouteReplyPrivate(const QGeoRouteRequest &request) + : error(QGeoRouteReply::NoError), + isFinished(false), + request(request) {} + +QGeoRouteReplyPrivate::QGeoRouteReplyPrivate(QGeoRouteReply::Error error, QString errorString) + : error(error), + errorString(errorString), + isFinished(true) {} + +QGeoRouteReplyPrivate::~QGeoRouteReplyPrivate() {} + +#include "moc_qgeoroutereply.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeoroutereply.h b/src/location/maps/qgeoroutereply.h new file mode 100644 index 0000000..318d85f --- /dev/null +++ b/src/location/maps/qgeoroutereply.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEREPLY_H +#define QGEOROUTEREPLY_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRouteRequest; +class QGeoRouteReplyPrivate; + +class Q_LOCATION_EXPORT QGeoRouteReply : public QObject +{ + Q_OBJECT +public: + enum Error { + NoError, + EngineNotSetError, + CommunicationError, + ParseError, + UnsupportedOptionError, + UnknownError + }; + + explicit QGeoRouteReply(Error error, const QString &errorString, QObject *parent = Q_NULLPTR); + virtual ~QGeoRouteReply(); + + bool isFinished() const; + Error error() const; + QString errorString() const; + + QGeoRouteRequest request() const; + QList routes() const; + + virtual void abort(); + +Q_SIGNALS: + void finished(); + void error(QGeoRouteReply::Error error, const QString &errorString = QString()); + +protected: + explicit QGeoRouteReply(const QGeoRouteRequest &request, QObject *parent = Q_NULLPTR); + + void setError(Error error, const QString &errorString); + void setFinished(bool finished); + + void setRoutes(const QList &routes); + void addRoutes(const QList &routes); + +private: + QGeoRouteReplyPrivate *d_ptr; + Q_DISABLE_COPY(QGeoRouteReply) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoroutereply_p.h b/src/location/maps/qgeoroutereply_p.h new file mode 100644 index 0000000..496a616 --- /dev/null +++ b/src/location/maps/qgeoroutereply_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEREPLY_P_H +#define QGEOROUTEREPLY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeorouterequest.h" +#include "qgeoroutereply.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoRoute; + +class QGeoRouteReplyPrivate +{ +public: + explicit QGeoRouteReplyPrivate(const QGeoRouteRequest &request); + QGeoRouteReplyPrivate(QGeoRouteReply::Error error, QString errorString); + ~QGeoRouteReplyPrivate(); + + QGeoRouteReply::Error error; + QString errorString; + bool isFinished; + + QGeoRouteRequest request; + QList routes; + +private: + Q_DISABLE_COPY(QGeoRouteReplyPrivate) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeorouterequest.cpp b/src/location/maps/qgeorouterequest.cpp new file mode 100644 index 0000000..369606e --- /dev/null +++ b/src/location/maps/qgeorouterequest.cpp @@ -0,0 +1,486 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeorouterequest.h" +#include "qgeorouterequest_p.h" + +#include "qgeocoordinate.h" +#include "qgeorectangle.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoRouteRequest + \inmodule QtLocation + \ingroup QtLocation-routing + \since 5.6 + + \brief The QGeoRouteRequest class represents the parameters and restrictions + which define a request for routing information. + + The default state of a QGeoRouteRequest instance will result in a request + for basic route segment and navigation maneuvers describing the fastest + route by car which covers the given waypoints. + + There may be significant variation in the features supported by different + providers of routing information, or even in the features supported by + the same provider if different levels of authorization are used. + + There are several functions in QGeoRoutingManager which can be used to + check which features are supported with the current provider and + authorization level. + \sa QGeoRoutingManager +*/ + +/* + DESIGN NOTE + + There are plans to make this extensible by allowing the user to set a + list of QGeoTransportOptions (or QGeoTransitOptions). We don't have any + subclasses for that just yet, otherwise they'd be present. + + A QGeoPublicTransportOption subclass would allow users to specify things + like cost limits, the maximum number of changes of vehicle, the maximum + walking time / distance between changes of vehicle. + + There's Nokia / Navteq support for specifying things like Truck attributes + so you can route around various road restrictions and conditions which + effect trucks. + + A QGeoTrafficAwareTransportOption is probably also a good place to put the + inputs for the various traffic / time aware bits of information. A + departure / arrrival time could be set, and the presence of this transport + options subclass (and the fact that user auth said that the user had + support) would mean we could provide better values for the estimated + travel times and so on. + + This all relies on at least one service making this data available to us, + which would probably be tied to token based authorization. It could be + some time before this becomes available. +*/ + +/*! + \enum QGeoRouteRequest::TravelMode + + Defines modes of travel to be used for a route. + + \value CarTravel + The route will be optimized for someone who is driving a car. + \value PedestrianTravel + The route will be optimized for someone who is walking. + \value BicycleTravel + The route will be optimized for someone who is riding a bicycle. + \value PublicTransitTravel + The route will be optimized for someone who is making use of public transit. + \value TruckTravel + The route will be optimized for someone who is driving a truck. +*/ + +/*! + \enum QGeoRouteRequest::FeatureType + + Defines a feature which is important to the planning of a route. + + These values will be used in combination with + QGeoRouteRequest::FeatureWeight to determine if they should or should + not be part of the route. + + \value NoFeature + Used by QGeoRoutingManager::supportedFeatureTypes() to indicate that + no features will be taken into account when planning the route. + \value TollFeature + Consdier tollways when planning the route. + \value HighwayFeature + Consider highways when planning the route. + \value PublicTransitFeature + Consider public transit when planning the route. + \value FerryFeature + Consider ferries when planning the route. + \value TunnelFeature + Consider tunnels when planning the route. + \value DirtRoadFeature + Consider dirt roads when planning the route. + \value ParksFeature + Consider parks when planning the route. + \value MotorPoolLaneFeature + Consider motor pool lanes when planning the route. +*/ + +/*! + \enum QGeoRouteRequest::FeatureWeight + + Defines the weight to associate with a feature during the + planning of a route. + + These values will be used in combination with + QGeoRouteRequest::Feature to determine if they should or should + not be part of the route. + + \value NeutralFeatureWeight + The presence or absence of the feature will not affect the + planning of the route. + \value PreferFeatureWeight + Routes which contain the feature will be preferred over those that do + not. + \value RequireFeatureWeight + Only routes which contain the feature will be considered, otherwise + no route will be returned. + \value AvoidFeatureWeight + Routes which do not contain the feature will be preferred over those + that do. + \value DisallowFeatureWeight + Only routes which do not contain the feature will be considered, + otherwise no route will be returned. +*/ + +// TODO improve description of MostScenicRoute +/*! + \enum QGeoRouteRequest::RouteOptimization + + Defines the type of optimization which is applied to the planning of the route. + + \value ShortestRoute + Minimize the length of the journey. + \value FastestRoute + Minimize the traveling time for the journey. + \value MostEconomicRoute + Minimize the cost of the journey. + \value MostScenicRoute + Maximize the scenic potential of the journey. +*/ + +/*! + \enum QGeoRouteRequest::SegmentDetail + + Defines the amount of route segment information that should be included + with the route. + + \value NoSegmentData + No segment data should be included with the route. A route requested + with this level of segment detail will initialize + QGeoRouteSegment::path() as a straight line between the positions of + the previous and next QGeoManeuver instances. + + \value BasicSegmentData + Basic segment data will be included with the route. This will include + QGeoRouteSegment::path(). +*/ + +/*! + \enum QGeoRouteRequest::ManeuverDetail + + Defines the amount of maneuver information that should be included with + the route. + + \value NoManeuvers + No maneuvers should be included with the route. + + \value BasicManeuvers + Basic manevuers will be included with the route. This will + include QGeoManeuver::instructionText(). +*/ + +/*! + Constructs a request to calculate a route through the coordinates \a waypoints. + + The route will traverse the objects of \a waypoints in order. +*/ +QGeoRouteRequest::QGeoRouteRequest(const QList &waypoints) + : d_ptr(new QGeoRouteRequestPrivate()) +{ + d_ptr->waypoints = waypoints; +} + +/*! + Constructs a request to calculate a route between \a origin and + \a destination. +*/ +QGeoRouteRequest::QGeoRouteRequest(const QGeoCoordinate &origin, const QGeoCoordinate &destination) + : d_ptr(new QGeoRouteRequestPrivate()) +{ + d_ptr->waypoints.append(origin); + d_ptr->waypoints.append(destination); +} + +/*! + Constructs a route request object from the contents of \a other. +*/ +QGeoRouteRequest::QGeoRouteRequest(const QGeoRouteRequest &other) + : d_ptr(other.d_ptr) {} + +/*! + Destroys the request. +*/ +QGeoRouteRequest::~QGeoRouteRequest() {} + +/*! + Assigns \a other to this route request object and then returns a reference + to this route request object. +*/ +QGeoRouteRequest &QGeoRouteRequest::operator= (const QGeoRouteRequest & other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns whether this route request and \a other are equal. +*/ +bool QGeoRouteRequest::operator ==(const QGeoRouteRequest &other) const +{ + return (d_ptr.constData() == other.d_ptr.constData()); +} + +/*! + Returns whether this route request and \a other are equal. +*/ +bool QGeoRouteRequest::operator !=(const QGeoRouteRequest &other) const +{ + return (d_ptr.constData() != other.d_ptr.constData()); +} + +/*! + Sets \a waypoints as the waypoints that the route should pass through. + + The waypoints should be given in order from origin to destination. + + This request will be invalid until the waypoints have been set to a + list containing two or more coordinates. +*/ +void QGeoRouteRequest::setWaypoints(const QList &waypoints) +{ + d_ptr->waypoints = waypoints; +} + +/*! + Returns the waypoints that the route will pass through. +*/ +QList QGeoRouteRequest::waypoints() const +{ + return d_ptr->waypoints; +} + +/*! + Sets \a areas as excluded areas that the route must not cross. +*/ +void QGeoRouteRequest::setExcludeAreas(const QList &areas) +{ + d_ptr->excludeAreas = areas; +} + +/*! + Returns areas the route must not cross. +*/ +QList QGeoRouteRequest::excludeAreas() const +{ + return d_ptr->excludeAreas; +} + +/*! + Sets the number of alternative routes to request to \a alternatives. If \a alternatives is + negative the number of alternative routes is set to 0. + + The default value is 0. +*/ +void QGeoRouteRequest::setNumberAlternativeRoutes(int alternatives) +{ + d_ptr->numberAlternativeRoutes = qMax(0, alternatives); +} + +/*! + Returns the number of alternative routes which will be requested. +*/ +int QGeoRouteRequest::numberAlternativeRoutes() const +{ + return d_ptr->numberAlternativeRoutes; +} + +/*! + Sets the travel modes which should be considered during the planning of the + route to \a travelModes. + + The default value is QGeoRouteRequest::CarTravel. +*/ +void QGeoRouteRequest::setTravelModes(QGeoRouteRequest::TravelModes travelModes) +{ + d_ptr->travelModes = travelModes; +} + +/*! + Returns the travel modes which this request specifies should be considered + during the planning of the route. +*/ +QGeoRouteRequest::TravelModes QGeoRouteRequest::travelModes() const +{ + return d_ptr->travelModes; +} + +/*! + Assigns the weight \a featureWeight to the feature \a featureType during + the planning of the route. + + By default all features are assigned a weight of NeutralFeatureWeight. + + It is impossible to assign a weight to QGeoRouteRequest::NoFeature. +*/ +void QGeoRouteRequest::setFeatureWeight(QGeoRouteRequest::FeatureType featureType, QGeoRouteRequest::FeatureWeight featureWeight) +{ + if (featureWeight != QGeoRouteRequest::NeutralFeatureWeight) { + if (featureType != QGeoRouteRequest::NoFeature) + d_ptr->featureWeights[featureType] = featureWeight; + } else { + d_ptr->featureWeights.remove(featureType); + } +} + +/*! + Returns the weight assigned to \a featureType in the planning of the route. + + If no feature weight has been specified for \a featureType then + NeutralFeatureWeight will be returned. +*/ +QGeoRouteRequest::FeatureWeight QGeoRouteRequest::featureWeight(QGeoRouteRequest::FeatureType featureType) const +{ + return d_ptr->featureWeights.value(featureType, QGeoRouteRequest::NeutralFeatureWeight); +} + +/*! + Returns the list of features that will be considered when planning the + route. Features with a weight of NeutralFeatureWeight will not be returned. +*/ +QList QGeoRouteRequest::featureTypes() const +{ + return d_ptr->featureWeights.keys(); +} + +/*! + Sets the optimization criteria to use while planning the route to + \a optimization. + + The default value is QGeoRouteRequest::FastestRoute. +*/ +void QGeoRouteRequest::setRouteOptimization(QGeoRouteRequest::RouteOptimizations optimization) +{ + d_ptr->routeOptimization = optimization; +} + +/*! + Returns the optimization criteria which this request specifies should be + used while planning the route. +*/ +QGeoRouteRequest::RouteOptimizations QGeoRouteRequest::routeOptimization() const +{ + return d_ptr->routeOptimization; +} + +/*! + Sets the level of detail to use when representing routing segments to + \a segmentDetail. +*/ +void QGeoRouteRequest::setSegmentDetail(QGeoRouteRequest::SegmentDetail segmentDetail) +{ + d_ptr->segmentDetail = segmentDetail; +} + +/*! + Returns the level of detail which will be used in the representation of + routing segments. +*/ +QGeoRouteRequest::SegmentDetail QGeoRouteRequest::segmentDetail() const +{ + return d_ptr->segmentDetail; +} + +/*! + Sets the level of detail to use when representing routing maneuvers to + \a maneuverDetail. + + The default value is QGeoRouteRequest::BasicManeuvers. +*/ +void QGeoRouteRequest::setManeuverDetail(QGeoRouteRequest::ManeuverDetail maneuverDetail) +{ + d_ptr->maneuverDetail = maneuverDetail; +} + +/*! + Returns the level of detail which will be used in the representation of + routing maneuvers. +*/ +QGeoRouteRequest::ManeuverDetail QGeoRouteRequest::maneuverDetail() const +{ + return d_ptr->maneuverDetail; +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoRouteRequestPrivate::QGeoRouteRequestPrivate() + : QSharedData(), + numberAlternativeRoutes(0), + travelModes(QGeoRouteRequest::CarTravel), + routeOptimization(QGeoRouteRequest::FastestRoute), + segmentDetail(QGeoRouteRequest::BasicSegmentData), + maneuverDetail(QGeoRouteRequest::BasicManeuvers) {} + +QGeoRouteRequestPrivate::QGeoRouteRequestPrivate(const QGeoRouteRequestPrivate &other) + : QSharedData(other), + waypoints(other.waypoints), + excludeAreas(other.excludeAreas), + numberAlternativeRoutes(other.numberAlternativeRoutes), + travelModes(other.travelModes), + featureWeights(other.featureWeights), + routeOptimization(other.routeOptimization), + segmentDetail(other.segmentDetail), + maneuverDetail(other.maneuverDetail) {} + +QGeoRouteRequestPrivate::~QGeoRouteRequestPrivate() {} + +bool QGeoRouteRequestPrivate::operator ==(const QGeoRouteRequestPrivate &other) const +{ + return ((waypoints == other.waypoints) + && (excludeAreas == other.excludeAreas) + && (numberAlternativeRoutes == other.numberAlternativeRoutes) + && (travelModes == other.travelModes) + && (featureWeights == other.featureWeights) + && (routeOptimization == other.routeOptimization) + && (segmentDetail == other.segmentDetail) + && (maneuverDetail == other.maneuverDetail)); +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeorouterequest.h b/src/location/maps/qgeorouterequest.h new file mode 100644 index 0000000..1692bbb --- /dev/null +++ b/src/location/maps/qgeorouterequest.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEREQUEST_H +#define QGEOROUTEREQUEST_H + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRectangle; +class QGeoRouteRequestPrivate; + +class Q_LOCATION_EXPORT QGeoRouteRequest +{ +public: + enum TravelMode { + CarTravel = 0x0001, + PedestrianTravel = 0x0002, + BicycleTravel = 0x0004, + PublicTransitTravel = 0x0008, + TruckTravel = 0x0010 + }; + Q_DECLARE_FLAGS(TravelModes, TravelMode) + + enum FeatureType { + NoFeature = 0x00000000, + TollFeature = 0x00000001, + HighwayFeature = 0x00000002, + PublicTransitFeature = 0x00000004, + FerryFeature = 0x00000008, + TunnelFeature = 0x00000010, + DirtRoadFeature = 0x00000020, + ParksFeature = 0x00000040, + MotorPoolLaneFeature = 0x00000080 + }; + Q_DECLARE_FLAGS(FeatureTypes, FeatureType) + + enum FeatureWeight { + NeutralFeatureWeight = 0x00000000, + PreferFeatureWeight = 0x00000001, + RequireFeatureWeight = 0x00000002, + AvoidFeatureWeight = 0x00000004, + DisallowFeatureWeight = 0x00000008 + }; + Q_DECLARE_FLAGS(FeatureWeights, FeatureWeight) + + enum RouteOptimization { + ShortestRoute = 0x0001, + FastestRoute = 0x0002, + MostEconomicRoute = 0x0004, + MostScenicRoute = 0x0008 + }; + Q_DECLARE_FLAGS(RouteOptimizations, RouteOptimization) + + enum SegmentDetail { + NoSegmentData = 0x0000, + BasicSegmentData = 0x0001 + }; + Q_DECLARE_FLAGS(SegmentDetails, SegmentDetail) + + enum ManeuverDetail { + NoManeuvers = 0x0000, + BasicManeuvers = 0x0001 + }; + Q_DECLARE_FLAGS(ManeuverDetails, ManeuverDetail) + + explicit QGeoRouteRequest(const QList &waypoints = QList()); + QGeoRouteRequest(const QGeoCoordinate &origin, + const QGeoCoordinate &destination); + QGeoRouteRequest(const QGeoRouteRequest &other); + + ~QGeoRouteRequest(); + + QGeoRouteRequest &operator= (const QGeoRouteRequest &other); + + bool operator == (const QGeoRouteRequest &other) const; + bool operator != (const QGeoRouteRequest &other) const; + + void setWaypoints(const QList &waypoints); + QList waypoints() const; + + void setExcludeAreas(const QList &areas); + QList excludeAreas() const; + + // defaults to 0 + void setNumberAlternativeRoutes(int alternatives); + int numberAlternativeRoutes() const; + + // defaults to TravelByCar + void setTravelModes(TravelModes travelModes); + TravelModes travelModes() const; + + void setFeatureWeight(FeatureType featureType, FeatureWeight featureWeight); + FeatureWeight featureWeight(FeatureType featureType) const; + QList featureTypes() const; + + // defaults to OptimizeFastest + void setRouteOptimization(RouteOptimizations optimization); + RouteOptimizations routeOptimization() const; + + // defaults to BasicSegmentData + void setSegmentDetail(SegmentDetail segmentDetail); + SegmentDetail segmentDetail() const; + + // defaults to BasicManeuvers + void setManeuverDetail(ManeuverDetail maneuverDetail); + ManeuverDetail maneuverDetail() const; + +private: + QExplicitlySharedDataPointer d_ptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoRouteRequest::TravelModes) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoRouteRequest::FeatureTypes) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoRouteRequest::FeatureWeights) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoRouteRequest::RouteOptimizations) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoRouteRequest::SegmentDetails) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoRouteRequest::ManeuverDetails) + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeorouterequest_p.h b/src/location/maps/qgeorouterequest_p.h new file mode 100644 index 0000000..ea0b142 --- /dev/null +++ b/src/location/maps/qgeorouterequest_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEREQUEST_P_H +#define QGEOROUTEREQUEST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeorouterequest.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRouteRequestPrivate : public QSharedData +{ +public: + QGeoRouteRequestPrivate(); + QGeoRouteRequestPrivate(const QGeoRouteRequestPrivate &other); + ~QGeoRouteRequestPrivate(); + + bool operator ==(const QGeoRouteRequestPrivate &other) const; + + QList waypoints; + QList excludeAreas; + int numberAlternativeRoutes; + QGeoRouteRequest::TravelModes travelModes; + QMap < QGeoRouteRequest::FeatureType, + QGeoRouteRequest::FeatureWeight > featureWeights; + QGeoRouteRequest::RouteOptimizations routeOptimization; + QGeoRouteRequest::SegmentDetail segmentDetail; + QGeoRouteRequest::ManeuverDetail maneuverDetail; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoroutesegment.cpp b/src/location/maps/qgeoroutesegment.cpp new file mode 100644 index 0000000..45c2cc6 --- /dev/null +++ b/src/location/maps/qgeoroutesegment.cpp @@ -0,0 +1,275 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroutesegment.h" +#include "qgeoroutesegment_p.h" + +#include "qgeocoordinate.h" +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoRouteSegment + \inmodule QtLocation + \ingroup QtLocation-routing + \since 5.6 + + \brief The QGeoRouteSegment class represents a segment of a route. + + A QGeoRouteSegment instance has information about the physical layout + of the route segment, the length of the route and estimated time required + to traverse the route segment and an optional QGeoManeuver associated with + the end of the route segment. + + QGeoRouteSegment instances can be thought of as edges on a routing + graph, with QGeoManeuver instances as optional labels attached to the + vertices of the graph. +*/ + +/*! + Constructs an invalid route segment object. + + The route segment will remain invalid until one of setNextRouteSegment(), + setTravelTime(), setDistance(), setPath() or setManeuver() is called. +*/ +QGeoRouteSegment::QGeoRouteSegment() + : d_ptr(new QGeoRouteSegmentPrivate()) {} + +/*! + Constructs a route segment object from the contents of \a other. +*/ +QGeoRouteSegment::QGeoRouteSegment(const QGeoRouteSegment &other) + : d_ptr(other.d_ptr) {} + +/*! + \internal +*/ +QGeoRouteSegment::QGeoRouteSegment(QExplicitlySharedDataPointer &d_ptr) + : d_ptr(d_ptr) {} + +/*! + Destroys this route segment object. +*/ +QGeoRouteSegment::~QGeoRouteSegment() {} + + +/*! + Assigns \a other to this route segment object and then returns a + reference to this route segment object. +*/ +QGeoRouteSegment &QGeoRouteSegment::operator= (const QGeoRouteSegment & other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns whether this route segment and \a other are equal. + + The value of nextRouteSegment() is not considered in the comparison. +*/ +bool QGeoRouteSegment::operator ==(const QGeoRouteSegment &other) const +{ + return ( (d_ptr.constData() == other.d_ptr.constData()) + || (*d_ptr) == (*other.d_ptr)); +} + +/*! + Returns whether this route segment and \a other are not equal. + + The value of nextRouteSegment() is not considered in the comparison. +*/ +bool QGeoRouteSegment::operator !=(const QGeoRouteSegment &other) const +{ + return !(operator==(other)); +} + +/*! + Returns whether this route segment is valid or not. + + If nextRouteSegment() is called on the last route segment of a route, the + returned value will be an invalid route segment. +*/ +bool QGeoRouteSegment::isValid() const +{ + return d_ptr->valid; +} + +/*! + Sets the next route segment in the route to \a routeSegment. +*/ +void QGeoRouteSegment::setNextRouteSegment(const QGeoRouteSegment &routeSegment) +{ + d_ptr->valid = true; + d_ptr->nextSegment = routeSegment.d_ptr; +} + +/*! + Returns the next route segment in the route. + + Will return an invalid route segment if this is the last route + segment in the route. +*/ +QGeoRouteSegment QGeoRouteSegment::nextRouteSegment() const +{ + if (d_ptr->valid && d_ptr->nextSegment) + return QGeoRouteSegment(d_ptr->nextSegment); + + QGeoRouteSegment segment; + segment.d_ptr->valid = false; + return segment; +} + +/*! + Sets the estimated amount of time it will take to traverse this segment of + the route, in seconds, to \a secs. +*/ +void QGeoRouteSegment::setTravelTime(int secs) +{ + d_ptr->valid = true; + d_ptr->travelTime = secs; +} + +/*! + Returns the estimated amount of time it will take to traverse this segment + of the route, in seconds. +*/ +int QGeoRouteSegment::travelTime() const +{ + return d_ptr->travelTime; +} + +/*! + Sets the distance covered by this segment of the route, in meters, to \a distance. +*/ +void QGeoRouteSegment::setDistance(qreal distance) +{ + d_ptr->valid = true; + d_ptr->distance = distance; +} + +/*! + Returns the distance covered by this segment of the route, in meters. +*/ +qreal QGeoRouteSegment::distance() const +{ + return d_ptr->distance; +} + +/*! + Sets the geometric shape of this segment of the route to \a path. + + The coordinates in \a path should be listed in the order in which they + would be traversed by someone traveling along this segment of the route. +*/ +void QGeoRouteSegment::setPath(const QList &path) +{ + d_ptr->valid = true; + d_ptr->path = path; +} + +/*! + Returns the geometric shape of this route segment of the route. + + The coordinates should be listed in the order in which they + would be traversed by someone traveling along this segment of the route. +*/ + +QList QGeoRouteSegment::path() const +{ + return d_ptr->path; +} + +/*! + Sets the maneuver for this route segment to \a maneuver. +*/ +void QGeoRouteSegment::setManeuver(const QGeoManeuver &maneuver) +{ + d_ptr->valid = true; + d_ptr->maneuver = maneuver; +} + +/*! + Returns the maneuver for this route segment. + + Will return an invalid QGeoManeuver if no information has been attached + to the endpoint of this route segment. +*/ +QGeoManeuver QGeoRouteSegment::maneuver() const +{ + return d_ptr->maneuver; +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoRouteSegmentPrivate::QGeoRouteSegmentPrivate() + : valid(false), + travelTime(0), + distance(0.0) {} + +QGeoRouteSegmentPrivate::QGeoRouteSegmentPrivate(const QGeoRouteSegmentPrivate &other) + : QSharedData(other), + valid(other.valid), + travelTime(other.travelTime), + distance(other.distance), + path(other.path), + maneuver(other.maneuver), + nextSegment(other.nextSegment) {} + +QGeoRouteSegmentPrivate::~QGeoRouteSegmentPrivate() +{ + nextSegment.reset(); +} + +bool QGeoRouteSegmentPrivate::operator ==(const QGeoRouteSegmentPrivate &other) const +{ + return ((valid == other.valid) + && (travelTime == other.travelTime) + && (distance == other.distance) + && (path == other.path) + && (maneuver == other.maneuver)); +} + +/******************************************************************************* +*******************************************************************************/ + +QT_END_NAMESPACE + diff --git a/src/location/maps/qgeoroutesegment.h b/src/location/maps/qgeoroutesegment.h new file mode 100644 index 0000000..bddc172 --- /dev/null +++ b/src/location/maps/qgeoroutesegment.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTESEGMENT_H +#define QGEOROUTESEGMENT_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCoordinate; +class QGeoManeuver; +class QGeoRouteSegmentPrivate; + +class Q_LOCATION_EXPORT QGeoRouteSegment +{ + +public: + QGeoRouteSegment(); + QGeoRouteSegment(const QGeoRouteSegment &other); + ~QGeoRouteSegment(); + + QGeoRouteSegment &operator= (const QGeoRouteSegment &other); + + bool operator ==(const QGeoRouteSegment &other) const; + bool operator !=(const QGeoRouteSegment &other) const; + + bool isValid() const; + + void setNextRouteSegment(const QGeoRouteSegment &routeSegment); + QGeoRouteSegment nextRouteSegment() const; + + void setTravelTime(int secs); + int travelTime() const; + + void setDistance(qreal distance); + qreal distance() const; + + void setPath(const QList &path); + QList path() const; + + void setManeuver(const QGeoManeuver &maneuver); + QGeoManeuver maneuver() const; + +protected: + QGeoRouteSegment(QExplicitlySharedDataPointer &d_ptr); + +private: + QExplicitlySharedDataPointer d_ptr; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoroutesegment_p.h b/src/location/maps/qgeoroutesegment_p.h new file mode 100644 index 0000000..1751266 --- /dev/null +++ b/src/location/maps/qgeoroutesegment_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTESEGMENT_P_H +#define QGEOROUTESEGMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeomaneuver.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCoordinate; + +class QGeoRouteSegmentPrivate : public QSharedData +{ +public: + QGeoRouteSegmentPrivate(); + QGeoRouteSegmentPrivate(const QGeoRouteSegmentPrivate &other); + ~QGeoRouteSegmentPrivate(); + + bool operator ==(const QGeoRouteSegmentPrivate &other) const; + + bool valid; + + int travelTime; + qreal distance; + QList path; + QGeoManeuver maneuver; + + QExplicitlySharedDataPointer nextSegment; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoroutingmanager.cpp b/src/location/maps/qgeoroutingmanager.cpp new file mode 100644 index 0000000..8eca671 --- /dev/null +++ b/src/location/maps/qgeoroutingmanager.cpp @@ -0,0 +1,413 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroutingmanager.h" +#include "qgeoroutingmanager_p.h" +#include "qgeoroutingmanagerengine.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoRoutingManager + \inmodule QtLocation + \ingroup QtLocation-routing + \since 5.6 + + \brief The QGeoRoutingManager class provides support for geographic routing + operations. + + The calculateRoute() and updateRoute() methods function QGeoRouteReply + objects, which manage these operations and report on the result of the + operations and any errors which may have occurred. + + The calculateRoute() function is used to find a route (or routes) that + follows a set of waypoints and matches various other criteria. The + QGeoRouteRequest class is used to specify this information. + + If supportsRouteUpdates() returns true then the QGeoRoutingManager + supports updating route information based on position updates. This + will cause the travel time and distance estimates to be updated, and + any QGeoRouteSegments already traversed to be removed from the route. + + The updates can be triggered with the updateRoute() function, which makes + use of the QGeoPositionInfo instances emitted as position updates by + QGeoPositionInfoSource. + + Instances of QGeoRoutingManager can be accessed with + QGeoServiceProvider::routingManager(). + + A small example of the usage of QGeoRoutingManager and QGeoRouteRequests + follows: + + \code +class MyRouteHandler : public QObject +{ + Q_OBJECT +public: + MyRouteHandler(QGeoRoutingManager *routingManager, + const QGeoCoordinate &origin, + const QGeoCoordinate &destination) { + + QGeoRouteRequest request(origin, destination); + + // The request defaults to the fastest route by car, which is + // equivalent to: + // request.setTravelMode(QGeoRouteRequest::CarTravel); + // request.setRouteOptimization(QGeoRouteRequest::FastestRoute); + + request.setAvoidFeatureTypes(QGeoRouteRequest::AvoidTolls); + request.setAvoidFeatureTypes(QGeoRouteRequest::AvoidMotorPoolLanes); + + QGeoRouteRequest::AvoidFeaturesTypes avoidableFeatures = routingManager->supportedAvoidFeatureTypes(); + + if (!(avoidableFeatures & request.avoidFeatureTypes())) { + // ... inform the user that the routing manager does not + // provide support for avoiding tolls and/or motor pool lanes ... + return; + } + + QGeoRouteReply *reply = routingManager->calculateRoute(request); + + if (reply->isFinished()) { + if (reply->error() == QGeoRouteReply::NoError) { + routeCalculated(reply); + } else { + routeError(reply, reply->error(), reply->errorString()); + } + return; + } + + connect(routingManager, + SIGNAL(finished(QGeoRouteReply*)), + this, + SLOT(routeCalculated(QGeoRouteReply*))); + + connect(routingManager, + SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString)), + this, + SLOT(routeError(QGeoRouteReply*,QGeoRouteReply::Error,QString))); + } + +private slots: + void routeCalculated(QGeoRouteReply *reply) + { + // A route request can ask for several alternative routes ... + if (reply->routes().size() != 0) { + + // ... but by default it will only get a single route + QGeoRoute route = reply->routes().at(0); + + //... now we have to make use of the route ... + } + + reply->deleteLater(); + } + + void routeError(QGeoRouteReply *reply, QGeoRouteReply:Error error, const QString &errorString) + { + // ... inform the user that an error has occurred ... + reply->deleteLater(); + } +}; + \endcode +*/ + +/*! + Constructs a new manager with the specified \a parent and with the + implementation provided by \a engine. + + This constructor is used internally by QGeoServiceProviderFactory. Regular + users should acquire instances of QGeoRoutingManager with + QGeoServiceProvider::routingManager(); +*/ +QGeoRoutingManager::QGeoRoutingManager(QGeoRoutingManagerEngine *engine, QObject *parent) + : QObject(parent), + d_ptr(new QGeoRoutingManagerPrivate()) +{ + d_ptr->engine = engine; + if (d_ptr->engine) { + d_ptr->engine->setParent(this); + + connect(d_ptr->engine, + SIGNAL(finished(QGeoRouteReply*)), + this, + SIGNAL(finished(QGeoRouteReply*))); + + connect(d_ptr->engine, + SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString)), + this, + SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString))); + } else { + qFatal("The routing manager engine that was set for this routing manager was NULL."); + } +} + +/*! + Destroys this manager. +*/ +QGeoRoutingManager::~QGeoRoutingManager() +{ + delete d_ptr; +} + +/*! + Returns the name of the engine which implements the behaviour of this + routing manager. + + The combination of managerName() and managerVersion() should be unique + amongst the plugin implementations. +*/ +QString QGeoRoutingManager::managerName() const +{ + return d_ptr->engine->managerName(); +} + +/*! + Returns the version of the engine which implements the behaviour of this + routin manager. + + The combination of managerName() and managerVersion() should be unique + amongst the plugin implementations. +*/ +int QGeoRoutingManager::managerVersion() const +{ + return d_ptr->engine->managerVersion(); +} + +/*! + Begins the calculation of the route specified by \a request. + + A QGeoRouteReply object will be returned, which can be used to manage the + routing operation and to return the results of the operation. + + This manager and the returned QGeoRouteReply object will emit signals + indicating if the operation completes or if errors occur. + + Once the operation has completed, QGeoRouteReply::routes can be used to + retrieve the calculated route or routes. + + If \a request includes features which are not supported by this manager, as + reported by the methods in this manager, then a + QGeoRouteReply::UnsupportedOptionError will occur. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoRoutingManager::finished(), + QGeoRoutingManager::error(), QGeoRouteReply::finished() or + QGeoRouteReply::error() with deleteLater(). +*/ +QGeoRouteReply *QGeoRoutingManager::calculateRoute(const QGeoRouteRequest &request) +{ + return d_ptr->engine->calculateRoute(request); +} + +/*! + Begins the process of updating \a route based on the current position \a + position. + + A QGeoRouteReply object will be returned, which can be used to manage the + routing operation and to return the results of the operation. + + This manager and the returned QGeoRouteReply object will emit signals + indicating if the operation completes or if errors occur. + + If supportsRouteUpdates() returns false an + QGeoRouteReply::UnsupportedOptionError will occur. + + Once the operation has completed, QGeoRouteReply::routes can be used to + retrieve the updated route. + + The returned route could be entirely different to the original route, + especially if \a position is far away from the initial route. + Otherwise the route will be similar, although the remaining time and + distance will be updated and any segments of the original route which + have been traversed will be removed. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoRoutingManager::finished(), + QGeoRoutingManager::error(), QGeoRouteReply::finished() or + QGeoRouteReply::error() with deleteLater(). +*/ +QGeoRouteReply *QGeoRoutingManager::updateRoute(const QGeoRoute &route, const QGeoCoordinate &position) +{ + return d_ptr->engine->updateRoute(route, position); +} + +/*! + Returns the travel modes supported by this manager. +*/ +QGeoRouteRequest::TravelModes QGeoRoutingManager::supportedTravelModes() const +{ + return d_ptr->engine->supportedTravelModes(); +} + +/*! + Returns the types of features that this manager can take into account + during route planning. +*/ +QGeoRouteRequest::FeatureTypes QGeoRoutingManager::supportedFeatureTypes() const +{ + return d_ptr->engine->supportedFeatureTypes(); +} + +/*! + Returns the weightings which this manager can apply to different features + during route planning. +*/ +QGeoRouteRequest::FeatureWeights QGeoRoutingManager::supportedFeatureWeights() const +{ + return d_ptr->engine->supportedFeatureWeights(); +} + +/*! + Returns the route optimizations supported by this manager. +*/ +QGeoRouteRequest::RouteOptimizations QGeoRoutingManager::supportedRouteOptimizations() const +{ + return d_ptr->engine->supportedRouteOptimizations(); +} + +/*! + Returns the levels of detail for routing segments which can be requested + with this manager. +*/ +QGeoRouteRequest::SegmentDetails QGeoRoutingManager::supportedSegmentDetails() const +{ + return d_ptr->engine->supportedSegmentDetails(); +} + +/*! + Returns the levels of detail for navigation maneuvers which can be + requested by this manager. +*/ +QGeoRouteRequest::ManeuverDetails QGeoRoutingManager::supportedManeuverDetails() const +{ + return d_ptr->engine->supportedManeuverDetails(); +} + +/*! + Sets the locale to be used by this manager to \a locale. + + If this routing manager supports returning addresses and instructions + in different languages, they will be returned in the language of \a locale. + + The locale used defaults to the system locale if this is not set. +*/ +void QGeoRoutingManager::setLocale(const QLocale &locale) +{ + d_ptr->engine->setLocale(locale); +} + +/*! + Returns the locale used to hint to this routing manager about what + language to use for addresses and instructions. +*/ +QLocale QGeoRoutingManager::locale() const +{ + return d_ptr->engine->locale(); +} + +/*! + Sets the measurement system used by this manager to \a system. + + The measurement system can be set independently of the locale. Both setLocale() and this + function set the measurement system. The value set by the last function called will be used. + + \sa measurementSystem(), locale(), setLocale() +*/ +void QGeoRoutingManager::setMeasurementSystem(QLocale::MeasurementSystem system) +{ + d_ptr->engine->setMeasurementSystem(system); +} + +/*! + Returns the measurement system used by this manager. + + If setMeasurementSystem() has been called then the value returned by this function may be + different to that returned by locale().\l {QLocale::measurementSystem()}{measurementSystem()}. + In which case the value returned by this function is what will be used by the manager. + + \sa setMeasurementSystem(), setLocale() +*/ +QLocale::MeasurementSystem QGeoRoutingManager::measurementSystem() const +{ + return d_ptr->engine->measurementSystem(); +} + +/*! +\fn void QGeoRoutingManager::finished(QGeoRouteReply *reply) + +This signal is emitted when \a reply has finished processing. + +If reply::error() equals QGeoRouteReply::NoError then the processing +finished successfully. + +This signal and QGeoRouteReply::finished() will be emitted at the same time. + +\note Do not delete the \a reply object in the slot connected to this signal. +Use deleteLater() instead. +*/ + +/*! +\fn void QGeoRoutingManager::error(QGeoRouteReply *reply, QGeoRouteReply::Error error, QString errorString) + +This signal is emitted when an error has been detected in the processing of +\a reply. The QGeoRoutingManager::finished() signal will probably follow. + +The error will be described by the error code \a error. If \a errorString is +not empty it will contain a textual description of the error. + +This signal and QGeoRouteReply::error() will be emitted at the same time. + +\note Do not delete the \a reply object in the slot connected to this signal. +Use deleteLater() instead. +*/ + +/******************************************************************************* +*******************************************************************************/ + +QGeoRoutingManagerPrivate::QGeoRoutingManagerPrivate() + : engine(0) {} + +QGeoRoutingManagerPrivate::~QGeoRoutingManagerPrivate() +{ + delete engine; +} + +#include "moc_qgeoroutingmanager.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeoroutingmanager.h b/src/location/maps/qgeoroutingmanager.h new file mode 100644 index 0000000..56cd8e5 --- /dev/null +++ b/src/location/maps/qgeoroutingmanager.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTINGMANAGER_H +#define QGEOROUTINGMANAGER_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRoutingManagerEngine; +class QGeoRoutingManagerPrivate; + +class Q_LOCATION_EXPORT QGeoRoutingManager : public QObject +{ + Q_OBJECT + +public: + ~QGeoRoutingManager(); + + QString managerName() const; + int managerVersion() const; + + QGeoRouteReply *calculateRoute(const QGeoRouteRequest &request); + QGeoRouteReply *updateRoute(const QGeoRoute &route, const QGeoCoordinate &position); + + QGeoRouteRequest::TravelModes supportedTravelModes() const; + QGeoRouteRequest::FeatureTypes supportedFeatureTypes() const; + QGeoRouteRequest::FeatureWeights supportedFeatureWeights() const; + QGeoRouteRequest::RouteOptimizations supportedRouteOptimizations() const; + QGeoRouteRequest::SegmentDetails supportedSegmentDetails() const; + QGeoRouteRequest::ManeuverDetails supportedManeuverDetails() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + void setMeasurementSystem(QLocale::MeasurementSystem system); + QLocale::MeasurementSystem measurementSystem() const; + +Q_SIGNALS: + void finished(QGeoRouteReply *reply); + void error(QGeoRouteReply *reply, QGeoRouteReply::Error error, QString errorString = QString()); + +private: + explicit QGeoRoutingManager(QGeoRoutingManagerEngine *engine, QObject *parent = Q_NULLPTR); + + QGeoRoutingManagerPrivate *d_ptr; + Q_DISABLE_COPY(QGeoRoutingManager) + + friend class QGeoServiceProvider; + friend class QGeoServiceProviderPrivate; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoroutingmanager_p.h b/src/location/maps/qgeoroutingmanager_p.h new file mode 100644 index 0000000..b3a9f2f --- /dev/null +++ b/src/location/maps/qgeoroutingmanager_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTINGMANAGER_P_H +#define QGEOROUTINGMANAGER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +class QGeoRoutingManagerEngine; + +class QGeoRoutingManagerPrivate +{ +public: + QGeoRoutingManagerPrivate(); + ~QGeoRoutingManagerPrivate(); + + QGeoRoutingManagerEngine *engine; + +private: + Q_DISABLE_COPY(QGeoRoutingManagerPrivate) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoroutingmanagerengine.cpp b/src/location/maps/qgeoroutingmanagerengine.cpp new file mode 100644 index 0000000..9c55268 --- /dev/null +++ b/src/location/maps/qgeoroutingmanagerengine.cpp @@ -0,0 +1,425 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroutingmanagerengine.h" +#include "qgeoroutingmanagerengine_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoRoutingManagerEngine + \inmodule QtLocation + \ingroup QtLocation-impl + \since 5.6 + + \brief The QGeoRoutingManagerEngine class provides an interface and + convenience methods to implementers of QGeoServiceProvider plugins who want + to provide access to geographic routing information. + + Subclasses of QGeoRoutingManagerEngine need to provide an implementation of + calculateRoute(). + + In the default implementation, supportsRouteUpdates() returns false and + updateRoute() returns a QGeoRouteReply object containing a + QGeoRouteReply::UnsupportedOptionError. + + If the routing service supports updating routes as they are being + traveled, the subclass should provide an implementation of updateRoute() + and call setSupportsRouteUpdates(true) at some point in time before + updateRoute() is called. + + The function setSupportsRouteUpdates() is one of several functions which + configure the reported capabilities of the engine. If the capabilities + of an engine differ from the default values these functions should be + used so that the reported capabilities are accurate. + + It is important that this is done before calculateRoute(), updateRoute() + or any of the capability reporting functions are used to prevent + incorrect or inconsistent behavior. + + A subclass of QGeoRouteManagerEngine will often make use of a subclass + fo QGeoRouteReply internally, in order to add any engine-specific + data (such as a QNetworkReply object for network-based services) to the + QGeoRouteReply instances used by the engine. + + \sa QGeoRoutingManager +*/ + +/*! + Constructs a new engine with the specified \a parent, using \a parameters + to pass any implementation specific data to the engine. +*/ +QGeoRoutingManagerEngine::QGeoRoutingManagerEngine(const QVariantMap ¶meters, QObject *parent) + : QObject(parent), + d_ptr(new QGeoRoutingManagerEnginePrivate()) +{ + Q_UNUSED(parameters) +} + +/*! + Destroys this engine. +*/ +QGeoRoutingManagerEngine::~QGeoRoutingManagerEngine() +{ + delete d_ptr; +} + +/*! + Sets the name which this engine implementation uses to distinguish itself + from the implementations provided by other plugins to \a managerName. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +void QGeoRoutingManagerEngine::setManagerName(const QString &managerName) +{ + d_ptr->managerName = managerName; +} + +/*! + Returns the name which this engine implementation uses to distinguish + itself from the implementations provided by other plugins. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +QString QGeoRoutingManagerEngine::managerName() const +{ + return d_ptr->managerName; +} + +/*! + Sets the version of this engine implementation to \a managerVersion. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +void QGeoRoutingManagerEngine::setManagerVersion(int managerVersion) +{ + d_ptr->managerVersion = managerVersion; +} + +/*! + Returns the version of this engine implementation. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +int QGeoRoutingManagerEngine::managerVersion() const +{ + return d_ptr->managerVersion; +} + +/*! +\fn QGeoRouteReply *QGeoRoutingManagerEngine::calculateRoute(const QGeoRouteRequest &request) + + Begins the calculation of the route specified by \a request. + + A QGeoRouteReply object will be returned, which can be used to manage the + routing operation and to return the results of the operation. + + This engine and the returned QGeoRouteReply object will emit signals + indicating if the operation completes or if errors occur. + + Once the operation has completed, QGeoRouteReply::routes can be used to + retrieve the calculated route or routes. + + If \a request includes features which are not supported by this engine, as + reported by the methods in this engine, then a + QGeoRouteReply::UnsupportedOptionError will occur. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoRoutingManagerEngine::finished(), + QGeoRoutingManagerEngine::error(), QGeoRouteReply::finished() or + QGeoRouteReply::error() with deleteLater(). +*/ + +/*! + Begins the process of updating \a route based on the current position \a + position. + + A QGeoRouteReply object will be returned, which can be used to manage the + routing operation and to return the results of the operation. + + This engine and the returned QGeoRouteReply object will emit signals + indicating if the operation completes or if errors occur. + + If supportsRouteUpdates() returns false an + QGeoRouteReply::UnsupportedOptionError will occur. + + Once the operation has completed, QGeoRouteReply::routes can be used to + retrieve the updated route. + + The returned route could be entirely different to the original route, + especially if \a position is far enough away from the initial route. + Otherwise the route will be similar, although the remaining time and + distance will be updated and any segments of the original route which + have been traversed will be removed. + + The user is responsible for deleting the returned reply object, although + this can be done in the slot connected to QGeoRoutingManagerEngine::finished(), + QGeoRoutingManagerEngine::error(), QGeoRouteReply::finished() or + QGeoRouteReply::error() with deleteLater(). +*/ +QGeoRouteReply *QGeoRoutingManagerEngine::updateRoute(const QGeoRoute &route, const QGeoCoordinate &position) +{ + Q_UNUSED(route) + Q_UNUSED(position) + return new QGeoRouteReply(QGeoRouteReply::UnsupportedOptionError, + QLatin1String("The updating of routes is not supported by this service provider."), this); +} + +/*! + Sets the travel modes supported by this engine to \a travelModes. + + It is important that subclasses use this method to ensure that the engine + reports its capabilities correctly. If this function is not used the + engine will report that it supports no travel modes at all. +*/ +void QGeoRoutingManagerEngine::setSupportedTravelModes(QGeoRouteRequest::TravelModes travelModes) +{ + d_ptr->supportedTravelModes = travelModes; +} + +/*! + Returns the travel modes supported by this engine. +*/ +QGeoRouteRequest::TravelModes QGeoRoutingManagerEngine::supportedTravelModes() const +{ + return d_ptr->supportedTravelModes; +} + +/*! + Sets the types of features that this engine can take into account + during route planning to \a featureTypes. + + It is important that subclasses use this method to ensure that the engine + reports its capabilities correctly. If this function is not used the + engine will report that it supports no feature types at all. +*/ +void QGeoRoutingManagerEngine::setSupportedFeatureTypes(QGeoRouteRequest::FeatureTypes featureTypes) +{ + d_ptr->supportedFeatureTypes = featureTypes; +} + +/*! + Returns the types of features that this engine can take into account + during route planning. +*/ +QGeoRouteRequest::FeatureTypes QGeoRoutingManagerEngine::supportedFeatureTypes() const +{ + return d_ptr->supportedFeatureTypes; +} + +/*! + Sets the weightings which this engine can apply to different features + during route planning to \a featureWeights. + + It is important that subclasses use this method to ensure that the engine + reports its capabilities correctly. If this function is not used the + engine will report that it supports no feature weights at all. +*/ +void QGeoRoutingManagerEngine::setSupportedFeatureWeights(QGeoRouteRequest::FeatureWeights featureWeights) +{ + d_ptr->supportedFeatureWeights = featureWeights; + d_ptr->supportedFeatureWeights |= QGeoRouteRequest::NeutralFeatureWeight; +} + +/*! + Returns the weightings which this engine can apply to different features + during route planning. +*/ +QGeoRouteRequest::FeatureWeights QGeoRoutingManagerEngine::supportedFeatureWeights() const +{ + return d_ptr->supportedFeatureWeights; +} + +/*! + Sets the route optimizations supported by this engine to \a optimizations. + + It is important that subclasses use this method to ensure that the engine + reports its capabilities correctly. If this function is not used the + engine will report that it supports no route optimizations at all. +*/ +void QGeoRoutingManagerEngine::setSupportedRouteOptimizations(QGeoRouteRequest::RouteOptimizations optimizations) +{ + d_ptr->supportedRouteOptimizations = optimizations; +} + +/*! + Returns the route optimizations supported by this engine. +*/ +QGeoRouteRequest::RouteOptimizations QGeoRoutingManagerEngine::supportedRouteOptimizations() const +{ + return d_ptr->supportedRouteOptimizations; +} + +/*! + Sets the levels of detail for routing segments which can be + requested by this engine to \a segmentDetails. + + It is important that subclasses use this method to ensure that the engine + reports its capabilities correctly. If this function is not used the + engine will report that it supports no segment detail at all. +*/ +void QGeoRoutingManagerEngine::setSupportedSegmentDetails(QGeoRouteRequest::SegmentDetails segmentDetails) +{ + d_ptr->supportedSegmentDetails = segmentDetails; +} + +/*! + Returns the levels of detail for routing segments which can be + requested by this engine. +*/ +QGeoRouteRequest::SegmentDetails QGeoRoutingManagerEngine::supportedSegmentDetails() const +{ + return d_ptr->supportedSegmentDetails; +} + +/*! + Sets the levels of detail for navigation maneuvers which can be + requested by this engine to \a maneuverDetails. + + It is important that subclasses use this method to ensure that the engine + reports its capabilities correctly. If this function is not used the + engine will report that it supports no maneuver details at all. +*/ +void QGeoRoutingManagerEngine::setSupportedManeuverDetails(QGeoRouteRequest::ManeuverDetails maneuverDetails) +{ + d_ptr->supportedManeuverDetails = maneuverDetails; +} + +/*! + Returns the levels of detail for navigation maneuvers which can be + requested by this engine. +*/ +QGeoRouteRequest::ManeuverDetails QGeoRoutingManagerEngine::supportedManeuverDetails() const +{ + return d_ptr->supportedManeuverDetails; +} + +/*! + Sets the locale to be used by this manager to \a locale. + + If this routing manager supports returning addresses and instructions + in different languages, they will be returned in the language of \a locale. + + The locale used defaults to the system locale if this is not set. +*/ +void QGeoRoutingManagerEngine::setLocale(const QLocale &locale) +{ + d_ptr->locale = locale; + d_ptr->measurementSystem = locale.measurementSystem(); +} + +/*! + Returns the locale used to hint to this routing manager about what + language to use for addresses and instructions. +*/ +QLocale QGeoRoutingManagerEngine::locale() const +{ + return d_ptr->locale; +} + +/*! + Sets the measurement system used by this manager to \a system. + + The measurement system can be set independently of the locale. Both setLocale() and this + function set the measurement system. The value set by the last function called will be used. + + \sa measurementSystem(), locale(), setLocale() +*/ +void QGeoRoutingManagerEngine::setMeasurementSystem(QLocale::MeasurementSystem system) +{ + d_ptr->measurementSystem = system; +} + +/*! + Returns the measurement system used by this manager. + + If setMeasurementSystem() has been called then the value returned by this function may be + different to that returned by locale().\l {QLocale::measurementSystem()}{measurementSystem()}. + In which case the value returned by this function is what will be used by the manager. + + \sa setMeasurementSystem(), setLocale() +*/ +QLocale::MeasurementSystem QGeoRoutingManagerEngine::measurementSystem() const +{ + return d_ptr->measurementSystem; +} + +/*! +\fn void QGeoRoutingManagerEngine::finished(QGeoRouteReply *reply) + +This signal is emitted when \a reply has finished processing. + +If reply::error() equals QGeoRouteReply::NoError then the processing +finished successfully. + +This signal and QGeoRouteReply::finished() will be emitted at the same time. + +\note Do not delete the \a reply object in the slot connected to this signal. +Use deleteLater() instead. +*/ + +/*! +\fn void QGeoRoutingManagerEngine::error(QGeoRouteReply *reply, QGeoRouteReply::Error error, QString errorString) + +This signal is emitted when an error has been detected in the processing of +\a reply. The QGeoRoutingManagerEngine::finished() signal will probably follow. + +The error will be described by the error code \a error. If \a errorString is +not empty it will contain a textual description of the error. + +This signal and QGeoRouteReply::error() will be emitted at the same time. + +\note Do not delete the \a reply object in the slot connected to this signal. +Use deleteLater() instead. +*/ + +/******************************************************************************* +*******************************************************************************/ + +QGeoRoutingManagerEnginePrivate::QGeoRoutingManagerEnginePrivate() +: managerVersion(-1), measurementSystem(locale.measurementSystem()) +{ +} + +QGeoRoutingManagerEnginePrivate::~QGeoRoutingManagerEnginePrivate() {} + +#include "moc_qgeoroutingmanagerengine.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeoroutingmanagerengine.h b/src/location/maps/qgeoroutingmanagerengine.h new file mode 100644 index 0000000..21aef7c --- /dev/null +++ b/src/location/maps/qgeoroutingmanagerengine.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTINGMANAGERENGINE_H +#define QGEOROUTINGMANAGERENGINE_H + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRoutingManagerEnginePrivate; + +class Q_LOCATION_EXPORT QGeoRoutingManagerEngine : public QObject +{ + Q_OBJECT +public: + explicit QGeoRoutingManagerEngine(const QVariantMap ¶meters, QObject *parent = Q_NULLPTR); + virtual ~QGeoRoutingManagerEngine(); + + QString managerName() const; + int managerVersion() const; + + virtual QGeoRouteReply *calculateRoute(const QGeoRouteRequest &request) = 0; + virtual QGeoRouteReply *updateRoute(const QGeoRoute &route, const QGeoCoordinate &position); + + QGeoRouteRequest::TravelModes supportedTravelModes() const; + QGeoRouteRequest::FeatureTypes supportedFeatureTypes() const; + QGeoRouteRequest::FeatureWeights supportedFeatureWeights() const; + QGeoRouteRequest::RouteOptimizations supportedRouteOptimizations() const; + QGeoRouteRequest::SegmentDetails supportedSegmentDetails() const; + QGeoRouteRequest::ManeuverDetails supportedManeuverDetails() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + void setMeasurementSystem(QLocale::MeasurementSystem system); + QLocale::MeasurementSystem measurementSystem() const; + +Q_SIGNALS: + void finished(QGeoRouteReply *reply); + void error(QGeoRouteReply *reply, QGeoRouteReply::Error error, QString errorString = QString()); + +protected: + void setSupportedTravelModes(QGeoRouteRequest::TravelModes travelModes); + void setSupportedFeatureTypes(QGeoRouteRequest::FeatureTypes featureTypes); + void setSupportedFeatureWeights(QGeoRouteRequest::FeatureWeights featureWeights); + void setSupportedRouteOptimizations(QGeoRouteRequest::RouteOptimizations optimizations); + void setSupportedSegmentDetails(QGeoRouteRequest::SegmentDetails segmentDetails); + void setSupportedManeuverDetails(QGeoRouteRequest::ManeuverDetails maneuverDetails); + +private: + void setManagerName(const QString &managerName); + void setManagerVersion(int managerVersion); + + QGeoRoutingManagerEnginePrivate *d_ptr; + Q_DISABLE_COPY(QGeoRoutingManagerEngine) + + friend class QGeoServiceProvider; + friend class QGeoServiceProviderPrivate; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoroutingmanagerengine_p.h b/src/location/maps/qgeoroutingmanagerengine_p.h new file mode 100644 index 0000000..7ba6c3d --- /dev/null +++ b/src/location/maps/qgeoroutingmanagerengine_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTINGMANAGERENGINE_P_H +#define QGEOROUTINGMANAGERENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeorouterequest.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRoutingManagerEnginePrivate +{ +public: + QGeoRoutingManagerEnginePrivate(); + ~QGeoRoutingManagerEnginePrivate(); + + QString managerName; + int managerVersion; + + QGeoRouteRequest::TravelModes supportedTravelModes; + QGeoRouteRequest::FeatureTypes supportedFeatureTypes; + QGeoRouteRequest::FeatureWeights supportedFeatureWeights; + QGeoRouteRequest::RouteOptimizations supportedRouteOptimizations; + QGeoRouteRequest::SegmentDetails supportedSegmentDetails; + QGeoRouteRequest::ManeuverDetails supportedManeuverDetails; + + QLocale locale; + QLocale::MeasurementSystem measurementSystem; + +private: + Q_DISABLE_COPY(QGeoRoutingManagerEnginePrivate) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoserviceprovider.cpp b/src/location/maps/qgeoserviceprovider.cpp new file mode 100644 index 0000000..51b0de6 --- /dev/null +++ b/src/location/maps/qgeoserviceprovider.cpp @@ -0,0 +1,730 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoserviceprovider.h" +#include "qgeoserviceprovider_p.h" +#include "qgeoserviceproviderfactory.h" + +#include "qgeocodingmanager.h" +#include "qgeomappingmanager_p.h" +#include "qgeoroutingmanager.h" +#include "qplacemanager.h" +#include "qgeocodingmanagerengine.h" +#include "qgeomappingmanagerengine_p.h" +#include "qgeoroutingmanagerengine.h" +#include "qplacemanagerengine.h" +#include "qplacemanagerengine_p.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + ("org.qt-project.qt.geoservice.serviceproviderfactory/5.0", + QLatin1String("/geoservices"))) +#endif + +/*! + \class QGeoServiceProvider + \inmodule QtLocation + \ingroup QtLocation-common + \since 5.6 + + \brief The QGeoServiceProvider class aggregates access to services which provide + geographical information. + + The Maps and Navigation API allows people to access various kinds of + geographical information, including functionality to perform geocoding, + routing and the display of maps. The QGeoServiceProvider aggregates the + access to a set of these services that are provided by a single vendor. + + It is possible to mix and match service providers for the various domains, + so that a geocoding manager from one service provider can be used with + a geographic routing manager from another service provider. + + This is not recommended unless the client is able to verify that the + data provided by the different services are compatible, as differences + in the underlying data sets could cause serious incongruences between + the services. + + Subclasses of QGeoServiceProvider guarantee that the different services + that they provide are interoperable. + + At this point there are two GeoServices plugins packaged with Qt. They are + accessible using their provider names: + + \list + \li "mapbox" -> \l {Qt Location Mapbox Plugin}{Mapbox service} + \li "here" -> \l {Qt Location HERE Plugin}{HERE Services} + \li "osm" -> \l {Qt Location Open Street Map Plugin}{OpenStreetMap Services} + \endlist + + Each service provider must follow a naming convention for their service specific + parameter names/keys. They use the provider name as prefix for all their + parameter names. For example, the \l {Qt Location HERE Plugin}{HERE} service provider + requires the \c here.app_id parameter. When a provider is loaded only those parameters are + passed on whose parameter names start with the provider name. This avoids the sharing + sensitive parameters such as confidential \c token or \c app_id parameters with other + plugins. + + Please check the GeoServices plugin specific documentation to + obtain a complete list of the available parameter names/keys and values. +*/ + +/*! + \enum QGeoServiceProvider::Error + + Describes an error related to the loading and setup of a service provider plugin. + + \value NoError + No error has occurred. + \value NotSupportedError + The plugin does not support this functionality. + \value UnknownParameterError + The plugin did not recognize one of the parameters it was given. + \value MissingRequiredParameterError + The plugin did not find one of the parameters it was expecting. + \value ConnectionError + The plugin could not connect to its backend service or database. +*/ + +/*! + \enum QGeoServiceProvider::RoutingFeature + + Describes the routing features supported by the geo service provider. + + \value NoRoutingFeatures No routing features are supported. + \value OnlineRoutingFeature Online routing is supported. + \value OfflineRoutingFeature Offline routing is supported. + \value LocalizedRoutingFeature Supports returning routes with localized addresses and + instructions. + \value RouteUpdatesFeature Updating an existing route based on the current position is + supported. + \value AlternativeRoutesFeature Supports returning alternative routes. + \value ExcludeAreasRoutingFeature Supports specifying a areas which the returned route must + not cross. + \value AnyRoutingFeatures Matches a geo service provider that provides any routing + features. +*/ + +/*! + \enum QGeoServiceProvider::GeocodingFeature + + Describes the geocoding features supported by the geo service provider. + + \value NoGeocodingFeatures No geocoding features are supported. + \value OnlineGeocodingFeature Online geocoding is supported. + \value OfflineGeocodingFeature Offline geocoding is supported. + \value ReverseGeocodingFeature Reverse geocoding is supported. + \value LocalizedGeocodingFeature Supports returning geocoding results with localized + addresses. + \value AnyGeocodingFeatures Matches a geo service provider that provides any geocoding + features. +*/ + +/*! + \enum QGeoServiceProvider::MappingFeature + + Describes the mapping features supported by the geo service provider. + + \value NoMappingFeatures No mapping features are supported. + \value OnlineMappingFeature Online mapping is supported. + \value OfflineMappingFeature Offline mapping is supported. + \value LocalizedMappingFeature Supports returning localized map data. + \value AnyMappingFeatures Matches a geo service provider that provides any mapping + features. +*/ + +/*! + \enum QGeoServiceProvider::PlacesFeature + + Describes the places features supported by the geo service provider. + + \value NoPlacesFeatures No places features are supported. + \value OnlinePlacesFeature Online places is supported. + \value OfflinePlacesFeature Offline places is supported. + \value SavePlaceFeature Saving places is supported. + \value RemovePlaceFeature Removing or deleting places is supported. + \value SaveCategoryFeature Saving categories is supported. + \value RemoveCategoryFeature Removing or deleting categories is supported. + \value PlaceRecommendationsFeature Searching for recommended places similar to another place + is supported. + \value SearchSuggestionsFeature Search suggestions is supported. + \value LocalizedPlacesFeature Supports returning localized place data. + \value NotificationsFeature Notifications of place and category changes is supported. + \value PlaceMatchingFeature Supports matching places from two different geo service + providers. + \value AnyPlacesFeatures Matches a geo service provider that provides any places + features. +*/ + +/*! + Returns a list of names of the available service providers, for use with + the QGeoServiceProvider constructors. +*/ +QStringList QGeoServiceProvider::availableServiceProviders() +{ + return QGeoServiceProviderPrivate::plugins().keys(); +} + +/*! + Constructs a QGeoServiceProvider whose backend has the name \a + providerName, using the provided \a parameters. + + If multiple plugins have the same \a providerName, the plugin with the + highest reported providerVersion() will be used. + + If \a allowExperimental is true then plugins marked as experimental may be used. By default + experimental plugins are not considered. + + If no plugin matching \a providerName was able to be loaded then error() + and errorString() will provide details about why this is the case. + + \note Before the list of \a parameters is passed on to the to-be-loaded + provider plugin, the list is filtered to avoid the sharing of plugin specific + parameters with unrelated provider plugins. Plugin specific parameter + keys must be prefixed with the provider name (e.g. \c here.app_id). +*/ +QGeoServiceProvider::QGeoServiceProvider(const QString &providerName, + const QVariantMap ¶meters, + bool allowExperimental) + : d_ptr(new QGeoServiceProviderPrivate()) +{ + d_ptr->experimental = allowExperimental; + d_ptr->parameterMap = parameters; + // TODO Qt 6 Remove silent nokia rename + if (providerName == QStringLiteral("nokia")) + d_ptr->providerName = QStringLiteral("here"); + else + d_ptr->providerName = providerName; + d_ptr->loadMeta(); +} + +/*! + Destroys the service provider object. +*/ +QGeoServiceProvider::~QGeoServiceProvider() +{ + delete d_ptr; +} + +/* Template for the routingFeatures(), geocodingFeatures() etc methods. + * Ideally, the enumName would be a template parameter, but strings + * are not a valid const expr. :( */ +template +Flags QGeoServiceProviderPrivate::features(const char *enumName) +{ + const QMetaObject *mo = &QGeoServiceProvider::staticMetaObject; + const QMetaEnum en = mo->enumerator( + mo->indexOfEnumerator(enumName)); + + /* We need the typename keyword here, or Flags::enum_type will be parsed + * as a non-type and lead to an error */ + Flags ret = typename Flags::enum_type(0); + if (this->metaData.contains(QStringLiteral("Features")) + && this->metaData.value(QStringLiteral("Features")).isArray()) { + QJsonArray features = this->metaData.value(QStringLiteral("Features")).toArray(); + foreach (const QJsonValue &v, features) { + int val = en.keyToValue(v.toString().toLatin1().constData()); + if (v.isString() && val != -1) { + ret |= typename Flags::enum_type(val); + } + } + } + + return ret; +} + +/*! + Returns the routing features supported by the geo service provider. +*/ +QGeoServiceProvider::RoutingFeatures QGeoServiceProvider::routingFeatures() const +{ + return d_ptr->features("RoutingFeatures"); +} + +/*! + Returns the geocoding features supported by the geo service provider. +*/ +QGeoServiceProvider::GeocodingFeatures QGeoServiceProvider::geocodingFeatures() const +{ + return d_ptr->features("GeocodingFeatures"); +} + +/*! + Returns the mapping features supported by the geo service provider. +*/ +QGeoServiceProvider::MappingFeatures QGeoServiceProvider::mappingFeatures() const +{ + return d_ptr->features("MappingFeatures"); +} + +/*! + Returns the places features supported by the geo service provider. +*/ +QGeoServiceProvider::PlacesFeatures QGeoServiceProvider::placesFeatures() const +{ + return d_ptr->features("PlacesFeatures"); +} + +/* Sadly, these are necessary to figure out which of the factory->createX + * methods we need to call. Ideally it would be nice to find a way to embed + * these into the manager() template. */ +template +Engine *createEngine(QGeoServiceProviderPrivate *) +{ + return 0; +} +template <> QGeoCodingManagerEngine *createEngine(QGeoServiceProviderPrivate *d_ptr) +{ + return d_ptr->factory->createGeocodingManagerEngine(d_ptr->cleanedParameterMap, &(d_ptr->geocodeError), &(d_ptr->geocodeErrorString)); +} +template <> QGeoRoutingManagerEngine *createEngine(QGeoServiceProviderPrivate *d_ptr) +{ + return d_ptr->factory->createRoutingManagerEngine(d_ptr->cleanedParameterMap, &(d_ptr->routingError), &(d_ptr->routingErrorString)); +} +template <> QGeoMappingManagerEngine *createEngine(QGeoServiceProviderPrivate *d_ptr) +{ + return d_ptr->factory->createMappingManagerEngine(d_ptr->cleanedParameterMap, &(d_ptr->mappingError), &(d_ptr->mappingErrorString)); +} +template <> QPlaceManagerEngine *createEngine(QGeoServiceProviderPrivate *d_ptr) +{ + return d_ptr->factory->createPlaceManagerEngine(d_ptr->cleanedParameterMap, &(d_ptr->placeError), &(d_ptr->placeErrorString)); +} + +/* Template for generating the code for each of the geocodingManager(), + * mappingManager() etc methods */ +template +Manager *QGeoServiceProviderPrivate::manager(QGeoServiceProvider::Error *_error, + QString *_errorString, Manager **_manager) +{ + // make local references just so this method is easier to read + QGeoServiceProvider::Error &error = *_error; + QString &errorString = *_errorString; + Manager *&manager = *_manager; + + if (!this->factory) { + this->filterParameterMap(); + this->loadPlugin(this->parameterMap); + } + + if (!this->factory || error != QGeoServiceProvider::NoError) + return 0; + + if (!manager) { + Engine *engine = createEngine(this); + + if (engine) { + engine->setManagerName( + this->metaData.value(QStringLiteral("Provider")).toString()); + engine->setManagerVersion( + int(this->metaData.value(QStringLiteral("Version")).toDouble())); + manager = new Manager(engine); + } else if (error == QGeoServiceProvider::NoError) { + error = QGeoServiceProvider::NotSupportedError; + errorString = QLatin1String("The service provider does not support the "); + errorString.append(QLatin1String(Manager::staticMetaObject.className())); + errorString.append(QLatin1String(" type.")); + } + + if (error != QGeoServiceProvider::NoError) { + delete manager; + manager = 0; + this->error = error; + this->errorString = errorString; + } + + if (manager && this->localeSet) + manager->setLocale(this->locale); + } + + if (manager) { + this->error = QGeoServiceProvider::NoError; + this->errorString.clear(); + } + + return manager; +} + +/*! + Returns the QGeoCodingManager made available by the service + provider. + + This function will return 0 if the service provider does not provide + any geocoding services. + + This function will attempt to construct a QGeoCodingManager instance + when it is called for the first time. If the attempt is successful the + QGeoCodingManager will be cached, otherwise each call of this function + will attempt to construct a QGeoCodingManager instance until the + construction is successful. + + The QGeoCodingManager is owned by this QGeoServiceProvider and should not + be deleted separately. Users should assume that deleting the + QGeoServiceProvider renders the pointer returned by this method invalid. + + After this function has been called, error() and errorString() will + report any errors which occurred during the construction of the + QGeoCodingManager. +*/ +QGeoCodingManager *QGeoServiceProvider::geocodingManager() const +{ + return d_ptr->manager( + &(d_ptr->geocodeError), &(d_ptr->geocodeErrorString), + &(d_ptr->geocodingManager)); +} + +/*! + Returns the QGeoMappingManager made available by the service provider. + + This function will return 0 if the service provider does not provide + any mapping services. + + This function will attempt to construct a QGeoMappingManager instance + when it is called for the first time. If the attempt is successful the + QGeoMappingManager will be cached, otherwise each call of this function + will attempt to construct a QGeoMappingManager instance until the + construction is successful. + + The QGeoMappingManager is owned by this QGeoServiceProvider and should not + be deleted separately. Users should assume that deleting the + QGeoServiceProvider renders the pointer returned by this method invalid. + + After this function has been called, error() and errorString() will + report any errors which occurred during the construction of the + QGeoMappingManager. + + \internal +*/ +QGeoMappingManager *QGeoServiceProvider::mappingManager() const +{ + return d_ptr->manager( + &(d_ptr->mappingError), &(d_ptr->mappingErrorString), + &(d_ptr->mappingManager)); +} + +/*! + Returns the QGeoRoutingManager made available by the service provider. + + This function will return 0 if the service provider does not provide + any geographic routing services. + + This function will attempt to construct a QGeoRoutingManager instance + when it is called for the first time. If the attempt is successful the + QGeoRoutingManager will be cached, otherwise each call of this function + will attempt to construct a QGeoRoutingManager instance until the + construction is successful. + + The QGeoRoutingManager is owned by this QGeoServiceProvider and should not + be deleted separately. Users should assume that deleting the + QGeoServiceProvider renders the pointer returned by this method invalid. + + After this function has been called, error() and errorString() will + report any errors which occurred during the construction of the + QGeoRoutingManager. +*/ +QGeoRoutingManager *QGeoServiceProvider::routingManager() const +{ + return d_ptr->manager( + &(d_ptr->routingError), &(d_ptr->routingErrorString), + &(d_ptr->routingManager)); +} + +/*! + Returns the QPlaceManager made available by the service provider. + + This function will attempt to construct a QPlaceManager instance + when it is called for the first time. If the attempt is successful the + QPlaceManager will be cached, otherwise each call of this function + will attempt to construct a QPlace instance until the + construction is successful. + + The QGeoPlaceManager is owned by this QGeoServiceProvider and should not + be deleted separately. Users should assume that deleting the + QGeoServiceProvider renders the pointer returned by this method invalid. + + After this function has been called, error() and errorString() will + report any errors which occurred during the construction of the QPlaceManager. +*/ +QPlaceManager *QGeoServiceProvider::placeManager() const +{ + return d_ptr->manager( + &(d_ptr->placeError), &(d_ptr->placeErrorString), + &(d_ptr->placeManager)); +} + +/*! + Returns an error code describing the error which occurred during the + last operation that was performed by this class. +*/ +QGeoServiceProvider::Error QGeoServiceProvider::error() const +{ + return d_ptr->error; +} + +/*! + Returns a string describing the error which occurred during the + last operation that was performed by this class. +*/ +QString QGeoServiceProvider::errorString() const +{ + return d_ptr->errorString; +} + +/*! + Sets whether experimental plugins are considered when locating the + correct plugin library for this service provider to \a allow. + + \b {Important:} this will destroy any existing managers held by this + service provider instance. You should be sure not to attempt to use any + pointers that you have previously retrieved after calling this method. +*/ +void QGeoServiceProvider::setAllowExperimental(bool allow) +{ + d_ptr->experimental = allow; + d_ptr->unload(); + d_ptr->loadMeta(); +} + +/*! + Sets the parameters used to construct individual manager classes for + this service provider to \a parameters. + + Before the list of \a parameters is passed on to the to-be-loaded + service provider, the list is filtered to avoid the sharing of provider specific + parameters with unrelated service providers. Provider specific parameter + keys must be prefixed with the provider name (e.g. \c here.app_id). + + \b {Important:} this will destroy any existing managers held by this + service provider instance. You should be sure not to attempt to use any + pointers that you have previously retrieved after calling this method. +*/ +void QGeoServiceProvider::setParameters(const QVariantMap ¶meters) +{ + d_ptr->parameterMap = parameters; + d_ptr->unload(); + d_ptr->loadMeta(); +} + +/*! + Sets the locale used by this service provider to \a locale. If the relevant features + (see LocalizedMappingFeature etc), this will change the languages, units + and other locale-specific attributes of the provider's data. +*/ +void QGeoServiceProvider::setLocale(const QLocale &locale) +{ + d_ptr->locale = locale; + d_ptr->localeSet = true; + + if (d_ptr->geocodingManager) + d_ptr->geocodingManager->setLocale(locale); + if (d_ptr->routingManager) + d_ptr->routingManager->setLocale(locale); + if (d_ptr->mappingManager) + d_ptr->mappingManager->setLocale(locale); + if (d_ptr->placeManager) + d_ptr->placeManager->setLocale(locale); +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoServiceProviderPrivate::QGeoServiceProviderPrivate() + : factory(0), + experimental(false), + geocodingManager(0), + routingManager(0), + mappingManager(0), + placeManager(0), + geocodeError(QGeoServiceProvider::NoError), + routingError(QGeoServiceProvider::NoError), + mappingError(QGeoServiceProvider::NoError), + placeError(QGeoServiceProvider::NoError), + error(QGeoServiceProvider::NoError), + localeSet(false) +{ + metaData.insert(QStringLiteral("index"), -1); +} + +QGeoServiceProviderPrivate::~QGeoServiceProviderPrivate() +{ + delete geocodingManager; + delete routingManager; + delete mappingManager; + delete placeManager; +} + +void QGeoServiceProviderPrivate::unload() +{ + delete geocodingManager; + geocodingManager = 0; + + delete routingManager; + routingManager = 0; + + delete mappingManager; + mappingManager = 0; + + delete placeManager; + placeManager = 0; + + factory = 0; + error = QGeoServiceProvider::NoError; + errorString = QLatin1String(""); + metaData = QJsonObject(); + metaData.insert(QStringLiteral("index"), -1); +} + +/* Filter out any parameter that doesn't match any plugin */ +void QGeoServiceProviderPrivate::filterParameterMap() +{ + const auto availablePlugins = QGeoServiceProviderPrivate::plugins(); + + cleanedParameterMap = parameterMap; + for (auto it = availablePlugins.keyBegin(), end = availablePlugins.keyEnd(); it != end; ++it) { + if (*it == providerName) // don't remove parameters for current provider + continue; + + QVariantMap::iterator i = cleanedParameterMap.begin(); + while (i != cleanedParameterMap.end()) { + // remove every parameter meant for other plugins + if (i.key().startsWith(QString(*it + QLatin1Char('.')))) + i = cleanedParameterMap.erase(i); + else + ++i; + } + } +} + +void QGeoServiceProviderPrivate::loadMeta() +{ + factory = 0; + metaData = QJsonObject(); + metaData.insert(QStringLiteral("index"), -1); + error = QGeoServiceProvider::NotSupportedError; + errorString = QString(QLatin1String("The geoservices provider %1 is not supported.")).arg(providerName); + + QList candidates = QGeoServiceProviderPrivate::plugins().values(providerName); + + int versionFound = -1; + int idx = -1; + + // figure out which version of the plugin we want + // (always latest unless experimental) + for (int i = 0; i < candidates.size(); ++i) { + QJsonObject meta = candidates[i]; + if (meta.contains(QStringLiteral("Version")) + && meta.value(QStringLiteral("Version")).isDouble() + && meta.contains(QStringLiteral("Experimental")) + && meta.value(QStringLiteral("Experimental")).isBool()) { + int ver = int(meta.value(QStringLiteral("Version")).toDouble()); + if (ver > versionFound && !(!experimental && meta.value(QStringLiteral("Experimental")).toBool())) { + versionFound = ver; + idx = i; + } + } + } + + if (idx != -1) { + error = QGeoServiceProvider::NoError; + errorString = QStringLiteral(""); + metaData = candidates[idx]; + } +} + +void QGeoServiceProviderPrivate::loadPlugin(const QVariantMap ¶meters) +{ + Q_UNUSED(parameters) + + if (int(metaData.value(QStringLiteral("index")).toDouble()) < 0) { + error = QGeoServiceProvider::NotSupportedError; + errorString = QString(QLatin1String("The geoservices provider is not supported.")); + factory = 0; + return; + } + + error = QGeoServiceProvider::NoError; + errorString = QLatin1String(""); + + int idx = int(metaData.value(QStringLiteral("index")).toDouble()); + + // load the actual plugin + factory = qobject_cast(loader()->instance(idx)); +} + +QHash QGeoServiceProviderPrivate::plugins(bool reload) +{ + static QHash plugins; + static bool alreadyDiscovered = false; + + if (reload == true) + alreadyDiscovered = false; + + if (!alreadyDiscovered) { + loadPluginMetadata(plugins); + alreadyDiscovered = true; + } + return plugins; +} + +void QGeoServiceProviderPrivate::loadPluginMetadata(QHash &list) +{ + QFactoryLoader *l = loader(); + QList meta = l->metaData(); + for (int i = 0; i < meta.size(); ++i) { + QJsonObject obj = meta.at(i).value(QStringLiteral("MetaData")).toObject(); + obj.insert(QStringLiteral("index"), i); + list.insertMulti(obj.value(QStringLiteral("Provider")).toString(), obj); + } +} + + +QT_END_NAMESPACE + diff --git a/src/location/maps/qgeoserviceprovider.h b/src/location/maps/qgeoserviceprovider.h new file mode 100644 index 0000000..ea8c2b6 --- /dev/null +++ b/src/location/maps/qgeoserviceprovider.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDER_H +#define QGEOSERVICEPROVIDER_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QLocale; +class QStringList; +class QGeoCodingManager; +class QGeoMappingManager; +class QGeoRoutingManager; +class QPlaceManager; +class QGeoCodingManagerEngine; +class QGeoMappingManagerEngine; +class QGeoRoutingManagerEngine; +class QPlaceManagerEngine; +class QGeoServiceProviderPrivate; + +class Q_LOCATION_EXPORT QGeoServiceProvider : public QObject +{ + Q_OBJECT + Q_ENUMS(Error) +public: + enum Error { + NoError, + NotSupportedError, + UnknownParameterError, + MissingRequiredParameterError, + ConnectionError + }; + + enum RoutingFeature { + NoRoutingFeatures = 0, + OnlineRoutingFeature = (1<<0), + OfflineRoutingFeature = (1<<1), + LocalizedRoutingFeature = (1<<2), + RouteUpdatesFeature = (1<<3), + AlternativeRoutesFeature = (1<<4), + ExcludeAreasRoutingFeature = (1<<5), + AnyRoutingFeatures = ~(0) + }; + + enum GeocodingFeature { + NoGeocodingFeatures = 0, + OnlineGeocodingFeature = (1<<0), + OfflineGeocodingFeature = (1<<1), + ReverseGeocodingFeature = (1<<2), + LocalizedGeocodingFeature = (1<<3), + AnyGeocodingFeatures = ~(0) + }; + + enum MappingFeature { + NoMappingFeatures = 0, + OnlineMappingFeature = (1<<0), + OfflineMappingFeature = (1<<1), + LocalizedMappingFeature = (1<<2), + AnyMappingFeatures = ~(0) + }; + + enum PlacesFeature { + NoPlacesFeatures = 0, + OnlinePlacesFeature = (1<<0), + OfflinePlacesFeature = (1<<1), + SavePlaceFeature = (1<<2), + RemovePlaceFeature = (1<<3), + SaveCategoryFeature = (1<<4), + RemoveCategoryFeature = (1<<5), + PlaceRecommendationsFeature = (1<<6), + SearchSuggestionsFeature = (1<<7), + LocalizedPlacesFeature = (1<<8), + NotificationsFeature = (1<<9), + PlaceMatchingFeature = (1<<10), + AnyPlacesFeatures = ~(0) + }; + + Q_DECLARE_FLAGS(RoutingFeatures, RoutingFeature) + Q_FLAGS(RoutingFeatures) + + Q_DECLARE_FLAGS(GeocodingFeatures, GeocodingFeature) + Q_FLAGS(GeocodingFeatures) + + Q_DECLARE_FLAGS(MappingFeatures, MappingFeature) + Q_FLAGS(MappingFeatures) + + Q_DECLARE_FLAGS(PlacesFeatures, PlacesFeature) + Q_FLAGS(PlacesFeatures) + + static QStringList availableServiceProviders(); + QGeoServiceProvider(const QString &providerName, + const QVariantMap ¶meters = QVariantMap(), + bool allowExperimental = false); + + ~QGeoServiceProvider(); + + RoutingFeatures routingFeatures() const; + GeocodingFeatures geocodingFeatures() const; + MappingFeatures mappingFeatures() const; + PlacesFeatures placesFeatures() const; + + QGeoCodingManager *geocodingManager() const; + QGeoMappingManager *mappingManager() const; + QGeoRoutingManager *routingManager() const; + QPlaceManager *placeManager() const; + + Error error() const; + QString errorString() const; + + void setParameters(const QVariantMap ¶meters); + void setLocale(const QLocale &locale); + void setAllowExperimental(bool allow); + +private: + QGeoServiceProviderPrivate *d_ptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoServiceProvider::RoutingFeatures) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoServiceProvider::GeocodingFeatures) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoServiceProvider::MappingFeatures) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoServiceProvider::PlacesFeatures) + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoserviceprovider_p.h b/src/location/maps/qgeoserviceprovider_p.h new file mode 100644 index 0000000..6bfb9b2 --- /dev/null +++ b/src/location/maps/qgeoserviceprovider_p.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDER_P_H +#define QGEOSERVICEPROVIDER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeoserviceprovider.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCodingManager; +class QGeoRoutingManager; +class QGeoMappingManager; + +class QGeoServiceProviderFactory; + +class QGeoServiceProviderPrivate +{ +public: + QGeoServiceProviderPrivate(); + ~QGeoServiceProviderPrivate(); + + void loadMeta(); + void loadPlugin(const QVariantMap ¶meters); + void unload(); + void filterParameterMap(); + + /* helper templates for generating the feature and manager accessors */ + template + Manager *manager(QGeoServiceProvider::Error *error, + QString *errorString, Manager **manager); + template + Flags features(const char *enumName); + + QGeoServiceProviderFactory *factory; + QJsonObject metaData; + + QVariantMap parameterMap; + QVariantMap cleanedParameterMap; + + bool experimental; + + QGeoCodingManager *geocodingManager; + QGeoRoutingManager *routingManager; + QGeoMappingManager *mappingManager; + QPlaceManager *placeManager; + + QGeoServiceProvider::Error geocodeError; + QGeoServiceProvider::Error routingError; + QGeoServiceProvider::Error mappingError; + QGeoServiceProvider::Error placeError; + + QString geocodeErrorString; + QString routingErrorString; + QString mappingErrorString; + QString placeErrorString; + + QGeoServiceProvider::Error error; + QString errorString; + + QString providerName; + + QLocale locale; + bool localeSet; + + static QHash plugins(bool reload = false); + static void loadPluginMetadata(QHash &list); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeoserviceproviderfactory.cpp b/src/location/maps/qgeoserviceproviderfactory.cpp new file mode 100644 index 0000000..7efc8a9 --- /dev/null +++ b/src/location/maps/qgeoserviceproviderfactory.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoserviceproviderfactory.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoServiceProviderFactory + \inmodule QtLocation + \ingroup QtLocation-impl + \since 5.6 + + \brief The QGeoServiceProviderFactory class is a factory class used as the + plugin interface for services related to geographical information. + + Implementers must provide a unique combination of providerName() and + providerVersion() per plugin. + + The other functions should be overridden if the plugin supports the + associated set of functionality. +*/ + +/*! +\fn QGeoServiceProviderFactory::~QGeoServiceProviderFactory() + +Destroys this QGeoServiceProviderFactory instance. +*/ + +/*! + Returns a new QGeoCodingManagerEngine instance, initialized with \a + parameters, which implements the location geocoding functionality. + + If \a error is not 0 it should be set to QGeoServiceProvider::NoError on + success or an appropriate QGeoServiceProvider::Error on failure. + + If \a errorString is not 0 it should be set to a string describing any + error which occurred. + + The default implementation returns 0, which causes a + QGeoServiceProvider::NotSupportedError in QGeoServiceProvider. +*/ +QGeoCodingManagerEngine *QGeoServiceProviderFactory::createGeocodingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const +{ + Q_UNUSED(parameters) + Q_UNUSED(error) + Q_UNUSED(errorString) + + return 0; +} + +/*! + Returns a new QGeoMappingManagerEngine instance, initialized with \a + parameters, which implements mapping functionality. + + If \a error is not 0 it should be set to QGeoServiceProvider::NoError on + success or an appropriate QGeoServiceProvider::Error on failure. + + If \a errorString is not 0 it should be set to a string describing any + error which occurred. + + The default implementation returns 0, which causes a + QGeoServiceProvider::NotSupportedError in QGeoServiceProvider. + + \internal +*/ +QGeoMappingManagerEngine *QGeoServiceProviderFactory::createMappingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const +{ + Q_UNUSED(parameters) + Q_UNUSED(error) + Q_UNUSED(errorString) + + return 0; +} + +/*! + Returns a new QGeoRoutingManagerEngine instance, initialized with \a + parameters, which implements routing functionality. + + If \a error is not 0 it should be set to QGeoServiceProvider::NoError on + success or an appropriate QGeoServiceProvider::Error on failure. + + If \a errorString is not 0 it should be set to a string describing any + error which occurred. + + The default implementation returns 0, which causes a + QGeoServiceProvider::NotSupportedError in QGeoServiceProvider. +*/ +QGeoRoutingManagerEngine *QGeoServiceProviderFactory::createRoutingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const + +{ + Q_UNUSED(parameters) + Q_UNUSED(error) + Q_UNUSED(errorString) + + return 0; +} + +/*! + Returns a new QPlaceManagerEngine instance, initialized with \a + parameters, which implements the place searching functionality. + + If \a error is not 0 it should be set to QGeoServiceProvider::NoError on + success or an appropriate QGeoServiceProvider::Error on failure. + + If \a errorString is not 0 it should be set to a string describing any + error which occurred. + + The default implementation returns 0, which causes a + QGeoServiceProvider::NotSupportedError in QGeoServiceProvider. +*/ +QPlaceManagerEngine *QGeoServiceProviderFactory::createPlaceManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const + +{ + Q_UNUSED(parameters) + Q_UNUSED(error) + Q_UNUSED(errorString) + + return 0; +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeoserviceproviderfactory.h b/src/location/maps/qgeoserviceproviderfactory.h new file mode 100644 index 0000000..cc60cf7 --- /dev/null +++ b/src/location/maps/qgeoserviceproviderfactory.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDERFACTORY_H +#define QGEOSERVICEPROVIDERFACTORY_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_EXPORT QGeoServiceProviderFactory +{ +public: + virtual ~QGeoServiceProviderFactory() {} + + virtual QGeoCodingManagerEngine *createGeocodingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + virtual QGeoMappingManagerEngine *createMappingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + virtual QGeoRoutingManagerEngine *createRoutingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + virtual QPlaceManagerEngine *createPlaceManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; +}; + +Q_DECLARE_INTERFACE(QGeoServiceProviderFactory, + "org.qt-project.qt.geoservice.serviceproviderfactory/5.0") + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeotiledmap.cpp b/src/location/maps/qgeotiledmap.cpp new file mode 100644 index 0000000..a6cbb29 --- /dev/null +++ b/src/location/maps/qgeotiledmap.cpp @@ -0,0 +1,414 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeotiledmap_p.h" +#include "qgeotiledmap_p_p.h" + +#include "qgeotiledmappingmanagerengine_p.h" +#include "qabstractgeotilecache_p.h" +#include "qgeotilespec_p.h" + +#include "qgeocameratiles_p.h" +#include "qgeotilerequestmanager_p.h" +#include "qgeotiledmapscene_p.h" +#include "qgeocameracapabilities_p.h" +#include + +QT_BEGIN_NAMESPACE +#define PREFETCH_FRUSTUM_SCALE 2.0 + +QGeoTiledMap::QGeoTiledMap(QGeoTiledMappingManagerEngine *engine, QObject *parent) + : QGeoMap(*new QGeoTiledMapPrivate(engine), parent) +{ + Q_D(QGeoTiledMap); + + d->m_tileRequests = new QGeoTileRequestManager(this, engine); + + QObject::connect(engine,&QGeoTiledMappingManagerEngine::tileVersionChanged, + this,&QGeoTiledMap::handleTileVersionChanged); +} + +QGeoTiledMap::~QGeoTiledMap() +{ + Q_D(QGeoTiledMap); + delete d->m_tileRequests; + d->m_tileRequests = 0; + + if (!d->m_engine.isNull()) { + QGeoTiledMappingManagerEngine *engine = qobject_cast(d->m_engine); + Q_ASSERT(engine); + engine->releaseMap(this); + } +} + +QGeoTileRequestManager *QGeoTiledMap::requestManager() +{ + Q_D(QGeoTiledMap); + return d->m_tileRequests; +} + +void QGeoTiledMap::updateTile(const QGeoTileSpec &spec) +{ + Q_D(QGeoTiledMap); + d->updateTile(spec); +} + +void QGeoTiledMap::setPrefetchStyle(QGeoTiledMap::PrefetchStyle style) +{ + Q_D(QGeoTiledMap); + d->m_prefetchStyle = style; +} + +QAbstractGeoTileCache *QGeoTiledMap::tileCache() +{ + Q_D(QGeoTiledMap); + return d->m_cache; +} + +QSGNode *QGeoTiledMap::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window) +{ + Q_D(QGeoTiledMap); + return d->updateSceneGraph(oldNode, window); +} + +void QGeoTiledMap::prefetchData() +{ + Q_D(QGeoTiledMap); + d->prefetchTiles(); +} + +void QGeoTiledMap::clearData() +{ + Q_D(QGeoTiledMap); + d->m_cache->clearAll(); + d->m_mapScene->clearTexturedTiles(); +} + +void QGeoTiledMap::handleTileVersionChanged() +{ + Q_D(QGeoTiledMap); + if (!d->m_engine.isNull()) { + QGeoTiledMappingManagerEngine* engine = qobject_cast(d->m_engine); + Q_ASSERT(engine); + d->changeTileVersion(engine->tileVersion()); + } +} + +void QGeoTiledMap::evaluateCopyrights(const QSet &visibleTiles) +{ + Q_UNUSED(visibleTiles); +} + +QGeoCoordinate QGeoTiledMap::itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport) const +{ + Q_D(const QGeoTiledMap); + if (clipToViewport) { + int w = width(); + int h = height(); + + if ((pos.x() < 0) || (w < pos.x()) || (pos.y() < 0) || (h < pos.y())) + return QGeoCoordinate(); + } + + return d->itemPositionToCoordinate(pos); +} + +QDoubleVector2D QGeoTiledMap::coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport) const +{ + Q_D(const QGeoTiledMap); + QDoubleVector2D pos = d->coordinateToItemPosition(coordinate); + + if (clipToViewport) { + int w = width(); + int h = height(); + double x = pos.x(); + double y = pos.y(); + if ((x < 0.0) || (x > w) || (y < 0) || (y > h) || qIsNaN(x) || qIsNaN(y)) + return QDoubleVector2D(qQNaN(), qQNaN()); + } + + return pos; +} + +// This method returns the minimum zoom level that this specific qgeomap type allows +// at a given canvas size (width,height) and for a given tile size (usually 256). +double QGeoTiledMap::minimumZoomForMapSize(int width, int height) const +{ + Q_D(const QGeoTiledMap); + double maxSize = qMax(width,height); + double numTiles = maxSize / d->m_visibleTiles->tileSize(); + return std::log(numTiles) / std::log(2.0); +} + +// This method recalculates the "no-trespassing" limits for the map center. +// This has to be done when: +// 1) the map is resized, because the meters per pixel remain the same, but +// the amount of pixels between the center and the borders changes +// 2) when the zoom level changes, because the amount of pixels between the center +// and the borders stays the same, but the meters per pixel change +double QGeoTiledMap::maximumLatitudeForZoom(double zoomLevel) const +{ + Q_D(const QGeoTiledMap); + double mapEdgeSize = std::pow(2.0,zoomLevel); + mapEdgeSize *= d->m_visibleTiles->tileSize(); + + // At init time weird things happen + int clampedWindowHeight = (height() > mapEdgeSize) ? mapEdgeSize : height(); + + // Use the window height divided by 2 as the topmost allowed center, with respect to the map size in pixels + double mercatorTopmost = (clampedWindowHeight * 0.5) / mapEdgeSize ; + QGeoCoordinate topMost = QGeoProjection::mercatorToCoord(QDoubleVector2D(0.0,mercatorTopmost)); + + return topMost.latitude(); +} + +QDoubleVector2D QGeoTiledMap::referenceCoordinateToItemPosition(const QGeoCoordinate &coordinate) const +{ + Q_D(const QGeoTiledMap); + QDoubleVector2D point = QGeoProjection::coordToMercator(coordinate); + return point * std::pow(2.0, d->m_cameraData.zoomLevel()) * d->m_visibleTiles->tileSize(); +} + +QGeoCoordinate QGeoTiledMap::referenceItemPositionToCoordinate(const QDoubleVector2D &pos) const +{ + Q_D(const QGeoTiledMap); + QDoubleVector2D point = pos / (std::pow(2.0, d->m_cameraData.zoomLevel()) * d->m_visibleTiles->tileSize()); + return QGeoProjection::mercatorToCoord(point); +} + +QGeoTiledMapPrivate::QGeoTiledMapPrivate(QGeoTiledMappingManagerEngine *engine) + : QGeoMapPrivate(engine), + m_cache(engine->tileCache()), + m_visibleTiles(new QGeoCameraTiles()), + m_prefetchTiles(new QGeoCameraTiles()), + m_mapScene(new QGeoTiledMapScene()), + m_tileRequests(0), + m_maxZoomLevel(static_cast(std::ceil(engine->cameraCapabilities().maximumZoomLevel()))), + m_minZoomLevel(static_cast(std::ceil(engine->cameraCapabilities().minimumZoomLevel()))), + m_prefetchStyle(QGeoTiledMap::PrefetchTwoNeighbourLayers) +{ + int tileSize = engine->tileSize().width(); + QString pluginString(engine->managerName() + QLatin1Char('_') + QString::number(engine->managerVersion())); + m_visibleTiles->setTileSize(tileSize); + m_prefetchTiles->setTileSize(tileSize); + m_visibleTiles->setPluginString(pluginString); + m_prefetchTiles->setPluginString(pluginString); + m_mapScene->setTileSize(tileSize); +} + +QGeoTiledMapPrivate::~QGeoTiledMapPrivate() +{ + // controller_ is a child of map_, don't need to delete it here + + delete m_mapScene; + delete m_visibleTiles; + delete m_prefetchTiles; + + // TODO map items are not deallocated! + // However: how to ensure this is done in rendering thread? +} + +void QGeoTiledMapPrivate::prefetchTiles() +{ + if (m_tileRequests) { + + QSet tiles; + QGeoCameraData camera = m_visibleTiles->cameraData(); + int currentIntZoom = static_cast(std::floor(camera.zoomLevel())); + + m_prefetchTiles->setCameraData(camera); + m_prefetchTiles->setViewExpansion(PREFETCH_FRUSTUM_SCALE); + tiles = m_prefetchTiles->createTiles(); + + switch (m_prefetchStyle) { + + case QGeoTiledMap::PrefetchNeighbourLayer: { + double zoomFraction = camera.zoomLevel() - currentIntZoom; + int nearestNeighbourLayer = zoomFraction > 0.5 ? currentIntZoom + 1 : currentIntZoom - 1; + if (nearestNeighbourLayer <= m_maxZoomLevel && nearestNeighbourLayer >= m_minZoomLevel) { + camera.setZoomLevel(nearestNeighbourLayer); + // Approx heuristic, keeping total # prefetched tiles roughly independent of the + // fractional zoom level. + double neighbourScale = (1.0 + zoomFraction)/2.0; + m_prefetchTiles->setCameraData(camera); + m_prefetchTiles->setViewExpansion(PREFETCH_FRUSTUM_SCALE * neighbourScale); + tiles += m_prefetchTiles->createTiles(); + } + } + break; + + case QGeoTiledMap::PrefetchTwoNeighbourLayers: { + // This is a simpler strategy, we just prefetch from layer above and below + // for the layer below we only use half the size as this fills the screen + if (currentIntZoom > m_minZoomLevel) { + camera.setZoomLevel(currentIntZoom - 1); + m_prefetchTiles->setCameraData(camera); + m_prefetchTiles->setViewExpansion(0.5); + tiles += m_prefetchTiles->createTiles(); + } + + if (currentIntZoom < m_maxZoomLevel) { + camera.setZoomLevel(currentIntZoom + 1); + m_prefetchTiles->setCameraData(camera); + m_prefetchTiles->setViewExpansion(1.0); + tiles += m_prefetchTiles->createTiles(); + } + + } + } + + m_tileRequests->requestTiles(tiles - m_mapScene->texturedTiles()); + } +} + +void QGeoTiledMapPrivate::changeCameraData(const QGeoCameraData &cameraData) +{ + Q_Q(QGeoTiledMap); + + // For zoomlevel, "snap" 0.01 either side of a whole number. + // This is so that when we turn off bilinear scaling, we're + // snapped to the exact pixel size of the tiles + QGeoCameraData cam = cameraData; + int izl = static_cast(std::floor(cam.zoomLevel())); + float delta = cam.zoomLevel() - izl; + + if (delta > 0.5) { + izl++; + delta -= 1.0; + } + if (qAbs(delta) < 0.01) { + cam.setZoomLevel(izl); + } + + m_visibleTiles->setCameraData(cam); + m_mapScene->setCameraData(cam); + + updateScene(); + q->sgNodeChanged(); +} + +void QGeoTiledMapPrivate::updateScene() +{ + Q_Q(QGeoTiledMap); + // detect if new tiles introduced + const QSet& tiles = m_visibleTiles->createTiles(); + bool newTilesIntroduced = !m_mapScene->visibleTiles().contains(tiles); + m_mapScene->setVisibleTiles(tiles); + + if (newTilesIntroduced) + q->evaluateCopyrights(tiles); + + // don't request tiles that are already built and textured + QList > cachedTiles = + m_tileRequests->requestTiles(m_visibleTiles->createTiles() - m_mapScene->texturedTiles()); + + foreach (const QSharedPointer &tex, cachedTiles) { + m_mapScene->addTile(tex->spec, tex); + } + + if (!cachedTiles.isEmpty()) + emit q->sgNodeChanged(); +} + +void QGeoTiledMapPrivate::changeActiveMapType(const QGeoMapType mapType) +{ + m_visibleTiles->setMapType(mapType); + m_prefetchTiles->setMapType(mapType); + updateScene(); +} + +void QGeoTiledMapPrivate::changeTileVersion(int version) +{ + m_visibleTiles->setMapVersion(version); + m_prefetchTiles->setMapVersion(version); + updateScene(); +} + +void QGeoTiledMapPrivate::changeMapSize(const QSize& size) +{ + Q_Q(QGeoTiledMap); + + m_visibleTiles->setScreenSize(size); + m_prefetchTiles->setScreenSize(size); + m_mapScene->setScreenSize(size); + + + if (!size.isEmpty() && m_cache) { + // absolute minimum size: one tile each side of display, 32-bit colour + int texCacheSize = (size.width() + m_visibleTiles->tileSize() * 2) * + (size.height() + m_visibleTiles->tileSize() * 2) * 4; + + // multiply by 3 so the 'recent' list in the cache is big enough for + // an entire display of tiles + texCacheSize *= 3; + // TODO: move this reasoning into the tilecache + + int newSize = qMax(m_cache->minTextureUsage(), texCacheSize); + m_cache->setMinTextureUsage(newSize); + } + + q->evaluateCopyrights(m_visibleTiles->createTiles()); + updateScene(); +} + +void QGeoTiledMapPrivate::updateTile(const QGeoTileSpec &spec) +{ + Q_Q(QGeoTiledMap); + // Only promote the texture up to GPU if it is visible + if (m_visibleTiles->createTiles().contains(spec)){ + QSharedPointer tex = m_tileRequests->tileTexture(spec); + if (!tex.isNull()) { + m_mapScene->addTile(spec, tex); + emit q->sgNodeChanged(); + } + } +} + +QSGNode *QGeoTiledMapPrivate::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window) +{ + return m_mapScene->updateSceneGraph(oldNode, window); +} + +QGeoCoordinate QGeoTiledMapPrivate::itemPositionToCoordinate(const QDoubleVector2D &pos) const +{ + return QGeoProjection::mercatorToCoord(m_mapScene->itemPositionToMercator(pos)); +} + +QDoubleVector2D QGeoTiledMapPrivate::coordinateToItemPosition(const QGeoCoordinate &coordinate) const +{ + return m_mapScene->mercatorToItemPosition(QGeoProjection::coordToMercator(coordinate)); +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeotiledmap_p.h b/src/location/maps/qgeotiledmap_p.h new file mode 100644 index 0000000..813e342 --- /dev/null +++ b/src/location/maps/qgeotiledmap_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOTILEDMAP_P_H +#define QGEOTILEDMAP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include "qgeomap_p.h" +#include "qgeocameradata_p.h" +#include "qgeomaptype_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoTileSpec; +class QGeoTileTexture; +class QAbstractGeoTileCache; +class QGeoTiledMapPrivate; +class QGeoTiledMappingManagerEngine; +class QGeoTileRequestManager; + +class QQuickWindow; +class QSGNode; + +class QPointF; + +class Q_LOCATION_EXPORT QGeoTiledMap : public QGeoMap +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoTiledMap) +public: + enum PrefetchStyle { PrefetchNeighbourLayer, PrefetchTwoNeighbourLayers }; + QGeoTiledMap(QGeoTiledMappingManagerEngine *engine, QObject *parent); + virtual ~QGeoTiledMap(); + + QAbstractGeoTileCache *tileCache(); + QGeoTileRequestManager *requestManager(); + void updateTile(const QGeoTileSpec &spec); + void setPrefetchStyle(PrefetchStyle style); + + QGeoCoordinate itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport = true) const Q_DECL_OVERRIDE; + QDoubleVector2D coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport = true) const Q_DECL_OVERRIDE; + + double minimumZoomForMapSize(int width, int height) const Q_DECL_OVERRIDE; + double maximumLatitudeForZoom(double zoomLevel) const Q_DECL_OVERRIDE; + + QDoubleVector2D referenceCoordinateToItemPosition(const QGeoCoordinate &coordinate) const Q_DECL_OVERRIDE; + QGeoCoordinate referenceItemPositionToCoordinate(const QDoubleVector2D &pos) const Q_DECL_OVERRIDE; + + void prefetchData() Q_DECL_OVERRIDE; + void clearData() Q_DECL_OVERRIDE; + +protected: + QSGNode *updateSceneGraph(QSGNode *, QQuickWindow *window) Q_DECL_OVERRIDE; + virtual void evaluateCopyrights(const QSet &visibleTiles); + +private Q_SLOTS: + void handleTileVersionChanged(); + +private: + Q_DISABLE_COPY(QGeoTiledMap) + +}; + +QT_END_NAMESPACE + +#endif // QGEOMAP_P_H diff --git a/src/location/maps/qgeotiledmap_p_p.h b/src/location/maps/qgeotiledmap_p_p.h new file mode 100644 index 0000000..c1b190b --- /dev/null +++ b/src/location/maps/qgeotiledmap_p_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOTILEDMAP_P_P_H +#define QGEOTILEDMAP_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeomap_p_p.h" +#include "qgeocameradata_p.h" +#include "qgeomaptype_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCameraTiles; +class QGeoTiledMapScene; +class QAbstractGeoTileCache; +class QGeoTiledMappingManagerEngine; +class QGeoTiledMap; +class QGeoTileRequestManager; +class QGeoTileSpec; +class QSGNode; +class QQuickWindow; + +class QGeoTiledMapPrivate : public QGeoMapPrivate +{ + Q_DECLARE_PUBLIC(QGeoTiledMap) +public: + QGeoTiledMapPrivate(QGeoTiledMappingManagerEngine *engine); + ~QGeoTiledMapPrivate(); + + QSGNode *updateSceneGraph(QSGNode *node, QQuickWindow *window); + + QGeoCoordinate itemPositionToCoordinate(const QDoubleVector2D &pos) const; + QDoubleVector2D coordinateToItemPosition(const QGeoCoordinate &coordinate) const; + + void updateTile(const QGeoTileSpec &spec); + void prefetchTiles(); + +protected: + void changeMapSize(const QSize& size) Q_DECL_OVERRIDE; + void changeCameraData(const QGeoCameraData &cameraData) Q_DECL_OVERRIDE; + void changeActiveMapType(const QGeoMapType mapType) Q_DECL_OVERRIDE; + void changeTileVersion(int version); + +private: + void updateScene(); + +private: + QAbstractGeoTileCache *m_cache; + QGeoCameraTiles *m_visibleTiles; + QGeoCameraTiles *m_prefetchTiles; + QGeoTiledMapScene *m_mapScene; + QGeoTileRequestManager *m_tileRequests; + int m_maxZoomLevel; + int m_minZoomLevel; + QGeoTiledMap::PrefetchStyle m_prefetchStyle; + bool m_geomoteryUpdated; + Q_DISABLE_COPY(QGeoTiledMapPrivate) +}; + +QT_END_NAMESPACE + +#endif // QGEOTILEDMAP_P_P_H diff --git a/src/location/maps/qgeotiledmappingmanagerengine.cpp b/src/location/maps/qgeotiledmappingmanagerengine.cpp new file mode 100644 index 0000000..dc5f188 --- /dev/null +++ b/src/location/maps/qgeotiledmappingmanagerengine.cpp @@ -0,0 +1,328 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotiledmappingmanagerengine_p.h" +#include "qgeotiledmappingmanagerengine_p_p.h" +#include "qgeotilefetcher_p.h" + + +#include "qgeotiledmap_p.h" +#include "qgeotilerequestmanager_p.h" +#include "qgeofiletilecache_p.h" +#include "qgeotilespec_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoTiledMappingManagerEngine::QGeoTiledMappingManagerEngine(QObject *parent) + : QGeoMappingManagerEngine(parent), + d_ptr(new QGeoTiledMappingManagerEnginePrivate) +{ +} + +/*! + Destroys this mapping manager. +*/ +QGeoTiledMappingManagerEngine::~QGeoTiledMappingManagerEngine() +{ + delete d_ptr; +} + +/*! + Sets the tile fetcher. Takes ownership of the QObject. +*/ +void QGeoTiledMappingManagerEngine::setTileFetcher(QGeoTileFetcher *fetcher) +{ + Q_D(QGeoTiledMappingManagerEngine); + + if (d->fetcher_) + d->fetcher_->deleteLater(); + fetcher->setParent(this); + d->fetcher_ = fetcher; + + qRegisterMetaType(); + + connect(d->fetcher_, + SIGNAL(tileFinished(QGeoTileSpec,QByteArray,QString)), + this, + SLOT(engineTileFinished(QGeoTileSpec,QByteArray,QString)), + Qt::QueuedConnection); + connect(d->fetcher_, + SIGNAL(tileError(QGeoTileSpec,QString)), + this, + SLOT(engineTileError(QGeoTileSpec,QString)), + Qt::QueuedConnection); + + engineInitialized(); +} + +QGeoTileFetcher *QGeoTiledMappingManagerEngine::tileFetcher() +{ + Q_D(QGeoTiledMappingManagerEngine); + return d->fetcher_; +} + +QGeoMap *QGeoTiledMappingManagerEngine::createMap() +{ + return NULL; +} + +void QGeoTiledMappingManagerEngine::releaseMap(QGeoTiledMap *map) +{ + d_ptr->mapHash_.remove(map); + + QHash > newTileHash = d_ptr->tileHash_; + typedef QHash >::const_iterator h_iter; + h_iter hi = d_ptr->tileHash_.constBegin(); + h_iter hend = d_ptr->tileHash_.constEnd(); + for (; hi != hend; ++hi) { + QSet maps = hi.value(); + if (maps.contains(map)) { + maps.remove(map); + if (maps.isEmpty()) + newTileHash.remove(hi.key()); + else + newTileHash.insert(hi.key(), maps); + } + } + d_ptr->tileHash_ = newTileHash; +} + +void QGeoTiledMappingManagerEngine::updateTileRequests(QGeoTiledMap *map, + const QSet &tilesAdded, + const QSet &tilesRemoved) +{ + Q_D(QGeoTiledMappingManagerEngine); + + typedef QSet::const_iterator tile_iter; + + // add and remove tiles from tileset for this map + + QSet oldTiles = d->mapHash_.value(map); + + tile_iter rem = tilesRemoved.constBegin(); + tile_iter remEnd = tilesRemoved.constEnd(); + for (; rem != remEnd; ++rem) { + oldTiles.remove(*rem); + } + + tile_iter add = tilesAdded.constBegin(); + tile_iter addEnd = tilesAdded.constEnd(); + for (; add != addEnd; ++add) { + oldTiles.insert(*add); + } + + d->mapHash_.insert(map, oldTiles); + + // add and remove map from mapset for the tiles + + QSet reqTiles; + QSet cancelTiles; + + rem = tilesRemoved.constBegin(); + for (; rem != remEnd; ++rem) { + QSet mapSet = d->tileHash_.value(*rem); + mapSet.remove(map); + if (mapSet.isEmpty()) { + cancelTiles.insert(*rem); + d->tileHash_.remove(*rem); + } else { + d->tileHash_.insert(*rem, mapSet); + } + } + + add = tilesAdded.constBegin(); + for (; add != addEnd; ++add) { + QSet mapSet = d->tileHash_.value(*add); + if (mapSet.isEmpty()) { + reqTiles.insert(*add); + } + mapSet.insert(map); + d->tileHash_.insert(*add, mapSet); + } + + cancelTiles -= reqTiles; + + QMetaObject::invokeMethod(d->fetcher_, "updateTileRequests", + Qt::QueuedConnection, + Q_ARG(QSet, reqTiles), + Q_ARG(QSet, cancelTiles)); +} + +void QGeoTiledMappingManagerEngine::engineTileFinished(const QGeoTileSpec &spec, const QByteArray &bytes, const QString &format) +{ + Q_D(QGeoTiledMappingManagerEngine); + + QSet maps = d->tileHash_.value(spec); + + typedef QSet::const_iterator map_iter; + + map_iter map = maps.constBegin(); + map_iter mapEnd = maps.constEnd(); + for (; map != mapEnd; ++map) { + QSet tileSet = d->mapHash_.value(*map); + tileSet.remove(spec); + if (tileSet.isEmpty()) + d->mapHash_.remove(*map); + else + d->mapHash_.insert(*map, tileSet); + } + + d->tileHash_.remove(spec); + tileCache()->insert(spec, bytes, format, d->cacheHint_); + + map = maps.constBegin(); + mapEnd = maps.constEnd(); + for (; map != mapEnd; ++map) { + (*map)->requestManager()->tileFetched(spec); + } +} + +void QGeoTiledMappingManagerEngine::engineTileError(const QGeoTileSpec &spec, const QString &errorString) +{ + Q_D(QGeoTiledMappingManagerEngine); + + QSet maps = d->tileHash_.value(spec); + typedef QSet::const_iterator map_iter; + map_iter map = maps.constBegin(); + map_iter mapEnd = maps.constEnd(); + for (; map != mapEnd; ++map) { + QSet tileSet = d->mapHash_.value(*map); + + tileSet.remove(spec); + if (tileSet.isEmpty()) + d->mapHash_.remove(*map); + else + d->mapHash_.insert(*map, tileSet); + } + d->tileHash_.remove(spec); + + for (map = maps.constBegin(); map != mapEnd; ++map) { + (*map)->requestManager()->tileError(spec, errorString); + } + + emit tileError(spec, errorString); +} + +void QGeoTiledMappingManagerEngine::setTileSize(const QSize &tileSize) +{ + Q_D(QGeoTiledMappingManagerEngine); + d->tileSize_ = tileSize; +} + +void QGeoTiledMappingManagerEngine::setTileVersion(int version) +{ + Q_D(QGeoTiledMappingManagerEngine); + if (d->m_tileVersion != version) { + d->m_tileVersion = version; + emit tileVersionChanged(); + } +} + +QSize QGeoTiledMappingManagerEngine::tileSize() const +{ + Q_D(const QGeoTiledMappingManagerEngine); + return d->tileSize_; +} + +int QGeoTiledMappingManagerEngine::tileVersion() const +{ + Q_D(const QGeoTiledMappingManagerEngine); + return d->m_tileVersion; +} + +QGeoTiledMappingManagerEngine::CacheAreas QGeoTiledMappingManagerEngine::cacheHint() const +{ + Q_D(const QGeoTiledMappingManagerEngine); + return d->cacheHint_; +} + +void QGeoTiledMappingManagerEngine::setCacheHint(QGeoTiledMappingManagerEngine::CacheAreas cacheHint) +{ + Q_D(QGeoTiledMappingManagerEngine); + d->cacheHint_ = cacheHint; +} + +/*! + Sets the tile cache. Takes ownership of the QObject. +*/ +void QGeoTiledMappingManagerEngine::setTileCache(QAbstractGeoTileCache *cache) +{ + Q_D(QGeoTiledMappingManagerEngine); + Q_ASSERT_X(!d->tileCache_, Q_FUNC_INFO, "This should be called only once"); + cache->setParent(this); + d->tileCache_ = cache; +} + +QAbstractGeoTileCache *QGeoTiledMappingManagerEngine::tileCache() +{ + Q_D(QGeoTiledMappingManagerEngine); + if (!d->tileCache_) { + QString cacheDirectory; + if (!managerName().isEmpty()) + cacheDirectory = QAbstractGeoTileCache::baseCacheDirectory() + managerName(); + d->tileCache_ = new QGeoFileTileCache(cacheDirectory); + } + return d->tileCache_; +} + +QSharedPointer QGeoTiledMappingManagerEngine::getTileTexture(const QGeoTileSpec &spec) +{ + return d_ptr->tileCache_->get(spec); +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoTiledMappingManagerEnginePrivate::QGeoTiledMappingManagerEnginePrivate() +: m_tileVersion(-1), + cacheHint_(QGeoTiledMappingManagerEngine::AllCaches), + tileCache_(0), + fetcher_(0) +{ +} + +QGeoTiledMappingManagerEnginePrivate::~QGeoTiledMappingManagerEnginePrivate() +{ +} + +#include "moc_qgeotiledmappingmanagerengine_p.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeotiledmappingmanagerengine_p.h b/src/location/maps/qgeotiledmappingmanagerengine_p.h new file mode 100644 index 0000000..86c5b63 --- /dev/null +++ b/src/location/maps/qgeotiledmappingmanagerengine_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEDMAPPINGMANAGERENGINE_H +#define QGEOTILEDMAPPINGMANAGERENGINE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include "qgeomaptype_p.h" +#include "qgeomappingmanagerengine_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoTiledMappingManagerEnginePrivate; +class QGeoMapRequestOptions; +class QGeoTileFetcher; +class QGeoTileTexture; + +class QGeoTileSpec; +class QGeoTiledMap; +class QAbstractGeoTileCache; + +class Q_LOCATION_EXPORT QGeoTiledMappingManagerEngine : public QGeoMappingManagerEngine +{ + Q_OBJECT + +public: + enum CacheArea { + DiskCache = 0x01, + MemoryCache = 0x02, + AllCaches = 0xFF + }; + Q_DECLARE_FLAGS(CacheAreas, CacheArea) + + explicit QGeoTiledMappingManagerEngine(QObject *parent = 0); + virtual ~QGeoTiledMappingManagerEngine(); + + QGeoTileFetcher *tileFetcher(); + + QGeoMap *createMap() Q_DECL_OVERRIDE; + void releaseMap(QGeoTiledMap *map); + + QSize tileSize() const; + int tileVersion() const; + + void updateTileRequests(QGeoTiledMap *map, + const QSet &tilesAdded, + const QSet &tilesRemoved); + + QAbstractGeoTileCache *tileCache(); + QSharedPointer getTileTexture(const QGeoTileSpec &spec); + + + QGeoTiledMappingManagerEngine::CacheAreas cacheHint() const; + +private Q_SLOTS: + void engineTileFinished(const QGeoTileSpec &spec, const QByteArray &bytes, const QString &format); + void engineTileError(const QGeoTileSpec &spec, const QString &errorString); + +Q_SIGNALS: + void tileError(const QGeoTileSpec &spec, const QString &errorString); + void tileVersionChanged(); + +protected: + void setTileFetcher(QGeoTileFetcher *fetcher); + void setTileSize(const QSize &tileSize); + void setTileVersion(int version); + void setCacheHint(QGeoTiledMappingManagerEngine::CacheAreas cacheHint); + void setTileCache(QAbstractGeoTileCache *cache); + +private: + QGeoTiledMappingManagerEnginePrivate *d_ptr; + + Q_DECLARE_PRIVATE(QGeoTiledMappingManagerEngine) + Q_DISABLE_COPY(QGeoTiledMappingManagerEngine) + + friend class QGeoTileFetcher; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoTiledMappingManagerEngine::CacheAreas) + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeotiledmappingmanagerengine_p_p.h b/src/location/maps/qgeotiledmappingmanagerengine_p_p.h new file mode 100644 index 0000000..86ad0f0 --- /dev/null +++ b/src/location/maps/qgeotiledmappingmanagerengine_p_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QGEOTILEDMAPPINGMANAGER_P_H +#define QGEOTILEDMAPPINGMANAGER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include "qgeotiledmappingmanagerengine_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoTiledMap; +class QAbstractGeoTileCache; +class QGeoTileSpec; +class QGeoTileFetcher; + +class QGeoTiledMappingManagerEnginePrivate +{ +public: + QGeoTiledMappingManagerEnginePrivate(); + ~QGeoTiledMappingManagerEnginePrivate(); + + QSize tileSize_; + int m_tileVersion; + QHash > mapHash_; + QHash > tileHash_; + QGeoTiledMappingManagerEngine::CacheAreas cacheHint_; + QAbstractGeoTileCache *tileCache_; + QGeoTileFetcher *fetcher_; + +private: + Q_DISABLE_COPY(QGeoTiledMappingManagerEnginePrivate) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeotiledmapreply.cpp b/src/location/maps/qgeotiledmapreply.cpp new file mode 100644 index 0000000..f2dfd9e --- /dev/null +++ b/src/location/maps/qgeotiledmapreply.cpp @@ -0,0 +1,318 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotiledmapreply_p.h" +#include "qgeotiledmapreply_p_p.h" + +#include + +QT_BEGIN_NAMESPACE +/*! + \class QGeoTiledMapReply + \inmodule QtLocation + \ingroup QtLocation-impl + \since 5.6 + \internal + + \brief The QGeoTiledMapReply class manages a tile fetch operation started + by an instance of QGeoTiledManagerEngine. + + Instances of QGeoTiledMapReply manage the state and results of these + operations. + + The isFinished(), error() and errorString() methods provide information + on whether the operation has completed and if it completed successfully. + + The finished() and error(QGeoTiledMapReply::Error,QString) + signals can be used to monitor the progress of the operation. + + It is possible that a newly created QGeoTiledMapReply may be in a finished + state, most commonly because an error has occurred. Since such an instance + will never emit the finished() or + error(QGeoTiledMapReply::Error,QString) signals, it is + important to check the result of isFinished() before making the connections + to the signals. + + If the operation completes successfully the results are accessed by + mapImageData() and mapImageFormat(). +*/ + +/*! + \enum QGeoTiledMapReply::Error + + Describes an error which prevented the completion of the operation. + + \value NoError + No error has occurred. + \value CommunicationError + An error occurred while communicating with the service provider. + \value ParseError + The response from the service provider was in an unrecognizable format + supported by the service provider. + \value UnknownError + An error occurred which does not fit into any of the other categories. +*/ + +/*! + Constructs a tiled map reply object based on \a request, with parent \a parent. +*/ +QGeoTiledMapReply::QGeoTiledMapReply(const QGeoTileSpec &spec, QObject *parent) + : QObject(parent), + d_ptr(new QGeoTiledMapReplyPrivate(spec)) +{ +} + +/*! + Constructs a tiled map reply object with a given \a error and \a errorString and the specified \a parent. +*/ +QGeoTiledMapReply::QGeoTiledMapReply(Error error, const QString &errorString, QObject *parent) + : QObject(parent), + d_ptr(new QGeoTiledMapReplyPrivate(error, errorString)) {} + +/*! + Destroys this tiled map reply object. +*/ +QGeoTiledMapReply::~QGeoTiledMapReply() +{ + delete d_ptr; +} + +/*! + Sets whether or not this reply has finished to \a finished. + + If \a finished is true, this will cause the finished() signal to be + emitted. + + If the operation completed successfully, + QGeoTiledMapReply::setMapImageData() should be called before this + function. If an error occurred, QGeoTiledMapReply::setError() should be used + instead. +*/ +void QGeoTiledMapReply::setFinished(bool finished) +{ + d_ptr->isFinished = finished; + if (d_ptr->isFinished) + emit this->finished(); +} + +/*! + Return true if the operation completed successfully or encountered an + error which cause the operation to come to a halt. +*/ +bool QGeoTiledMapReply::isFinished() const +{ + return d_ptr->isFinished; +} + +/*! + Sets the error state of this reply to \a error and the textual + representation of the error to \a errorString. + + This will also cause error() and finished() signals to be emitted, in that + order. +*/ +void QGeoTiledMapReply::setError(QGeoTiledMapReply::Error error, const QString &errorString) +{ + d_ptr->error = error; + d_ptr->errorString = errorString; + emit this->error(error, errorString); + setFinished(true); +} + +/*! + Returns the error state of this reply. + + If the result is QGeoTiledMapReply::NoError then no error has occurred. +*/ +QGeoTiledMapReply::Error QGeoTiledMapReply::error() const +{ + return d_ptr->error; +} + +/*! + Returns the textual representation of the error state of this reply. + + If no error has occurred this will return an empty string. It is possible + that an error occurred which has no associated textual representation, in + which case this will also return an empty string. + + To determine whether an error has occurred, check to see if + QGeoTiledMapReply::error() is equal to QGeoTiledMapReply::NoError. +*/ +QString QGeoTiledMapReply::errorString() const +{ + return d_ptr->errorString; +} + +/*! + Returns whether the reply is coming from a cache. +*/ +bool QGeoTiledMapReply::isCached() const +{ + return d_ptr->isCached; +} + +/*! + Sets whether the reply is coming from a cache to \a cached. +*/ +void QGeoTiledMapReply::setCached(bool cached) +{ + d_ptr->isCached = cached; +} + +/*! + Returns the request which corresponds to this reply. +*/ +QGeoTileSpec QGeoTiledMapReply::tileSpec() const +{ + return d_ptr->spec; +} + +/*! + Returns the tile image data. +*/ +QByteArray QGeoTiledMapReply::mapImageData() const +{ + return d_ptr->mapImageData; +} + +/*! + Sets the tile image data to \a data. +*/ +void QGeoTiledMapReply::setMapImageData(const QByteArray &data) +{ + d_ptr->mapImageData = data; +} + +/*! + Returns the format of the tile image. +*/ +QString QGeoTiledMapReply::mapImageFormat() const +{ + return d_ptr->mapImageFormat; +} + +/*! + Sets the format of the tile image to \a format. +*/ +void QGeoTiledMapReply::setMapImageFormat(const QString &format) +{ + d_ptr->mapImageFormat = format; +} + +/*! + Cancels the operation immediately. + + This will do nothing if the reply is finished. +*/ +void QGeoTiledMapReply::abort() +{ + if (!isFinished()) + setFinished(true); +} + +/* + \fn void QGeoTiledMapReply::finished() + + This signal is emitted when this reply has finished processing. + + If error() equals QGeoTiledMapReply::NoError then the processing + finished successfully. + + This signal and QGeoRoutingManager::finished() will be + emitted at the same time. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. + + \fn void QGeoTiledMapReply::error(QGeoTiledMapReply::Error error, const QString &errorString) + + This signal is emitted when an error has been detected in the processing of + this reply. The finished() signal will probably follow. + + The error will be described by the error code \a error. If \a errorString is + not empty it will contain a textual description of the error. + + This signal and QGeoRoutingManager::error() will be emitted at the same time. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ + +/*! + \fn void QGeoTiledMapReply::finished() + + This signal is emitted when this reply has finished processing. + + If error() equals QGeoTiledMapReply::NoError then the processing + finished successfully. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ +/*! + \fn void QGeoTiledMapReply::error(QGeoTiledMapReply::Error error, const QString &errorString) + + This signal is emitted when an error has been detected in the processing of + this reply. The finished() signal will probably follow. + + The error will be described by the error code \a error. If \a errorString is + not empty it will contain a textual description of the error. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ + +/******************************************************************************* +*******************************************************************************/ + +QGeoTiledMapReplyPrivate::QGeoTiledMapReplyPrivate(const QGeoTileSpec &spec) + : error(QGeoTiledMapReply::NoError), + isFinished(false), + isCached(false), + spec(spec) {} + +QGeoTiledMapReplyPrivate::QGeoTiledMapReplyPrivate(QGeoTiledMapReply::Error error, const QString &errorString) + : error(error), + errorString(errorString), + isFinished(true), + isCached(false) {} + +QGeoTiledMapReplyPrivate::~QGeoTiledMapReplyPrivate() {} + +#include "moc_qgeotiledmapreply_p.cpp" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeotiledmapreply_p.h b/src/location/maps/qgeotiledmapreply_p.h new file mode 100644 index 0000000..91852cc --- /dev/null +++ b/src/location/maps/qgeotiledmapreply_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEDMAPREPLY_H +#define QGEOTILEDMAPREPLY_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoTileSpec; +class QGeoTiledMapReplyPrivate; + +class Q_LOCATION_EXPORT QGeoTiledMapReply : public QObject +{ + Q_OBJECT + +public: + enum Error { + NoError, + CommunicationError, + ParseError, + UnknownError + }; + + QGeoTiledMapReply(const QGeoTileSpec &spec, QObject *parent = 0); + QGeoTiledMapReply(Error error, const QString &errorString, QObject *parent = 0); + virtual ~QGeoTiledMapReply(); + + bool isFinished() const; + Error error() const; + QString errorString() const; + + bool isCached() const; + + QGeoTileSpec tileSpec() const; + + QByteArray mapImageData() const; + QString mapImageFormat() const; + + virtual void abort(); + +Q_SIGNALS: + void finished(); + void error(QGeoTiledMapReply::Error error, const QString &errorString = QString()); + +protected: + void setError(Error error, const QString &errorString); + void setFinished(bool finished); + + void setCached(bool cached); + + void setMapImageData(const QByteArray &data); + void setMapImageFormat(const QString &format); + +private: + QGeoTiledMapReplyPrivate *d_ptr; + Q_DISABLE_COPY(QGeoTiledMapReply) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeotiledmapreply_p_p.h b/src/location/maps/qgeotiledmapreply_p_p.h new file mode 100644 index 0000000..5dcf5a7 --- /dev/null +++ b/src/location/maps/qgeotiledmapreply_p_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEDMAPREPLY_P_H +#define QGEOTILEDMAPREPLY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeotiledmapreply_p.h" +#include "qgeotilespec_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoTiledMapReplyPrivate +{ +public: + QGeoTiledMapReplyPrivate(const QGeoTileSpec &spec); + QGeoTiledMapReplyPrivate(QGeoTiledMapReply::Error error, const QString &errorString); + ~QGeoTiledMapReplyPrivate(); + + QGeoTiledMapReply::Error error; + QString errorString; + bool isFinished; + bool isCached; + + QGeoTileSpec spec; + QByteArray mapImageData; + QString mapImageFormat; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeotiledmapscene.cpp b/src/location/maps/qgeotiledmapscene.cpp new file mode 100644 index 0000000..57750f4 --- /dev/null +++ b/src/location/maps/qgeotiledmapscene.cpp @@ -0,0 +1,749 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2014 Jolla Ltd, author: +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeotiledmapscene_p.h" +#include "qgeocameradata_p.h" +#include "qabstractgeotilecache_p.h" +#include "qgeotilespec_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoTiledMapScenePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGeoTiledMapScene) +public: + QGeoTiledMapScenePrivate(); + ~QGeoTiledMapScenePrivate(); + + QSize m_screenSize; // in pixels + int m_tileSize; // the pixel resolution for each tile + QGeoCameraData m_cameraData; + QSet m_visibleTiles; + + QDoubleVector3D m_cameraUp; + QDoubleVector3D m_cameraEye; + QDoubleVector3D m_cameraCenter; + QMatrix4x4 m_projectionMatrix; + + // scales up the tile geometry and the camera altitude, resulting in no visible effect + // other than to control the accuracy of the render by keeping the values in a sensible range + double m_scaleFactor; + + // rounded down, positive zoom is zooming in, corresponding to reduced altitude + int m_intZoomLevel; + + // mercatorToGrid transform + // the number of tiles in each direction for the whole map (earth) at the current zoom level. + // it is 1< > m_textures; + + // tilesToGrid transform + int m_minTileX; // the minimum tile index, i.e. 0 to sideLength which is 1<< zoomLevel + int m_minTileY; + int m_maxTileX; + int m_maxTileY; + int m_tileXWrapsBelow; // the wrap point as a tile index + + // cameraToGrid transform + double m_mercatorCenterX; // center of camera in grid space (0 to sideLength) + double m_mercatorCenterY; + double m_mercatorWidth; // width of camera in grid space (0 to sideLength) + double m_mercatorHeight; + + // screenToWindow transform + double m_screenOffsetX; // in pixels + double m_screenOffsetY; // in pixels + // cameraToScreen transform + double m_screenWidth; // in pixels + double m_screenHeight; // in pixels + + bool m_useVerticalLock; + bool m_verticalLock; + bool m_linearScaling; + + void addTile(const QGeoTileSpec &spec, QSharedPointer texture); + + QDoubleVector2D itemPositionToMercator(const QDoubleVector2D &pos) const; + QDoubleVector2D mercatorToItemPosition(const QDoubleVector2D &mercator) const; + + void setVisibleTiles(const QSet &tiles); + void removeTiles(const QSet &oldTiles); + bool buildGeometry(const QGeoTileSpec &spec, QSGGeometry::TexturedPoint2D *vertices); + void setTileBounds(const QSet &tiles); + void setupCamera(); +}; + +QGeoTiledMapScene::QGeoTiledMapScene(QObject *parent) + : QObject(*new QGeoTiledMapScenePrivate(),parent) +{ +} + +QGeoTiledMapScene::~QGeoTiledMapScene() +{ +} + +void QGeoTiledMapScene::setUseVerticalLock(bool lock) +{ + Q_D(QGeoTiledMapScene); + d->m_useVerticalLock = lock; +} + +void QGeoTiledMapScene::setScreenSize(const QSize &size) +{ + Q_D(QGeoTiledMapScene); + d->m_screenSize = size; +} + +void QGeoTiledMapScene::setTileSize(int tileSize) +{ + Q_D(QGeoTiledMapScene); + d->m_tileSize = tileSize; +} + +void QGeoTiledMapScene::setCameraData(const QGeoCameraData &cameraData) +{ + Q_D(QGeoTiledMapScene); + d->m_cameraData = cameraData; + d->m_intZoomLevel = static_cast(std::floor(d->m_cameraData.zoomLevel())); + float delta = cameraData.zoomLevel() - d->m_intZoomLevel; + d->m_linearScaling = qAbs(delta) > 0.05; + d->m_sideLength = 1 << d->m_intZoomLevel; +} + +void QGeoTiledMapScene::setVisibleTiles(const QSet &tiles) +{ + Q_D(QGeoTiledMapScene); + d->setVisibleTiles(tiles); +} + +const QSet &QGeoTiledMapScene::visibleTiles() const +{ + Q_D(const QGeoTiledMapScene); + return d->m_visibleTiles; +} + +void QGeoTiledMapScene::addTile(const QGeoTileSpec &spec, QSharedPointer texture) +{ + Q_D(QGeoTiledMapScene); + d->addTile(spec, texture); +} + +QDoubleVector2D QGeoTiledMapScene::itemPositionToMercator(const QDoubleVector2D &pos) const +{ + Q_D(const QGeoTiledMapScene); + return d->itemPositionToMercator(pos); +} + +QDoubleVector2D QGeoTiledMapScene::mercatorToItemPosition(const QDoubleVector2D &mercator) const +{ + Q_D(const QGeoTiledMapScene); + return d->mercatorToItemPosition(mercator); +} + +bool QGeoTiledMapScene::verticalLock() const +{ + Q_D(const QGeoTiledMapScene); + return d->m_verticalLock; +} + +QSet QGeoTiledMapScene::texturedTiles() +{ + Q_D(QGeoTiledMapScene); + QSet textured; + foreach (const QGeoTileSpec &tile, d->m_textures.keys()) { + textured += tile; + } + return textured; +} + +void QGeoTiledMapScene::clearTexturedTiles() +{ + Q_D(QGeoTiledMapScene); + d->m_textures.clear(); +} + +QGeoTiledMapScenePrivate::QGeoTiledMapScenePrivate() + : QObjectPrivate(), + m_tileSize(0), + m_scaleFactor(10.0), + m_intZoomLevel(0), + m_sideLength(0), + m_minTileX(-1), + m_minTileY(-1), + m_maxTileX(-1), + m_maxTileY(-1), + m_tileXWrapsBelow(0), + m_mercatorCenterX(0.0), + m_mercatorCenterY(0.0), + m_mercatorWidth(0.0), + m_mercatorHeight(0.0), + m_screenOffsetX(0.0), + m_screenOffsetY(0.0), + m_screenWidth(0.0), + m_screenHeight(0.0), + m_useVerticalLock(false), + m_verticalLock(false), + m_linearScaling(false) +{ +} + +QGeoTiledMapScenePrivate::~QGeoTiledMapScenePrivate() +{ +} + +QDoubleVector2D QGeoTiledMapScenePrivate::itemPositionToMercator(const QDoubleVector2D &pos) const +{ + double x = m_mercatorWidth * (((pos.x() - m_screenOffsetX) / m_screenWidth) - 0.5); + x += m_mercatorCenterX; + + if (x > 1.0 * m_sideLength) + x -= 1.0 * m_sideLength; + if (x < 0.0) + x += 1.0 * m_sideLength; + + x /= 1.0 * m_sideLength; + + double y = m_mercatorHeight * (((pos.y() - m_screenOffsetY) / m_screenHeight) - 0.5); + y += m_mercatorCenterY; + y /= 1.0 * m_sideLength; + + return QDoubleVector2D(x, y); +} + +QDoubleVector2D QGeoTiledMapScenePrivate::mercatorToItemPosition(const QDoubleVector2D &mercator) const +{ + double mx = m_sideLength * mercator.x(); + + double lb = m_mercatorCenterX - m_mercatorWidth / 2.0; + if (lb < 0.0) + lb += m_sideLength; + double ub = m_mercatorCenterX + m_mercatorWidth / 2.0; + if (m_sideLength < ub) + ub -= m_sideLength; + + double m = (mx - m_mercatorCenterX) / m_mercatorWidth; + + double mWrapLower = (mx - m_mercatorCenterX - m_sideLength) / m_mercatorWidth; + double mWrapUpper = (mx - m_mercatorCenterX + m_sideLength) / m_mercatorWidth; + + // correct for crossing dateline + if (qFuzzyCompare(ub - lb + 1.0, 1.0) || (ub < lb) ) { + if (m_mercatorCenterX < ub) { + if (lb < mx) { + m = mWrapLower; + } + } else if (lb < m_mercatorCenterX) { + if (mx <= ub) { + m = mWrapUpper; + } + } + } + + // apply wrapping if necessary so we don't return unreasonably large pos/neg screen positions + // also allows map items to be drawn properly if some of their coords are out of the screen + if ( qAbs(mWrapLower) < qAbs(m) ) + m = mWrapLower; + if ( qAbs(mWrapUpper) < qAbs(m) ) + m = mWrapUpper; + + double x = m_screenWidth * (0.5 + m); + double y = m_screenHeight * (0.5 + (m_sideLength * mercator.y() - m_mercatorCenterY) / m_mercatorHeight); + + return QDoubleVector2D(x + m_screenOffsetX, y + m_screenOffsetY); +} + +bool QGeoTiledMapScenePrivate::buildGeometry(const QGeoTileSpec &spec, QSGGeometry::TexturedPoint2D *vertices) +{ + int x = spec.x(); + + if (x < m_tileXWrapsBelow) + x += m_sideLength; + + if ((x < m_minTileX) + || (m_maxTileX < x) + || (spec.y() < m_minTileY) + || (m_maxTileY < spec.y()) + || (spec.zoom() != m_intZoomLevel)) { + return false; + } + + double edge = m_scaleFactor * m_tileSize; + + double x1 = (x - m_minTileX); + double x2 = x1 + 1.0; + + double y1 = (m_minTileY - spec.y()); + double y2 = y1 - 1.0; + + x1 *= edge; + x2 *= edge; + y1 *= edge; + y2 *= edge; + + //Texture coordinate order for veritcal flip of texture + vertices[0].set(x1, y1, 0, 0); + vertices[1].set(x1, y2, 0, 1); + vertices[2].set(x2, y1, 1, 0); + vertices[3].set(x2, y2, 1, 1); + + return true; +} + +void QGeoTiledMapScenePrivate::addTile(const QGeoTileSpec &spec, QSharedPointer texture) +{ + if (!m_visibleTiles.contains(spec)) // Don't add the geometry if it isn't visible + return; + + m_textures.insert(spec, texture); +} + +void QGeoTiledMapScenePrivate::setVisibleTiles(const QSet &tiles) +{ + // work out the tile bounds for the new scene + setTileBounds(tiles); + + // set up the gl camera for the new scene + setupCamera(); + + QSet toRemove = m_visibleTiles - tiles; + if (!toRemove.isEmpty()) + removeTiles(toRemove); + + m_visibleTiles = tiles; +} + +void QGeoTiledMapScenePrivate::removeTiles(const QSet &oldTiles) +{ + typedef QSet::const_iterator iter; + iter i = oldTiles.constBegin(); + iter end = oldTiles.constEnd(); + + for (; i != end; ++i) { + QGeoTileSpec tile = *i; + m_textures.remove(tile); + } +} + +void QGeoTiledMapScenePrivate::setTileBounds(const QSet &tiles) +{ + if (tiles.isEmpty()) { + m_minTileX = -1; + m_minTileY = -1; + m_maxTileX = -1; + m_maxTileY = -1; + return; + } + + typedef QSet::const_iterator iter; + iter i = tiles.constBegin(); + iter end = tiles.constEnd(); + + // determine whether the set of map tiles crosses the dateline. + // A gap in the tiles indicates dateline crossing + bool hasFarLeft = false; + bool hasFarRight = false; + bool hasMidLeft = false; + bool hasMidRight = false; + + for (; i != end; ++i) { + if ((*i).zoom() != m_intZoomLevel) + continue; + int x = (*i).x(); + if (x == 0) + hasFarLeft = true; + else if (x == (m_sideLength - 1)) + hasFarRight = true; + else if (x == ((m_sideLength / 2) - 1)) { + hasMidLeft = true; + } else if (x == (m_sideLength / 2)) { + hasMidRight = true; + } + } + + // if dateline crossing is detected we wrap all x pos of tiles + // that are in the left half of the map. + m_tileXWrapsBelow = 0; + + if (hasFarLeft && hasFarRight) { + if (!hasMidRight) { + m_tileXWrapsBelow = m_sideLength / 2; + } else if (!hasMidLeft) { + m_tileXWrapsBelow = (m_sideLength / 2) - 1; + } + } + + // finally, determine the min and max bounds + i = tiles.constBegin(); + + QGeoTileSpec tile = *i; + + int x = tile.x(); + if (tile.x() < m_tileXWrapsBelow) + x += m_sideLength; + + m_minTileX = x; + m_maxTileX = x; + m_minTileY = tile.y(); + m_maxTileY = tile.y(); + + ++i; + + for (; i != end; ++i) { + tile = *i; + if (tile.zoom() != m_intZoomLevel) + continue; + + int x = tile.x(); + if (tile.x() < m_tileXWrapsBelow) + x += m_sideLength; + + m_minTileX = qMin(m_minTileX, x); + m_maxTileX = qMax(m_maxTileX, x); + m_minTileY = qMin(m_minTileY, tile.y()); + m_maxTileY = qMax(m_maxTileY, tile.y()); + } +} + +void QGeoTiledMapScenePrivate::setupCamera() +{ + double f = 1.0 * qMin(m_screenSize.width(), m_screenSize.height()); + + // fraction of zoom level + double z = std::pow(2.0, m_cameraData.zoomLevel() - m_intZoomLevel) * m_tileSize; + + // calculate altitdue that allows the visible map tiles + // to fit in the screen correctly (note that a larger f will cause + // the camera be higher, resulting in gray areas displayed around + // the tiles) + double altitude = f / (2.0 * z) ; + + // mercatorWidth_ and mercatorHeight_ define the ratio for + // mercator and screen coordinate conversion, + // see mercatorToItemPosition() and itemPositionToMercator() + m_mercatorHeight = m_screenSize.height() / z; + m_mercatorWidth = m_screenSize.width() / z; + + // calculate center + double edge = m_scaleFactor * m_tileSize; + + // first calculate the camera center in map space in the range of 0 <-> sideLength (2^z) + QDoubleVector3D center = (m_sideLength * QGeoProjection::coordToMercator(m_cameraData.center())); + + // wrap the center if necessary (due to dateline crossing) + if (center.x() < m_tileXWrapsBelow) + center.setX(center.x() + 1.0 * m_sideLength); + + m_mercatorCenterX = center.x(); + m_mercatorCenterY = center.y(); + + // work out where the camera center is w.r.t minimum tile bounds + center.setX(center.x() - 1.0 * m_minTileX); + center.setY(1.0 * m_minTileY - center.y()); + + // letter box vertically + if (m_useVerticalLock && (m_mercatorHeight > 1.0 * m_sideLength)) { + center.setY(-1.0 * m_sideLength / 2.0); + m_mercatorCenterY = m_sideLength / 2.0; + m_screenOffsetY = m_screenSize.height() * (0.5 - m_sideLength / (2 * m_mercatorHeight)); + m_screenHeight = m_screenSize.height() - 2 * m_screenOffsetY; + m_mercatorHeight = 1.0 * m_sideLength; + m_verticalLock = true; + } else { + m_screenOffsetY = 0.0; + m_screenHeight = m_screenSize.height(); + m_verticalLock = false; + } + + if (m_mercatorWidth > 1.0 * m_sideLength) { + m_screenOffsetX = m_screenSize.width() * (0.5 - (m_sideLength / (2 * m_mercatorWidth))); + m_screenWidth = m_screenSize.width() - 2 * m_screenOffsetX; + m_mercatorWidth = 1.0 * m_sideLength; + } else { + m_screenOffsetX = 0.0; + m_screenWidth = m_screenSize.width(); + } + + // apply necessary scaling to the camera center + center *= edge; + + // calculate eye + + QDoubleVector3D eye = center; + eye.setZ(altitude * edge); + + // calculate up + + QDoubleVector3D view = eye - center; + QDoubleVector3D side = QDoubleVector3D::normal(view, QDoubleVector3D(0.0, 1.0, 0.0)); + QDoubleVector3D up = QDoubleVector3D::normal(side, view); + + // old bearing, tilt and roll code + // QMatrix4x4 mBearing; + // mBearing.rotate(-1.0 * camera.bearing(), view); + // up = mBearing * up; + + // QDoubleVector3D side2 = QDoubleVector3D::normal(up, view); + // QMatrix4x4 mTilt; + // mTilt.rotate(camera.tilt(), side2); + // eye = (mTilt * view) + center; + + // view = eye - center; + // side = QDoubleVector3D::normal(view, QDoubleVector3D(0.0, 1.0, 0.0)); + // up = QDoubleVector3D::normal(view, side2); + + // QMatrix4x4 mRoll; + // mRoll.rotate(camera.roll(), view); + // up = mRoll * up; + + // near plane and far plane + + double nearPlane = 1.0; + double farPlane = (altitude + 1.0) * edge; + + m_cameraUp = up; + m_cameraCenter = center; + m_cameraEye = eye; + + double aspectRatio = 1.0 * m_screenSize.width() / m_screenSize.height(); + float halfWidth = 1; + float halfHeight = 1; + if (aspectRatio > 1.0) { + halfWidth *= aspectRatio; + } else if (aspectRatio > 0.0f && aspectRatio < 1.0f) { + halfHeight /= aspectRatio; + } + m_projectionMatrix.setToIdentity(); + m_projectionMatrix.frustum(-halfWidth, halfWidth, -halfHeight, halfHeight, nearPlane, farPlane); +} + +class QGeoTiledMapTileContainerNode : public QSGTransformNode +{ +public: + void addChild(const QGeoTileSpec &spec, QSGSimpleTextureNode *node) + { + tiles.insert(spec, node); + appendChildNode(node); + } + QHash tiles; +}; + +class QGeoTiledMapRootNode : public QSGClipNode +{ +public: + QGeoTiledMapRootNode() + : isTextureLinear(false) + , geometry(QSGGeometry::defaultAttributes_Point2D(), 4) + , root(new QSGTransformNode()) + , tiles(new QGeoTiledMapTileContainerNode()) + , wrapLeft(new QGeoTiledMapTileContainerNode()) + , wrapRight(new QGeoTiledMapTileContainerNode()) + { + setIsRectangular(true); + setGeometry(&geometry); + root->appendChildNode(tiles); + root->appendChildNode(wrapLeft); + root->appendChildNode(wrapRight); + appendChildNode(root); + } + + ~QGeoTiledMapRootNode() + { + qDeleteAll(textures); + } + + void setClipRect(const QRect &rect) + { + if (rect != clipRect) { + QSGGeometry::updateRectGeometry(&geometry, rect); + QSGClipNode::setClipRect(rect); + clipRect = rect; + markDirty(DirtyGeometry); + } + } + + void updateTiles(QGeoTiledMapTileContainerNode *root, QGeoTiledMapScenePrivate *d, double camAdjust); + + bool isTextureLinear; + + QSGGeometry geometry; + QRect clipRect; + + QSGTransformNode *root; + + QGeoTiledMapTileContainerNode *tiles; // The majority of the tiles + QGeoTiledMapTileContainerNode *wrapLeft; // When zoomed out, the tiles that wrap around on the left. + QGeoTiledMapTileContainerNode *wrapRight; // When zoomed out, the tiles that wrap around on the right + + QHash textures; +}; + +static bool qgeotiledmapscene_isTileInViewport(const QSGGeometry::TexturedPoint2D *tp, const QMatrix4x4 &matrix) { + QPolygonF polygon; polygon.reserve(4); + for (int i=0; i<4; ++i) + polygon << matrix * QPointF(tp[i].x, tp[i].y); + return QRectF(-1, -1, 2, 2).intersects(polygon.boundingRect()); +} + +static QVector3D toVector3D(const QDoubleVector3D& in) +{ + return QVector3D(in.x(), in.y(), in.z()); +} + +void QGeoTiledMapRootNode::updateTiles(QGeoTiledMapTileContainerNode *root, + QGeoTiledMapScenePrivate *d, + double camAdjust) +{ + // Set up the matrix... + QDoubleVector3D eye = d->m_cameraEye; + eye.setX(eye.x() + camAdjust); + QDoubleVector3D center = d->m_cameraCenter; + center.setX(center.x() + camAdjust); + QMatrix4x4 cameraMatrix; + cameraMatrix.lookAt(toVector3D(eye), toVector3D(center), toVector3D(d->m_cameraUp)); + root->setMatrix(d->m_projectionMatrix * cameraMatrix); + + QSet tilesInSG = QSet::fromList(root->tiles.keys()); + QSet toRemove = tilesInSG - d->m_visibleTiles; + QSet toAdd = d->m_visibleTiles - tilesInSG; + + foreach (const QGeoTileSpec &s, toRemove) + delete root->tiles.take(s); + + for (QHash::iterator it = root->tiles.begin(); + it != root->tiles.end(); ) { + QSGGeometry visualGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); + QSGGeometry::TexturedPoint2D *v = visualGeometry.vertexDataAsTexturedPoint2D(); + bool ok = d->buildGeometry(it.key(), v) && qgeotiledmapscene_isTileInViewport(v, root->matrix()); + QSGSimpleTextureNode *node = it.value(); + QSGNode::DirtyState dirtyBits = 0; + + // Check and handle changes to vertex data. + if (ok && memcmp(node->geometry()->vertexData(), v, 4 * sizeof(QSGGeometry::TexturedPoint2D)) != 0) { + if (v[0].x == v[3].x || v[0].y == v[3].y) { // top-left == bottom-right => invalid => remove + ok = false; + } else { + memcpy(node->geometry()->vertexData(), v, 4 * sizeof(QSGGeometry::TexturedPoint2D)); + dirtyBits |= QSGNode::DirtyGeometry; + } + } + + if (!ok) { + it = root->tiles.erase(it); + delete node; + } else { + if (isTextureLinear != d->m_linearScaling) { + node->setFiltering(d->m_linearScaling ? QSGTexture::Linear : QSGTexture::Nearest); + dirtyBits |= QSGNode::DirtyMaterial; + } + if (dirtyBits != 0) + node->markDirty(dirtyBits); + it++; + } + } + + foreach (const QGeoTileSpec &s, toAdd) { + QGeoTileTexture *tileTexture = d->m_textures.value(s).data(); + if (!tileTexture || tileTexture->image.isNull()) + continue; + QSGSimpleTextureNode *tileNode = new QSGSimpleTextureNode(); + // note: setTexture will update coordinates so do it here, before we buildGeometry + tileNode->setTexture(textures.value(s)); + Q_ASSERT(tileNode->geometry()); + Q_ASSERT(tileNode->geometry()->attributes() == QSGGeometry::defaultAttributes_TexturedPoint2D().attributes); + Q_ASSERT(tileNode->geometry()->vertexCount() == 4); + if (d->buildGeometry(s, tileNode->geometry()->vertexDataAsTexturedPoint2D()) + && qgeotiledmapscene_isTileInViewport(tileNode->geometry()->vertexDataAsTexturedPoint2D(), root->matrix())) { + tileNode->setFiltering(d->m_linearScaling ? QSGTexture::Linear : QSGTexture::Nearest); + root->addChild(s, tileNode); + } else { + delete tileNode; + } + } +} + +QSGNode *QGeoTiledMapScene::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window) +{ + Q_D(QGeoTiledMapScene); + float w = d->m_screenSize.width(); + float h = d->m_screenSize.height(); + if (w <= 0 || h <= 0) { + delete oldNode; + return 0; + } + + QGeoTiledMapRootNode *mapRoot = static_cast(oldNode); + if (!mapRoot) + mapRoot = new QGeoTiledMapRootNode(); + + mapRoot->setClipRect(QRect(d->m_screenOffsetX, d->m_screenOffsetY, d->m_screenWidth, d->m_screenHeight)); + + QMatrix4x4 itemSpaceMatrix; + itemSpaceMatrix.scale(w / 2, h / 2); + itemSpaceMatrix.translate(1, 1); + itemSpaceMatrix.scale(1, -1); + mapRoot->root->setMatrix(itemSpaceMatrix); + + QSet textures = QSet::fromList(mapRoot->textures.keys()); + QSet toRemove = textures - d->m_visibleTiles; + QSet toAdd = d->m_visibleTiles - textures; + + foreach (const QGeoTileSpec &spec, toRemove) + mapRoot->textures.take(spec)->deleteLater(); + foreach (const QGeoTileSpec &spec, toAdd) { + QGeoTileTexture *tileTexture = d->m_textures.value(spec).data(); + if (!tileTexture || tileTexture->image.isNull()) + continue; + mapRoot->textures.insert(spec, window->createTextureFromImage(tileTexture->image)); + } + + double sideLength = d->m_scaleFactor * d->m_tileSize * d->m_sideLength; + mapRoot->updateTiles(mapRoot->tiles, d, 0); + mapRoot->updateTiles(mapRoot->wrapLeft, d, +sideLength); + mapRoot->updateTiles(mapRoot->wrapRight, d, -sideLength); + + mapRoot->isTextureLinear = d->m_linearScaling; + + return mapRoot; +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeotiledmapscene_p.h b/src/location/maps/qgeotiledmapscene_p.h new file mode 100644 index 0000000..1e3a9bc --- /dev/null +++ b/src/location/maps/qgeotiledmapscene_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOTILEDMAPSCENE_P_H +#define QGEOTILEDMAPSCENE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCameraData; +class QGeoTileSpec; +class QDoubleVector2D; +class QGeoTileTexture; +class QSGNode; +class QQuickWindow; +class QGeoTiledMapScenePrivate; + +class Q_LOCATION_EXPORT QGeoTiledMapScene : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoTiledMapScene) +public: + explicit QGeoTiledMapScene(QObject *parent = 0); + virtual ~QGeoTiledMapScene(); + + void setScreenSize(const QSize &size); + void setTileSize(int tileSize); + void setCameraData(const QGeoCameraData &cameraData); + + void setVisibleTiles(const QSet &tiles); + const QSet &visibleTiles() const; + + void setUseVerticalLock(bool lock); + + void addTile(const QGeoTileSpec &spec, QSharedPointer texture); + + QDoubleVector2D itemPositionToMercator(const QDoubleVector2D &pos) const; + QDoubleVector2D mercatorToItemPosition(const QDoubleVector2D &mercator) const; + + QSGNode *updateSceneGraph(QSGNode *oldNode, QQuickWindow *window); + + bool verticalLock() const; + QSet texturedTiles(); + + void clearTexturedTiles(); + +Q_SIGNALS: + void newTilesVisible(const QSet &newTiles); + +private: + Q_DISABLE_COPY(QGeoTiledMapScene) +}; + +QT_END_NAMESPACE + +#endif // QGEOTILEDMAPSCENE_P_H diff --git a/src/location/maps/qgeotilefetcher.cpp b/src/location/maps/qgeotilefetcher.cpp new file mode 100644 index 0000000..0e0e81c --- /dev/null +++ b/src/location/maps/qgeotilefetcher.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qgeomappingmanagerengine_p.h" +#include "qgeotilefetcher_p.h" +#include "qgeotilefetcher_p_p.h" +#include "qgeotiledmapreply_p.h" +#include "qgeotilespec_p.h" +#include "qgeotiledmap_p.h" + +QT_BEGIN_NAMESPACE + +QGeoTileFetcher::QGeoTileFetcher(QObject *parent) +: QObject(parent), d_ptr(new QGeoTileFetcherPrivate) +{ + Q_D(QGeoTileFetcher); + + d->enabled_ = true; + + if (!d->queue_.isEmpty()) + d->timer_.start(0, this); +} + +QGeoTileFetcher::~QGeoTileFetcher() +{ + + delete d_ptr; +} + +void QGeoTileFetcher::updateTileRequests(const QSet &tilesAdded, + const QSet &tilesRemoved) +{ + Q_D(QGeoTileFetcher); + + QMutexLocker ml(&d->queueMutex_); + + cancelTileRequests(tilesRemoved); + + d->queue_ += tilesAdded.toList(); + + if (d->enabled_ && initialized() && !d->queue_.isEmpty() && !d->timer_.isActive()) + d->timer_.start(0, this); +} + +void QGeoTileFetcher::cancelTileRequests(const QSet &tiles) +{ + Q_D(QGeoTileFetcher); + + typedef QSet::const_iterator tile_iter; + tile_iter tile = tiles.constBegin(); + tile_iter end = tiles.constEnd(); + for (; tile != end; ++tile) { + QGeoTiledMapReply *reply = d->invmap_.value(*tile, 0); + if (reply) { + d->invmap_.remove(*tile); + reply->abort(); + if (reply->isFinished()) + reply->deleteLater(); + } + d->queue_.removeAll(*tile); + } +} + +void QGeoTileFetcher::requestNextTile() +{ + Q_D(QGeoTileFetcher); + + QMutexLocker ml(&d->queueMutex_); + + if (!d->enabled_) + return; + + if (d->queue_.isEmpty()) + return; + + QGeoTileSpec ts = d->queue_.takeFirst(); + + QGeoTiledMapReply *reply = getTileImage(ts); + + if (reply->isFinished()) { + handleReply(reply, ts); + } else { + connect(reply, + SIGNAL(finished()), + this, + SLOT(finished()), + Qt::QueuedConnection); + + d->invmap_.insert(ts, reply); + } + + if (d->queue_.isEmpty()) + d->timer_.stop(); +} + +void QGeoTileFetcher::finished() +{ + Q_D(QGeoTileFetcher); + + QMutexLocker ml(&d->queueMutex_); + + QGeoTiledMapReply *reply = qobject_cast(sender()); + if (!reply) + return; + + QGeoTileSpec spec = reply->tileSpec(); + + if (!d->invmap_.contains(spec)) { + reply->deleteLater(); + return; + } + + d->invmap_.remove(spec); + + handleReply(reply, spec); +} + +void QGeoTileFetcher::timerEvent(QTimerEvent *event) +{ + Q_D(QGeoTileFetcher); + if (event->timerId() != d->timer_.timerId()) { + QObject::timerEvent(event); + return; + } + + if (d->queue_.isEmpty() || !initialized()) { + d->timer_.stop(); + return; + } + + requestNextTile(); +} + +bool QGeoTileFetcher::initialized() const +{ + return true; +} + +void QGeoTileFetcher::handleReply(QGeoTiledMapReply *reply, const QGeoTileSpec &spec) +{ + Q_D(QGeoTileFetcher); + + if (!d->enabled_) { + reply->deleteLater(); + return; + } + + if (reply->error() == QGeoTiledMapReply::NoError) { + emit tileFinished(spec, reply->mapImageData(), reply->mapImageFormat()); + } else { + emit tileError(spec, reply->errorString()); + } + + reply->deleteLater(); +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoTileFetcherPrivate::QGeoTileFetcherPrivate() +: enabled_(false) +{ +} + +QGeoTileFetcherPrivate::~QGeoTileFetcherPrivate() +{ +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeotilefetcher_p.h b/src/location/maps/qgeotilefetcher_p.h new file mode 100644 index 0000000..cbd8b99 --- /dev/null +++ b/src/location/maps/qgeotilefetcher_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEFETCHER_H +#define QGEOTILEFETCHER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include "qgeomaptype_p.h" +#include "qgeotiledmappingmanagerengine_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoMapRequestOptions; + +class QGeoTileFetcherPrivate; +class QGeoTiledMappingManagerEngine; +class QGeoTiledMapReply; +class QGeoTileSpec; + +class Q_LOCATION_EXPORT QGeoTileFetcher : public QObject +{ + Q_OBJECT + +public: + QGeoTileFetcher(QObject *parent = 0); + virtual ~QGeoTileFetcher(); + +public Q_SLOTS: + void updateTileRequests(const QSet &tilesAdded, const QSet &tilesRemoved); + +private Q_SLOTS: + void cancelTileRequests(const QSet &tiles); + void requestNextTile(); + void finished(); + +Q_SIGNALS: + void tileFinished(const QGeoTileSpec &spec, const QByteArray &bytes, const QString &format); + void tileError(const QGeoTileSpec &spec, const QString &errorString); + +protected: + void timerEvent(QTimerEvent *event); + QGeoTiledMappingManagerEngine::CacheAreas cacheHint() const; + virtual bool initialized() const; + +private: + QGeoTileFetcherPrivate *d_ptr; + + virtual QGeoTiledMapReply *getTileImage(const QGeoTileSpec &spec) = 0; + void handleReply(QGeoTiledMapReply *reply, const QGeoTileSpec &spec); + + Q_DECLARE_PRIVATE(QGeoTileFetcher) + Q_DISABLE_COPY(QGeoTileFetcher) + + friend class QGeoTiledMappingManagerEngine; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeotilefetcher_p_p.h b/src/location/maps/qgeotilefetcher_p_p.h new file mode 100644 index 0000000..acd7288 --- /dev/null +++ b/src/location/maps/qgeotilefetcher_p_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEFETCHER_P_H +#define QGEOTILEFETCHER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include "qgeomaptype_p.h" + +QT_BEGIN_NAMESPACE + +class QGeoTileSpec; +class QGeoTiledMapReply; +class QGeoTiledMappingManagerEngine; + +class QGeoTileFetcherPrivate +{ +public: + QGeoTileFetcherPrivate(); + virtual ~QGeoTileFetcherPrivate(); + + bool enabled_; + QBasicTimer timer_; + QMutex queueMutex_; + QList queue_; + QHash invmap_; + +private: + Q_DISABLE_COPY(QGeoTileFetcherPrivate) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/maps/qgeotilerequestmanager.cpp b/src/location/maps/qgeotilerequestmanager.cpp new file mode 100644 index 0000000..1409856 --- /dev/null +++ b/src/location/maps/qgeotilerequestmanager.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeotilerequestmanager_p.h" +#include "qgeotilespec_p.h" +#include "qgeotiledmap_p.h" +#include "qgeotiledmappingmanagerengine_p.h" +#include "qabstractgeotilecache_p.h" +#include + +QT_BEGIN_NAMESPACE + +class RetryFuture; + +class QGeoTileRequestManagerPrivate +{ +public: + explicit QGeoTileRequestManagerPrivate(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine); + ~QGeoTileRequestManagerPrivate(); + + QGeoTiledMap *m_map; + QPointer m_engine; + + QList > requestTiles(const QSet &tiles); + void tileError(const QGeoTileSpec &tile, const QString &errorString); + + QHash m_retries; + QHash > m_futures; + QSet m_requested; + + void tileFetched(const QGeoTileSpec &spec); +}; + +QGeoTileRequestManager::QGeoTileRequestManager(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine) + : d_ptr(new QGeoTileRequestManagerPrivate(map, engine)) +{ + +} + +QGeoTileRequestManager::~QGeoTileRequestManager() +{ + +} + +QList > QGeoTileRequestManager::requestTiles(const QSet &tiles) +{ + return d_ptr->requestTiles(tiles); +} + +void QGeoTileRequestManager::tileFetched(const QGeoTileSpec &spec) +{ + d_ptr->tileFetched(spec); +} + +QSharedPointer QGeoTileRequestManager::tileTexture(const QGeoTileSpec &spec) +{ + if (d_ptr->m_engine) + return d_ptr->m_engine->getTileTexture(spec); + else + return QSharedPointer(); +} + +void QGeoTileRequestManager::tileError(const QGeoTileSpec &tile, const QString &errorString) +{ + d_ptr->tileError(tile, errorString); +} + +QGeoTileRequestManagerPrivate::QGeoTileRequestManagerPrivate(QGeoTiledMap *map,QGeoTiledMappingManagerEngine *engine) + : m_map(map), + m_engine(engine) +{ +} + +QGeoTileRequestManagerPrivate::~QGeoTileRequestManagerPrivate() +{ +} + +QList > QGeoTileRequestManagerPrivate::requestTiles(const QSet &tiles) +{ + QSet cancelTiles = m_requested - tiles; + QSet requestTiles = tiles - m_requested; + QSet cached; +// int tileSize = tiles.size(); +// int newTiles = requestTiles.size(); + + typedef QSet::const_iterator iter; + + QList > cachedTex; + + // remove tiles in cache from request tiles + if (!m_engine.isNull()) { + iter i = requestTiles.constBegin(); + iter end = requestTiles.constEnd(); + for (; i != end; ++i) { + QGeoTileSpec tile = *i; + QSharedPointer tex = m_engine->getTileTexture(tile); + if (tex) { + cachedTex << tex; + cached.insert(tile); + } + } + } + + requestTiles -= cached; + + m_requested -= cancelTiles; + m_requested += requestTiles; + +// qDebug() << "required # tiles: " << tileSize << ", new tiles: " << newTiles << ", total server requests: " << requested_.size(); + + if (!requestTiles.isEmpty() || !cancelTiles.isEmpty()) { + if (!m_engine.isNull()) { +// qDebug() << "new server requests: " << requestTiles.size() << ", server cancels: " << cancelTiles.size(); + m_engine->updateTileRequests(m_map, requestTiles, cancelTiles); + + // Remove any cancelled tiles from the error retry hash to avoid + // re-using the numbers for a totally different request cycle. + iter i = cancelTiles.constBegin(); + iter end = cancelTiles.constEnd(); + for (; i != end; ++i) { + m_retries.remove(*i); + m_futures.remove(*i); + } + } + } + + return cachedTex; +} + +void QGeoTileRequestManagerPrivate::tileFetched(const QGeoTileSpec &spec) +{ + m_map->updateTile(spec); + m_requested.remove(spec); + m_retries.remove(spec); + m_futures.remove(spec); +} + +// Represents a tile that needs to be retried after a certain period of time +class RetryFuture : public QObject +{ + Q_OBJECT +public: + RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent = 0); + +public Q_SLOTS: + void retry(); + +private: + QGeoTileSpec m_tile; + QGeoTiledMap *m_map; + QPointer m_engine; +}; + +RetryFuture::RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent) + : QObject(parent), m_tile(tile), m_map(map), m_engine(engine) +{} + +void RetryFuture::retry() +{ + QSet requestTiles; + QSet cancelTiles; + requestTiles.insert(m_tile); + if (!m_engine.isNull()) + m_engine->updateTileRequests(m_map, requestTiles, cancelTiles); +} + +void QGeoTileRequestManagerPrivate::tileError(const QGeoTileSpec &tile, const QString &errorString) +{ + if (m_requested.contains(tile)) { + int count = m_retries.value(tile, 0); + m_retries.insert(tile, count + 1); + + if (count >= 5) { + qWarning("QGeoTileRequestManager: Failed to fetch tile (%d,%d,%d) 5 times, giving up. " + "Last error message was: '%s'", + tile.x(), tile.y(), tile.zoom(), qPrintable(errorString)); + m_requested.remove(tile); + m_retries.remove(tile); + m_futures.remove(tile); + + } else { + // Exponential time backoff when retrying + int delay = (1 << count) * 500; + + QSharedPointer future(new RetryFuture(tile,m_map,m_engine)); + m_futures.insert(tile, future); + + QTimer::singleShot(delay, future.data(), SLOT(retry())); + // Passing .data() to singleShot is ok -- Qt will clean up the + // connection if the target qobject is deleted + } + } +} + +#include "qgeotilerequestmanager.moc" + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeotilerequestmanager_p.h b/src/location/maps/qgeotilerequestmanager_p.h new file mode 100644 index 0000000..66d2251 --- /dev/null +++ b/src/location/maps/qgeotilerequestmanager_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOTILEREQUESTMANAGER_P_H +#define QGEOTILEREQUESTMANAGER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +class QGeoTiledMap; +class QGeoTiledMappingManagerEngine; +class QGeoTileSpec; +class QGeoTileTexture; + +class QGeoTileRequestManagerPrivate; + +class QGeoTileRequestManager +{ +public: + explicit QGeoTileRequestManager(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine); + ~QGeoTileRequestManager(); + + QList > requestTiles(const QSet &tiles); + + void tileError(const QGeoTileSpec &tile, const QString &errorString); + void tileFetched(const QGeoTileSpec &spec); + QSharedPointer tileTexture(const QGeoTileSpec &spec); + +private: + QScopedPointer d_ptr; + Q_DISABLE_COPY(QGeoTileRequestManager) +}; + +QT_END_NAMESPACE + +#endif // QGEOTILEREQUESTMANAGER_P_H diff --git a/src/location/maps/qgeotilespec.cpp b/src/location/maps/qgeotilespec.cpp new file mode 100644 index 0000000..2e04b53 --- /dev/null +++ b/src/location/maps/qgeotilespec.cpp @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotilespec_p.h" +#include "qgeotilespec_p_p.h" + +#include + +QT_BEGIN_NAMESPACE + +QGeoTileSpec::QGeoTileSpec() + : d(QSharedDataPointer(new QGeoTileSpecPrivate())) {} + +QGeoTileSpec::QGeoTileSpec(const QString &plugin, int mapId, int zoom, int x, int y, int version) + : d(QSharedDataPointer(new QGeoTileSpecPrivate(plugin, mapId, zoom, x, y, version))) {} + +QGeoTileSpec::QGeoTileSpec(const QGeoTileSpec &other) + : d(other.d) {} + +QGeoTileSpec::~QGeoTileSpec() { +} + +QGeoTileSpec &QGeoTileSpec::operator = (const QGeoTileSpec &other) +{ + if (this == &other) + return *this; + + d = other.d; + return *this; +} + +QString QGeoTileSpec::plugin() const +{ + return d->plugin_; +} + +void QGeoTileSpec::setZoom(int zoom) +{ + d->zoom_ = zoom; +} + +int QGeoTileSpec::zoom() const +{ + return d->zoom_; +} + +void QGeoTileSpec::setX(int x) +{ + d->x_ = x; +} + +int QGeoTileSpec::x() const +{ + return d->x_; +} + +void QGeoTileSpec::setY(int y) +{ + d->y_ = y; +} + +int QGeoTileSpec::y() const +{ + return d->y_; +} + +void QGeoTileSpec::setMapId(int mapId) +{ + d->mapId_ = mapId; +} + +int QGeoTileSpec::mapId() const +{ + return d->mapId_; +} + +void QGeoTileSpec::setVersion(int version) +{ + d->version_ = version; +} + +int QGeoTileSpec::version() const +{ + return d->version_; +} + +bool QGeoTileSpec::operator == (const QGeoTileSpec &rhs) const +{ + return (*(d.constData()) == *(rhs.d.constData())); +} + +bool QGeoTileSpec::operator < (const QGeoTileSpec &rhs) const +{ + return (*(d.constData()) < *(rhs.d.constData())); +} + +unsigned int qHash(const QGeoTileSpec &spec) +{ + unsigned int result = (qHash(spec.plugin()) * 13) % 31; + result += ((spec.mapId() * 17) % 31) << 5; + result += ((spec.zoom() * 19) % 31) << 10; + result += ((spec.x() * 23) % 31) << 15; + result += ((spec.y() * 29) % 31) << 20; + result += (spec.version() % 3) << 25; + return result; +} + +QDebug operator<< (QDebug dbg, const QGeoTileSpec &spec) +{ + dbg << spec.plugin() << spec.mapId() << spec.zoom() << spec.x() << spec.y() << spec.version(); + return dbg; +} + +QGeoTileSpecPrivate::QGeoTileSpecPrivate() + : mapId_(0), + zoom_(-1), + x_(-1), + y_(-1), + version_(-1) {} + +QGeoTileSpecPrivate::QGeoTileSpecPrivate(const QGeoTileSpecPrivate &other) + : QSharedData(other), + plugin_(other.plugin_), + mapId_(other.mapId_), + zoom_(other.zoom_), + x_(other.x_), + y_(other.y_), + version_(other.version_) {} + +QGeoTileSpecPrivate::QGeoTileSpecPrivate(const QString &plugin, int mapId, int zoom, int x, int y, int version) + : plugin_(plugin), + mapId_(mapId), + zoom_(zoom), + x_(x), + y_(y), + version_(version) {} + +QGeoTileSpecPrivate::~QGeoTileSpecPrivate() {} + +QGeoTileSpecPrivate &QGeoTileSpecPrivate::operator = (const QGeoTileSpecPrivate &other) +{ + if (this == &other) + return *this; + + plugin_ = other.plugin_; + mapId_ = other.mapId_; + zoom_ = other.zoom_; + x_ = other.x_; + y_ = other.y_; + version_ = other.version_; + + return *this; +} + +bool QGeoTileSpecPrivate::operator == (const QGeoTileSpecPrivate &rhs) const +{ + if (plugin_ != rhs.plugin_) + return false; + + if (mapId_ != rhs.mapId_) + return false; + + if (zoom_ != rhs.zoom_) + return false; + + if (x_ != rhs.x_) + return false; + + if (y_ != rhs.y_) + return false; + + if (version_ != rhs.version_) + return false; + + return true; +} + +bool QGeoTileSpecPrivate::operator < (const QGeoTileSpecPrivate &rhs) const +{ + if (plugin_ < rhs.plugin_) + return true; + if (plugin_ > rhs.plugin_) + return false; + + if (mapId_ < rhs.mapId_) + return true; + if (mapId_ > rhs.mapId_) + return false; + + if (zoom_ < rhs.zoom_) + return true; + if (zoom_ > rhs.zoom_) + return false; + + if (x_ < rhs.x_) + return true; + if (x_ > rhs.x_) + return false; + + if (y_ < rhs.y_) + return true; + if (y_ > rhs.y_) + return false; + + return (version_ < rhs.version_); +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeotilespec_p.h b/src/location/maps/qgeotilespec_p.h new file mode 100644 index 0000000..b277824 --- /dev/null +++ b/src/location/maps/qgeotilespec_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILESPEC_H +#define QGEOTILESPEC_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoTileSpecPrivate; + +class Q_LOCATION_EXPORT QGeoTileSpec +{ +public: + QGeoTileSpec(); + QGeoTileSpec(const QGeoTileSpec &other); + QGeoTileSpec(const QString &plugin, int mapId, int zoom, int x, int y, int version = -1); + ~QGeoTileSpec(); + + QGeoTileSpec &operator = (const QGeoTileSpec &other); + + QString plugin() const; + + void setZoom(int zoom); + int zoom() const; + + void setX(int x); + int x() const; + + void setY(int y); + int y() const; + + void setMapId(int mapId); + int mapId() const; + + void setVersion(int version); + int version() const; + + bool operator == (const QGeoTileSpec &rhs) const; + bool operator < (const QGeoTileSpec &rhs) const; + +private: + QSharedDataPointer d; +}; + +Q_LOCATION_EXPORT unsigned int qHash(const QGeoTileSpec &spec); + +Q_LOCATION_EXPORT QDebug operator<<(QDebug, const QGeoTileSpec &); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoTileSpec) + +#endif // QGEOTILESPEC_H diff --git a/src/location/maps/qgeotilespec_p_p.h b/src/location/maps/qgeotilespec_p_p.h new file mode 100644 index 0000000..1e7442f --- /dev/null +++ b/src/location/maps/qgeotilespec_p_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOTILESPEC_P_H +#define QGEOTILESPEC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoTileSpecPrivate : public QSharedData +{ +public: + QGeoTileSpecPrivate(); + QGeoTileSpecPrivate(const QGeoTileSpecPrivate &other); + QGeoTileSpecPrivate(const QString &plugin, int mapId, int zoom, int x, int y, int version); + ~QGeoTileSpecPrivate(); + + QGeoTileSpecPrivate &operator = (const QGeoTileSpecPrivate &other); + + bool operator == (const QGeoTileSpecPrivate &rhs) const; + bool operator < (const QGeoTileSpecPrivate &rhs) const; + + QString plugin_; + int mapId_; + int zoom_; + int x_; + int y_; + int version_; +}; + +QT_END_NAMESPACE + +#endif // QGEOTILESPEC_P_H diff --git a/src/location/places/placemacro.h b/src/location/places/placemacro.h new file mode 100644 index 0000000..e0603fc --- /dev/null +++ b/src/location/places/placemacro.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PLACE_MACRO_H +#define PLACE_MACRO_H + +#include + +QT_BEGIN_NAMESPACE + +#define Q_DECLARE_D_FUNC(Class) \ + inline Class##Private *d_func(); \ + inline const Class##Private *d_func() const;\ + friend class Class##Private; + +#define Q_DECLARE_COPY_CTOR(Class, BaseClass) \ + Class(const BaseClass &other); + +#define Q_IMPLEMENT_D_FUNC(Class) \ + Class##Private *Class::d_func() { return reinterpret_cast(d_ptr.data()); } \ + const Class##Private *Class::d_func() const { return reinterpret_cast(d_ptr.constData()); } + +#define Q_IMPLEMENT_COPY_CTOR(Class, BaseClass) \ + Class::Class(const BaseClass &other) : BaseClass() { Class##Private::copyIfPossible(d_ptr, other); } + +#define Q_DEFINE_PRIVATE_HELPER(Class, BaseClass, ClassType) \ + BaseClass##Private *clone() const { return new Class##Private(*this); } \ + static void copyIfPossible(QSharedDataPointer &d_ptr, const BaseClass &other) \ + { \ + if (other.type() == ClassType) \ + d_ptr = extract_d(other); \ + else \ + d_ptr = new Class##Private; \ + } + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/places.pri b/src/location/places/places.pri new file mode 100644 index 0000000..1a3796f --- /dev/null +++ b/src/location/places/places.pri @@ -0,0 +1,93 @@ +INCLUDEPATH += places + +PUBLIC_HEADERS += \ + places/placemacro.h \ +#data classes + places/qplace.h \ + places/qplaceattribute.h \ + places/qplacecontactdetail.h \ + places/qplacecategory.h \ + places/qplacecontent.h \ + places/qplacecontentreply.h \ + places/qplaceeditorial.h \ + places/qplaceimage.h \ + places/qplaceicon.h \ + places/qplaceratings.h \ + places/qplacereview.h \ + places/qplacesupplier.h \ + places/qplaceuser.h \ +#result + places/qplacesearchresult.h \ + places/qplaceresult.h \ + places/qplaceproposedsearchresult.h \ +#request classes + places/qplacecontentrequest.h \ + places/qplacematchrequest.h \ + places/qplacesearchrequest.h \ +#reply classes + places/qplacereply.h \ + places/qplacedetailsreply.h \ + places/qplaceidreply.h \ + places/qplacematchreply.h \ + places/qplacesearchreply.h \ + places/qplacesearchsuggestionreply.h \ + places/unsupportedreplies_p.h \ +#manager and engine + places/qplacemanager.h \ + places/qplacemanagerengine.h + +PRIVATE_HEADERS += \ + places/qplace_p.h \ + places/qplaceattribute_p.h \ + places/qplacecategory_p.h \ + places/qplacecontent_p.h \ + places/qplacecontactdetail_p.h \ + places/qplaceeditorial_p.h \ + places/qplaceicon_p.h \ + places/qplaceimage_p.h \ + places/qplaceratings_p.h \ + places/qplaceresult_p.h \ + places/qplaceproposedsearchresult_p.h \ + places/qplacereview_p.h \ + places/qplacesupplier_p.h \ + places/qplacesearchresult_p.h \ + places/qplacereply_p.h \ + places/qplacemanagerengine_p.h \ + places/qplacecontentrequest_p.h \ + places/qplaceuser_p.h + +SOURCES += \ +#data classes + places/qplace.cpp \ + places/qplaceattribute.cpp \ + places/qplacecategory.cpp \ + places/qplacecontactdetail.cpp \ + places/qplacecontent.cpp \ + places/qplacecontentreply.cpp \ + places/qplaceeditorial.cpp \ + places/qplaceuser.cpp \ +#result + places/qplaceicon.cpp \ + places/qplaceimage.cpp \ + places/qplaceratings.cpp \ + places/qplacereview.cpp \ + places/qplaceidreply.cpp \ + places/qplacesupplier.cpp \ +#result + places/qplacesearchresult.cpp \ + places/qplaceresult.cpp \ + places/qplaceproposedsearchresult.cpp \ +#request classes + places/qplacecontentrequest.cpp \ + places/qplacematchrequest.cpp \ + places/qplacesearchrequest.cpp \ +#reply classes + places/qplacereply.cpp \ + places/qplacedetailsreply.cpp \ + places/qplacematchreply.cpp \ + places/qplacesearchreply.cpp \ + places/qplacesearchsuggestionreply.cpp \ +#manager and engine + places/qplacemanager.cpp \ + places/qplacemanagerengine.cpp + diff --git a/src/location/places/qplace.cpp b/src/location/places/qplace.cpp new file mode 100644 index 0000000..82f9f64 --- /dev/null +++ b/src/location/places/qplace.cpp @@ -0,0 +1,721 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplace.h" +#include "qplace_p.h" + +#ifdef QPLACE_DEBUG +#include +#endif + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QPlace + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlace class represents a set of data about a place. + + \input place-definition.qdocinc + + \section2 Contact Information + The contact information of a place is based around a common set of + \l {Contact Types}{contact types}. To retrieve all the phone numbers + of a place, one would do: + + \snippet places/requesthandler.h Phone numbers + + The contact types are string values by design to allow for providers + to introduce new contact types. + + For convenience there are a set of functions which return the value + of the first contact detail of each type. + \list + \li QPlace::primaryPhone() + \li QPlace::primaryEmail() + \li QPlace::primaryWebsite() + \li QPlace::primaryFax() + \endlist + + \section2 Extended Attributes + Places may have additional attributes which are not covered in the formal API. + Similar to contacts attributes are based around a common set of + \l {Attribute Types}{attribute types}. To retrieve an extended attribute one + would do: + \snippet places/requesthandler.h Opening hours + + The attribute types are string values by design to allow providers + to introduce new attribute types. + + \section2 Content + The QPlace object is only meant to be a convenient container to hold + rich content such as images, reviews and so on. Retrieval of content + should happen via QPlaceManager::getPlaceContent(). + + The content is stored as a QPlaceContent::Collection which contains + both the index of the content, as well as the content itself. This enables + developers to check whether a particular item has already been retrieved + and if not, then request that content. + + \section3 Attribution + Places have a field for a rich text attribution string. Some providers + may require that the attribution be shown when a place is displayed + to a user. + + \section2 Categories + Different categories may be assigned to a place to indicate that the place + is associated with those categories. When saving a place, the only meaningful + data is the category id, the rest of the category data is effectively ignored. + The category must already exist before saving the place (it is not possible + to create a new category, assign it to the place, save the place and expect + the category to be created). + + \section2 Saving Caveats + \input place-caveats.qdocinc +*/ + +/*! + Constructs an empty place object. +*/ +QPlace::QPlace() + : d_ptr(new QPlacePrivate()) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QPlace::QPlace(const QPlace &other) + : d_ptr(other.d_ptr) +{ +} + +/*! + Destroys this place. +*/ +QPlace::~QPlace() +{ +} + +/*! + Assigns \a other to this place and returns a reference + to this place. +*/ +QPlace &QPlace::operator= (const QPlace & other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +inline QPlacePrivate *QPlace::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QPlacePrivate *QPlace::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +/*! + Returns true if \a other is equal to this place, + otherwise returns false. +*/ +bool QPlace::operator== (const QPlace &other) const +{ + Q_D(const QPlace); + return *d == *other.d_func(); +} + +/*! + Returns true if \a other is not equal to this place, + otherwise returns false. +*/ +bool QPlace::operator!= (const QPlace &other) const +{ + Q_D(const QPlace); + return !(*d == *other.d_func()); +} + +/*! + Returns categories that this place belongs to. +*/ +QList QPlace::categories() const +{ + Q_D(const QPlace); + return d->categories; +} + +/*! + Sets a single \a category that this place belongs to. +*/ +void QPlace::setCategory(const QPlaceCategory &category) +{ + Q_D(QPlace); + d->categories.clear(); + d->categories.append(category); +} + +/*! + Sets the \a categories that this place belongs to. +*/ +void QPlace::setCategories(const QList &categories) +{ + Q_D(QPlace); + d->categories = categories; +} + +/*! + Returns the location of the place. +*/ +QGeoLocation QPlace::location() const +{ + Q_D(const QPlace); + return d->location; +} + +/*! + Sets the \a location of the place. +*/ +void QPlace::setLocation(const QGeoLocation &location) +{ + Q_D(QPlace); + d->location = location; +} + +/*! + Returns an aggregated rating of the place. +*/ +QPlaceRatings QPlace::ratings() const +{ + Q_D(const QPlace); + return d->ratings; +} + +/*! + Sets the aggregated \a rating of the place. +*/ +void QPlace::setRatings(const QPlaceRatings &rating) +{ + Q_D(QPlace); + d->ratings = rating; +} + +/*! + Returns the supplier of this place. +*/ +QPlaceSupplier QPlace::supplier() const +{ + Q_D(const QPlace); + return d->supplier; +} + +/*! + Sets the supplier of this place to \a supplier. +*/ +void QPlace::setSupplier(const QPlaceSupplier &supplier) +{ + Q_D(QPlace); + d->supplier = supplier; +} + +/*! + Returns a collection of content associated with a place. + This collection is a map with the key being the index of the content object + and value being the content object itself. + + The \a type specifies which kind of content is to be retrieved. +*/ +QPlaceContent::Collection QPlace::content(QPlaceContent::Type type) const +{ + Q_D(const QPlace); + return d->contentCollections.value(type); +} + +/*! + Sets a collection of \a content for the given \a type. +*/ +void QPlace::setContent(QPlaceContent::Type type, const QPlaceContent::Collection &content) +{ + Q_D(QPlace); + d->contentCollections.insert(type, content); +} + +/*! + Adds a collection of \a content of the given \a type to the place. Any index in \a content + that already exists is overwritten. +*/ +void QPlace::insertContent(QPlaceContent::Type type, const QPlaceContent::Collection &content) +{ + Q_D(QPlace); + QMapIterator iter(content); + while (iter.hasNext()) { + iter.next(); + d->contentCollections[type].insert(iter.key(), iter.value()); + } +} + +/*! + Returns the total count of content objects of the given \a type. + This total count indicates how many the manager/provider should have available. + (As opposed to how many objects this place instance is currently assigned). + + A negative count indicates that the total number of items is unknown. + By default the total content count is set to 0. +*/ +int QPlace::totalContentCount(QPlaceContent::Type type) const +{ + Q_D(const QPlace); + return d->contentCounts.value(type, 0); +} + +/*! + Sets the \a totalCount of content objects of the given \a type. +*/ +void QPlace::setTotalContentCount(QPlaceContent::Type type, int totalCount) +{ + Q_D(QPlace); + d->contentCounts.insert(type, totalCount); +} + +/*! + Returns the name of the place. +*/ +QString QPlace::name() const +{ + Q_D(const QPlace); + return d->name; +} + +/*! + Sets the \a name of the place. +*/ +void QPlace::setName(const QString &name) +{ + Q_D(QPlace); + d->name = name; +} + +/*! + Returns the identifier of the place. The place identifier is only meaningful to the QPlaceManager that + generated it and is not transferable between managers. The place identifier is not guaranteed + to be universally unique, but unique for the manager that generated it. +*/ +QString QPlace::placeId() const +{ + Q_D(const QPlace); + return d->placeId; +} + +/*! + Sets the \a identifier of the place. +*/ +void QPlace::setPlaceId(const QString &identifier) +{ + Q_D(QPlace); + d->placeId = identifier; +} + +/*! + Returns a rich text attribution string of the place. Note, some providers may have a + requirement where the attribution must be shown whenever a place is displayed to an end user. +*/ +QString QPlace::attribution() const +{ + Q_D(const QPlace); + return d->attribution; +} + +/*! + Sets the \a attribution string of the place. +*/ +void QPlace::setAttribution(const QString &attribution) +{ + Q_D(QPlace); + d->attribution = attribution; +} + +/*! + Returns the icon of the place. +*/ +QPlaceIcon QPlace::icon() const +{ + Q_D(const QPlace); + return d->icon; +} + +/*! + Sets the \a icon of the place. +*/ +void QPlace::setIcon(const QPlaceIcon &icon) +{ + Q_D(QPlace); + d->icon = icon; +} + +/*! + Returns the primary phone number for this place. This accesses the first contact detail + of the \l {QPlaceContactDetail::Phone}{phone number type}. If no phone details exist, then an empty string is returned. +*/ +QString QPlace::primaryPhone() const +{ + Q_D(const QPlace); + QList phoneNumbers = d->contacts.value(QPlaceContactDetail::Phone); + if (!phoneNumbers.isEmpty()) + return phoneNumbers.at(0).value(); + else + return QString(); +} + +/*! + Returns the primary fax number for this place. This convenience function accesses the first contact + detail of the \l {QPlaceContactDetail::Fax}{fax type}. If no fax details exist, then an empty string is returned. +*/ +QString QPlace::primaryFax() const +{ + Q_D(const QPlace); + QList faxNumbers = d->contacts.value(QPlaceContactDetail::Fax); + if (!faxNumbers.isEmpty()) + return faxNumbers.at(0).value(); + else + return QString(); +} + +/*! + Returns the primary email address for this place. This convenience function accesses the first + contact detail of the \l {QPlaceContactDetail::Email}{email type}. If no email addresses exist, then + an empty string is returned. +*/ +QString QPlace::primaryEmail() const +{ + Q_D(const QPlace); + QList emailAddresses = d->contacts.value(QPlaceContactDetail::Email); + if (!emailAddresses.isEmpty()) + return emailAddresses.at(0).value(); + else + return QString(); +} + +/*! + Returns the primary website of the place. This convenience function accesses the first + contact detail of the \l {QPlaceContactDetail::Website}{website type}. If no websites exist, + then an empty string is returned. +*/ +QUrl QPlace::primaryWebsite() const +{ + Q_D(const QPlace); + QList websites = d->contacts.value(QPlaceContactDetail::Website); + if (!websites.isEmpty()) + return QUrl(websites.at(0).value()); + else + return QString(); +} + +/*! + Returns true if the details of this place have been fetched, + otherwise returns false. +*/ +bool QPlace::detailsFetched() const +{ + Q_D(const QPlace); + return d->detailsFetched; +} + +/*! + Sets whether the details of this place have been \a fetched or not. +*/ +void QPlace::setDetailsFetched(bool fetched) +{ + Q_D(QPlace); + d->detailsFetched = fetched; +} + +/*! + Returns the types of extended attributes that this place has. +*/ +QStringList QPlace::extendedAttributeTypes() const +{ + Q_D(const QPlace); + return d->extendedAttributes.keys(); +} + +/*! + Returns the exteded attribute corresponding to the specified \a attributeType. + If the place does not have that particular attribute type, a default constructed + QPlaceExtendedAttribute is returned. +*/ +QPlaceAttribute QPlace::extendedAttribute(const QString &attributeType) const +{ + Q_D(const QPlace); + return d->extendedAttributes.value(attributeType); +} + +/*! + Assigns an \a attribute of the given \a attributeType to a place. If the given \a attributeType + already exists in the place, then it is overwritten. + + If \a attribute is a default constructed QPlaceAttribute, then the \a attributeType + is removed from the place which means it will not be listed by QPlace::extendedAttributeTypes(). +*/ +void QPlace::setExtendedAttribute(const QString &attributeType, + const QPlaceAttribute &attribute) +{ + Q_D(QPlace); + if (attribute == QPlaceAttribute()) + d->extendedAttributes.remove(attributeType); + else + d->extendedAttributes.insert(attributeType, attribute); +} + +/*! + Remove the attribute of \a attributeType from the place. + + The attribute will no longer be listed by QPlace::extendedAttributeTypes() +*/ +void QPlace::removeExtendedAttribute(const QString &attributeType) +{ + Q_D(QPlace); + d->extendedAttributes.remove(attributeType); +} + +/*! + Returns the type of contact details this place has. + + See QPlaceContactDetail for a list of common \l {QPlaceContactDetail::Email}{contact types}. +*/ +QStringList QPlace::contactTypes() const +{ + Q_D(const QPlace); + return d->contacts.keys(); +} + +/*! + Returns a list of contact details of the specified \a contactType. + + See QPlaceContactDetail for a list of common \l {QPlaceContactDetail::Email}{contact types}. +*/ +QList QPlace::contactDetails(const QString &contactType) const +{ + Q_D(const QPlace); + return d->contacts.value(contactType); +} + +/*! + Sets the contact \a details of a specified \a contactType. + + If \a details is empty, then the \a contactType is removed from the place such + that it is no longer returned by QPlace::contactTypes(). + + See QPlaceContactDetail for a list of common \l {QPlaceContactDetail::Email}{contact types}. +*/ +void QPlace::setContactDetails(const QString &contactType, QList details) +{ + Q_D(QPlace); + if (details.isEmpty()) + d->contacts.remove(contactType); + else + d->contacts.insert(contactType, details); +} + +/*! + Appends a contact \a detail of a specified \a contactType. + + See QPlaceContactDetail for a list of common \l {QPlaceContactDetail::Email}{contact types}. +*/ +void QPlace::appendContactDetail(const QString &contactType, const QPlaceContactDetail &detail) +{ + Q_D(QPlace); + QList details = d->contacts.value(contactType); + details.append(detail); + d->contacts.insert(contactType, details); +} + +/*! + Removes all the contact details of a given \a contactType. + + The \a contactType is no longer returned when QPlace::contactTypes() is called. +*/ +void QPlace::removeContactDetails(const QString &contactType) +{ + Q_D(QPlace); + d->contacts.remove(contactType); +} + +/*! + Sets the visibility of the place to \a visibility. +*/ +void QPlace::setVisibility(QLocation::Visibility visibility) +{ + Q_D(QPlace); + d->visibility = visibility; +} + +/*! + Returns the visibility of the place. + + The default visibility of a new place is set to QtLocatin::Unspecified visibility. + If a place is saved with unspecified visibility the backend chooses an appropriate + default visibility to use when saving. +*/ +QLocation::Visibility QPlace::visibility() const +{ + Q_D(const QPlace); + return d->visibility; +} + +/*! + Returns a boolean indicating whether the all the fields of the place are empty or not. +*/ +bool QPlace::isEmpty() const +{ + Q_D(const QPlace); + return d->isEmpty(); +} + +/******************************************************************************* +*******************************************************************************/ + +QPlacePrivate::QPlacePrivate() +: QSharedData(), visibility(QLocation::UnspecifiedVisibility), detailsFetched(false) +{ +} + +QPlacePrivate::QPlacePrivate(const QPlacePrivate &other) + : QSharedData(other), + categories(other.categories), + location(other.location), + ratings(other.ratings), + supplier(other.supplier), + name(other.name), + placeId(other.placeId), + attribution(other.attribution), + contentCollections(other.contentCollections), + contentCounts(other.contentCounts), + extendedAttributes(other.extendedAttributes), + contacts(other.contacts), + visibility(other.visibility), + icon(other.icon), + detailsFetched(other.detailsFetched) +{ +} + +QPlacePrivate::~QPlacePrivate() {} + +QPlacePrivate &QPlacePrivate::operator= (const QPlacePrivate & other) +{ + if (this == &other) + return *this; + + categories = other.categories; + location = other.location; + ratings = other.ratings; + supplier = other.supplier; + name = other.name; + placeId = other.placeId; + attribution = other.attribution; + contentCollections = other.contentCollections; + contentCounts = other.contentCounts; + contacts = other.contacts; + extendedAttributes = other.extendedAttributes; + visibility = other.visibility; + detailsFetched = other.detailsFetched; + icon = other.icon; + + return *this; +} + +bool QPlacePrivate::operator== (const QPlacePrivate &other) const +{ +#ifdef QPLACE_DEBUG + qDebug() << "categories: " << (categories == other.categories); + qDebug() << "location:" << (location == other.location); + qDebug() << "ratings" << (ratings == other.ratings); + qDebug() << "supplier" << (supplier == other.supplier); + qDebug() << "contentCollections " << (contentCollections == other.contentCollections); + qDebug() << "contentCounts " << (contentCounts == other.contentCounts); + qDebug() << "name " << (name == other.name); + qDebug() << "placeId" << (placeId == other.placeId); + qDebug() << "attribution" << (attribution == other.attribution); + qDebug() << "contacts" << (contacts == other.contacts); + qDebug() << "extendedAttributes" << (extendedAttributes == other.extendedAttributes); + qDebug() << "visibility" << (visibility == other.visibility); + qDebug() << "icon" << (icon == other.icon); +#endif + + return (categories == other.categories + && location == other.location + && ratings == other.ratings + && supplier == other.supplier + && contentCollections == other.contentCollections + && contentCounts == other.contentCounts + && name == other.name + && placeId == other.placeId + && attribution == other.attribution + && contacts == other.contacts + && extendedAttributes == other.extendedAttributes + && visibility == other.visibility + && icon == other.icon + ); +} + + +bool QPlacePrivate::isEmpty() const +{ + return (categories.isEmpty() + && location.isEmpty() + && ratings.isEmpty() + && supplier.isEmpty() + && contentCollections.isEmpty() + && contentCounts.isEmpty() + && name.isEmpty() + && placeId.isEmpty() + && attribution.isEmpty() + && contacts.isEmpty() + && extendedAttributes.isEmpty() + && QLocation::UnspecifiedVisibility == visibility + && icon.isEmpty() + ); +} + +QT_END_NAMESPACE diff --git a/src/location/places/qplace.h b/src/location/places/qplace.h new file mode 100644 index 0000000..a36ce08 --- /dev/null +++ b/src/location/places/qplace.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACE_H +#define QPLACE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QString; +class QPlaceIcon; +class QPlacePrivate; + +class Q_LOCATION_EXPORT QPlace +{ +public: + QPlace(); + QPlace(const QPlace &other); + ~QPlace(); + + QPlace &operator=(const QPlace &other); + + bool operator==(const QPlace &other) const; + bool operator!=(const QPlace &other) const; + + QList categories() const; + void setCategory(const QPlaceCategory &category); + void setCategories(const QList &categories); + QGeoLocation location() const; + void setLocation(const QGeoLocation &location); + QPlaceRatings ratings() const; + void setRatings(const QPlaceRatings &ratings); + QPlaceSupplier supplier() const; + void setSupplier(const QPlaceSupplier &supplier); + + QString attribution() const; + void setAttribution(const QString &attribution); + + QPlaceIcon icon() const; + void setIcon(const QPlaceIcon &icon); + + QPlaceContent::Collection content(QPlaceContent::Type type) const; + void setContent(QPlaceContent::Type type, const QPlaceContent::Collection &content); + void insertContent(QPlaceContent::Type type, const QPlaceContent::Collection &content); + + int totalContentCount(QPlaceContent::Type type) const; + void setTotalContentCount(QPlaceContent::Type type, int total); + + QString name() const; + void setName(const QString &name); + QString placeId() const; + void setPlaceId(const QString &identifier); + + QString primaryPhone() const; + QString primaryFax() const; + QString primaryEmail() const; + QUrl primaryWebsite() const; + + bool detailsFetched() const; + void setDetailsFetched(bool fetched); + + QStringList extendedAttributeTypes() const; + QPlaceAttribute extendedAttribute(const QString &attributeType) const; + void setExtendedAttribute(const QString &attributeType, const QPlaceAttribute &attribute); + void removeExtendedAttribute(const QString &attributeType); + + QStringList contactTypes() const; + QList contactDetails(const QString &contactType) const; + void setContactDetails(const QString &contactType, QList details); + void appendContactDetail(const QString &contactType, const QPlaceContactDetail &detail); + void removeContactDetails(const QString &contactType); + + QLocation::Visibility visibility() const; + void setVisibility(QLocation::Visibility visibility); + + bool isEmpty() const; + +private: + QSharedDataPointer d_ptr; + + inline QPlacePrivate *d_func(); + inline const QPlacePrivate *d_func() const; +}; + +Q_DECLARE_TYPEINFO(QPlace, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlace) + +#endif diff --git a/src/location/places/qplace_p.h b/src/location/places/qplace_p.h new file mode 100644 index 0000000..cb1b13c --- /dev/null +++ b/src/location/places/qplace_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACE_P_H +#define QPLACE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include "qplace.h" +#include "qgeoaddress.h" +#include "qgeorectangle.h" +#include "qgeocoordinate.h" +#include "qplacesupplier.h" +#include + +QT_BEGIN_NAMESPACE + +class QPlacePrivate : public QSharedData +{ +public: + QPlacePrivate(); + QPlacePrivate(const QPlacePrivate &other); + ~QPlacePrivate(); + + QPlacePrivate &operator= (const QPlacePrivate &other); + + bool operator==(const QPlacePrivate &other) const; + + bool isEmpty() const; + + QList categories; + QGeoLocation location; + QPlaceRatings ratings; + QPlaceSupplier supplier; + QString name; + QString placeId; + QString attribution; + + QMap contentCollections; + QMap contentCounts; + + QMap extendedAttributes; + QMap > contacts; + + QLocation::Visibility visibility; + QPlaceIcon icon; + bool detailsFetched; +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/location/places/qplaceattribute.cpp b/src/location/places/qplaceattribute.cpp new file mode 100644 index 0000000..e7812a7 --- /dev/null +++ b/src/location/places/qplaceattribute.cpp @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceattribute_p.h" +#include "qplaceattribute.h" + +QT_USE_NAMESPACE + +template<> QPlaceAttributePrivate *QSharedDataPointer::clone() +{ + return d->clone(); +} + +QPlaceAttributePrivate::QPlaceAttributePrivate(const QPlaceAttributePrivate &other) + : QSharedData(other), + label(other.label), + text(other.text) +{ +} + +bool QPlaceAttributePrivate::operator== (const QPlaceAttributePrivate &other) const +{ + return label == other.label + && text == other.text; +} + +bool QPlaceAttributePrivate::isEmpty() const +{ + return label.isEmpty() + && text.isEmpty(); +} + + +/*! + \class QPlaceAttribute + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceAttribute class represents generic attribute information about a place. + + A QPlaceAttribute instance stores an additional piece of information about a place that is not + otherwise exposed through the QPlace class. A QPlaceAttribute encapsulates a + localized label which describes the attribute and rich text string representing the attribute's value. + Generally, both are intended to be displayed to the end-user as is. + + Some plugins may not support attributes at all, others may only support a + certain set, others still may support a dynamically changing set of attributes + over time or even allow attributes to be arbitrarily defined by the client + application. The attributes could also vary on a place by place basis, + for example one place may have opening hours while another does not. + Consult the \l {Plugin References and Parameters}{plugin + references} for details. + + \section2 Attribute Types + The QPlaceAttribute class defines some constant strings which characterize standard \e {attribute types}. + \list + \li QPlaceAttribute::OpeningHours + \li QPlaceAttribute::Payment + \li QPlaceAttribute::Provider + \endlist + + There is a class of attribute types of the format x_id_ for example x_id_here. + This class of attributes is a set of alternative identifiers of the place, from the specified provider's + perspective. + + The above types are used to access and modify attributes in QPlace via: + \list + \li QPlace::extendedAttribute() + \li QPlace::setExtendedAttribute() + \li QPlace::removeExtendedAttribute() + \li QPlace::removeExtendedAttribute() + \endlist + + The \e {attribute type} is a string type so that providers are able to introduce + new attributes as necessary. Custom attribute types should always be prefixed + by a qualifier in order to avoid conflicts. + + \section3 User Readable and Non-User Readable Attributes + Some attributes may not be intended to be readable by end users, the label field + of such attributes are empty to indicate this fact. +*/ + +/*! + \variable QPlaceAttribute::OpeningHours + Specifies the opening hours. +*/ +const QString QPlaceAttribute::OpeningHours(QLatin1String("openingHours")); + +/*! + \variable QPlaceAttribute::Payment + The constant to specify an attribute that defines the methods of payment. +*/ +const QString QPlaceAttribute::Payment(QLatin1String("payment")); + +/*! + \variable QPlaceAttribute::Provider + The constant to specify an attribute that defines which + provider the place came from. +*/ +const QString QPlaceAttribute::Provider(QLatin1String("x_provider")); + +/*! + Constructs an attribute. +*/ +QPlaceAttribute::QPlaceAttribute() + : d_ptr(new QPlaceAttributePrivate) +{ +} + +/*! + Destroys the attribute. +*/ +QPlaceAttribute::~QPlaceAttribute() +{ +} + +/*! + Creates a copy of \a other. +*/ +QPlaceAttribute::QPlaceAttribute(const QPlaceAttribute &other) + :d_ptr(other.d_ptr) +{ +} + +/*! + Assigns \a other to this attribute and returns a reference to this + attribute. +*/ +QPlaceAttribute &QPlaceAttribute::operator=(const QPlaceAttribute &other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns true if \a other is equal to this attribute, otherwise + returns false. +*/ +bool QPlaceAttribute::operator== (const QPlaceAttribute &other) const +{ + if (d_ptr == other.d_ptr) + return true; + return ( *(d_ptr.constData()) == *(other.d_ptr.constData())); +} + +/*! + Returns true if \a other is not equal to this attribute, + otherwise returns false. +*/ +bool QPlaceAttribute::operator!= (const QPlaceAttribute &other) const +{ + return (!this->operator ==(other)); +} + +/*! + Returns a localized label describing the attribute. +*/ +QString QPlaceAttribute::label() const +{ + return d_ptr->label; +} + +/*! + Sets the \a label of the attribute. +*/ +void QPlaceAttribute::setLabel(const QString &label) +{ + d_ptr->label = label; +} + +/*! + Returns a piece of rich text representing the attribute value. +*/ +QString QPlaceAttribute::text() const +{ + return d_ptr->text; +} + +/*! + Sets the \a text of the attribute. +*/ +void QPlaceAttribute::setText(const QString &text) +{ + d_ptr->text = text; +} + +/*! + Returns a boolean indicating whether the all the fields of the place attribute are empty or not. +*/ +bool QPlaceAttribute::isEmpty() const +{ + return d_ptr->isEmpty(); +} diff --git a/src/location/places/qplaceattribute.h b/src/location/places/qplaceattribute.h new file mode 100644 index 0000000..91a171d --- /dev/null +++ b/src/location/places/qplaceattribute.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEATTRIBUTE_H +#define QPLACEATTRIBUTE_H + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceAttributePrivate; +class Q_LOCATION_EXPORT QPlaceAttribute +{ +public: + static const QString OpeningHours; + static const QString Payment; + static const QString Provider; + + QPlaceAttribute(); + QPlaceAttribute(const QPlaceAttribute &other); + virtual ~QPlaceAttribute(); + + QPlaceAttribute &operator=(const QPlaceAttribute &other); + + bool operator==(const QPlaceAttribute &other) const; + bool operator!=(const QPlaceAttribute &other) const; + + QString label() const; + void setLabel(const QString &label); + + QString text() const; + void setText(const QString &text); + + bool isEmpty() const; + +protected: + QSharedDataPointer d_ptr; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceAttribute) + +#endif diff --git a/src/location/places/qplaceattribute_p.h b/src/location/places/qplaceattribute_p.h new file mode 100644 index 0000000..1f7442e --- /dev/null +++ b/src/location/places/qplaceattribute_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEATTRIBUTE_P_H +#define QPLACEATTRIBUTE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceAttributePrivate : public QSharedData +{ +public: + QPlaceAttributePrivate(){} + QPlaceAttributePrivate(const QPlaceAttributePrivate &other); + virtual ~QPlaceAttributePrivate(){} + + + virtual bool operator== (const QPlaceAttributePrivate &other) const; + virtual QPlaceAttributePrivate *clone() const { return new QPlaceAttributePrivate(*this); } + + bool isEmpty() const; + + QString label; + QString text; +}; + +template<> QPlaceAttributePrivate *QSharedDataPointer::clone(); + +QT_END_NAMESPACE + +#endif + diff --git a/src/location/places/qplacecategory.cpp b/src/location/places/qplacecategory.cpp new file mode 100644 index 0000000..5629631 --- /dev/null +++ b/src/location/places/qplacecategory.cpp @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacecategory.h" +#include "qplacecategory_p.h" + +QT_BEGIN_NAMESPACE + +QPlaceCategoryPrivate::QPlaceCategoryPrivate() +: visibility(QLocation::UnspecifiedVisibility) +{ +} + +QPlaceCategoryPrivate::QPlaceCategoryPrivate(const QPlaceCategoryPrivate &other) +: QSharedData(other), categoryId(other.categoryId), name(other.name), visibility(other.visibility), + icon(other.icon) +{ +} + +QPlaceCategoryPrivate::~QPlaceCategoryPrivate() +{ +} + +QPlaceCategoryPrivate &QPlaceCategoryPrivate::operator=(const QPlaceCategoryPrivate &other) +{ + if (this == &other) + return *this; + + categoryId = other.categoryId; + name = other.name; + icon = other.icon; + return *this; +} + +bool QPlaceCategoryPrivate::isEmpty() const +{ + return categoryId.isEmpty() + && name.isEmpty() + && icon.isEmpty() + && QLocation::UnspecifiedVisibility == visibility; +} + +/*! + \class QPlaceCategory + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceCategory class represents a category that a \l QPlace can be associated with. + + Categories are used to search for places based on the categories they are associated with. The + list/tree of available categories can be obtained from \l QPlaceManager. The + \l QPlaceSearchRequest::setCategories() function can be used to limit the search results to + places with the specified categories. + + If the \l QGeoServiceProvider supports it, categories can be created and removed. This + functionality is available in the \l QPlaceManager class. +*/ + +/*! + \fn bool QPlaceCategory::operator!=(const QPlaceCategory &other) const + + Returns true if \a other is not equal to this category; otherwise returns false. +*/ + +/*! + Constructs a category. +*/ +QPlaceCategory::QPlaceCategory() + : d(new QPlaceCategoryPrivate) +{ +} + +/*! + Constructs a category which is a copy of \a other. +*/ +QPlaceCategory::QPlaceCategory(const QPlaceCategory &other) + :d(other.d) +{ +} + +/*! + Destroys the category. +*/ +QPlaceCategory::~QPlaceCategory() +{ +} + +/*! + Assigns \a other to this category and returns a reference to this category. +*/ +QPlaceCategory &QPlaceCategory::operator =(const QPlaceCategory &other) +{ + if (this == &other) + return *this; + + d = other.d; + return *this; +} + +/*! + Returns true if \a other is equal to this category; otherwise returns false. +*/ +bool QPlaceCategory::operator==(const QPlaceCategory &other) const +{ + return d->categoryId == other.d->categoryId && + d->name == other.d->name && + d->visibility == other.d->visibility && + d->icon == other.d->icon; +} + +/*! + Returns the identifier of the category. The category identifier is a string which uniquely identifies this category + within a particular \l QPlaceManager. The identifier is only meaningful to the QPlaceManager + that generated it and is not transferable between managers. +*/ +QString QPlaceCategory::categoryId() const +{ + return d->categoryId; +} + +/*! + Sets the \a identifier of the category. +*/ +void QPlaceCategory::setCategoryId(const QString &identifier) +{ + d->categoryId = identifier; +} + +/*! + Returns the name of category. +*/ +QString QPlaceCategory::name() const +{ + return d->name; +} + +/*! + Sets the \a name of the category. +*/ +void QPlaceCategory::setName(const QString &name) +{ + d->name = name; +} + +/*! + Sets the \a visibility of the category. +*/ +void QPlaceCategory::setVisibility(QLocation::Visibility visibility) +{ + d->visibility = visibility; +} + +/*! + Returns the visibility of the category. +*/ +QLocation::Visibility QPlaceCategory::visibility() const +{ + return d->visibility; +} + +/*! + Returns the icon associated with the category. +*/ +QPlaceIcon QPlaceCategory::icon() const +{ + return d->icon; +} + +/*! + Sets the \a icon of the category. +*/ +void QPlaceCategory::setIcon(const QPlaceIcon &icon) +{ + d->icon = icon; +} + +/*! + Returns a boolean indicating whether the all the fields of the place category are empty or not. +*/ +bool QPlaceCategory::isEmpty() const +{ + return d->isEmpty(); +} + +QT_END_NAMESPACE diff --git a/src/location/places/qplacecategory.h b/src/location/places/qplacecategory.h new file mode 100644 index 0000000..56a9854 --- /dev/null +++ b/src/location/places/qplacecategory.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECATEGORY_H +#define QPLACECATEGORY_H + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceIcon; + +class QPlaceCategoryPrivate; +class Q_LOCATION_EXPORT QPlaceCategory +{ +public: + QPlaceCategory(); + QPlaceCategory(const QPlaceCategory &other); + + virtual ~QPlaceCategory(); + + QPlaceCategory &operator=(const QPlaceCategory &other); + + bool operator==(const QPlaceCategory &other) const; + bool operator!=(const QPlaceCategory &other) const { + return !(other == *this); + } + + QString categoryId() const; + void setCategoryId(const QString &identifier); + + QString name() const; + void setName(const QString &name); + + QLocation::Visibility visibility() const; + void setVisibility(QLocation::Visibility visibility); + + QPlaceIcon icon() const; + void setIcon(const QPlaceIcon &icon); + + bool isEmpty() const; + +private: + QSharedDataPointer d; +}; + +Q_DECLARE_TYPEINFO(QPlaceCategory, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceCategory) + +#endif // QPLACECATEGORY_H diff --git a/src/location/places/qplacecategory_p.h b/src/location/places/qplacecategory_p.h new file mode 100644 index 0000000..07fec3c --- /dev/null +++ b/src/location/places/qplacecategory_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECATEGORY_P_H +#define QPLACECATEGORY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include "qplaceicon.h" + +QT_BEGIN_NAMESPACE + +class QPlaceCategoryPrivate : public QSharedData +{ +public: + QPlaceCategoryPrivate(); + QPlaceCategoryPrivate(const QPlaceCategoryPrivate &other); + + ~QPlaceCategoryPrivate(); + QPlaceCategoryPrivate &operator= (const QPlaceCategoryPrivate &other); + bool operator==(const QPlaceCategoryPrivate &other) const; + bool isEmpty() const; + + QString categoryId; + QString name; + QLocation::Visibility visibility; + QPlaceIcon icon; +}; + +QT_END_NAMESPACE + +#endif // QPLACECATEGORY_P_H diff --git a/src/location/places/qplacecontactdetail.cpp b/src/location/places/qplacecontactdetail.cpp new file mode 100644 index 0000000..d19fafb --- /dev/null +++ b/src/location/places/qplacecontactdetail.cpp @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacecontactdetail_p.h" +#include "qplacecontactdetail.h" + +QT_USE_NAMESPACE + +QPlaceContactDetailPrivate::QPlaceContactDetailPrivate(const QPlaceContactDetailPrivate &other) + : QSharedData(other), + label(other.label), + value(other.value) +{ +} + +bool QPlaceContactDetailPrivate::operator== (const QPlaceContactDetailPrivate &other) const +{ + return label == other.label + && value == other.value; +} + +/*! +\class QPlaceContactDetail +\brief The QPlaceContactDetail class represents a contact detail such as a phone number or website url. +\inmodule QtLocation + +\ingroup QtLocation-places +\ingroup QtLocation-places-data + +The detail consists of a label and value. The label is a localized string that can be presented +to the end user that describes that detail value which is the actual phone number, email address and so on. + +\section2 Contact Types + +The QPlaceContactDetail class defines some constant strings which characterize standard \e {contact types}. +\list + \li QPlaceContactDetail::Phone + \li QPlaceContactDetail::Email + \li QPlaceContactDetail::Website + \li QPlaceContactDetail::Fax +\endlist + +These types are used to access and modify contact details in QPlace via: +\list + \li QPlace::contactDetails() + \li QPlace::setContactDetails() + \li QPlace::appendContactDetail() + \li QPlace::contactTypes() +\endlist + +The \e {contact type} is intended to be a string type so that providers are able to introduce new contact +types if necessary. +*/ + +/*! + \variable QPlaceContactDetail::Phone + The constant to specify phone contact details +*/ +const QString QPlaceContactDetail::Phone(QLatin1String("phone")); + +/*! + \variable QPlaceContactDetail::Email + The constant to specify email contact details. +*/ +const QString QPlaceContactDetail::Email(QLatin1String("email")); + +/*! + \variable QPlaceContactDetail::Website + The constant used to specify website contact details. +*/ +const QString QPlaceContactDetail::Website(QLatin1String("website")); + +/*! + \variable QPlaceContactDetail::Fax + The constant used to specify fax contact details. +*/ +const QString QPlaceContactDetail::Fax(QLatin1String("fax")); + +/*! + Constructs a contact detail. +*/ +QPlaceContactDetail::QPlaceContactDetail() + : d_ptr(new QPlaceContactDetailPrivate) +{ +} + +/*! + Destroys the contact detail. +*/ +QPlaceContactDetail::~QPlaceContactDetail() +{ +} + +/*! + Creates a copy of \a other. +*/ +QPlaceContactDetail::QPlaceContactDetail(const QPlaceContactDetail &other) + :d_ptr(other.d_ptr) +{ +} + +/*! + Assigns \a other to this contact detail and returns a reference to this + contact detail. +*/ +QPlaceContactDetail &QPlaceContactDetail::operator=(const QPlaceContactDetail &other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns true if \a other is equal to this contact detail, otherwise + returns false. +*/ +bool QPlaceContactDetail::operator== (const QPlaceContactDetail &other) const +{ + if (d_ptr == other.d_ptr) + return true; + return ( *(d_ptr.constData()) == *(other.d_ptr.constData())); +} + +/*! + Returns true if \a other is not equal to this contact detail, + otherwise returns false. +*/ +bool QPlaceContactDetail::operator!= (const QPlaceContactDetail &other) const +{ + return (!this->operator ==(other)); +} + +/*! + Returns a label describing the contact detail. + + The label can potentially be localized. The language is dependent on the entity that sets it, + typically this is the manager from which the places are sourced. + The QPlaceManager::locales() field defines what language is used. +*/ +QString QPlaceContactDetail::label() const +{ + return d_ptr->label; +} + +/*! + Sets the \a label of the contact detail. +*/ +void QPlaceContactDetail::setLabel(const QString &label) +{ + d_ptr->label = label; +} + +/*! + Returns the value of the contact detail. +*/ +QString QPlaceContactDetail::value() const +{ + return d_ptr->value; +} + +/*! + Sets the \a value of this contact detail. +*/ +void QPlaceContactDetail::setValue(const QString &value) +{ + d_ptr->value = value; +} + +/*! + Clears the contact detail. +*/ +void QPlaceContactDetail::clear() +{ + d_ptr->label.clear(); + d_ptr->value.clear(); +} diff --git a/src/location/places/qplacecontactdetail.h b/src/location/places/qplacecontactdetail.h new file mode 100644 index 0000000..5113d83 --- /dev/null +++ b/src/location/places/qplacecontactdetail.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECONTACTDETAIL_H +#define QPLACECONTACTDETAIL_H + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceContactDetailPrivate; +class Q_LOCATION_EXPORT QPlaceContactDetail +{ +public: + static const QString Phone; + static const QString Email; + static const QString Website; + static const QString Fax; + + QPlaceContactDetail(); + QPlaceContactDetail(const QPlaceContactDetail &other); + virtual ~QPlaceContactDetail(); + + QPlaceContactDetail &operator=(const QPlaceContactDetail &other); + + bool operator==(const QPlaceContactDetail &other) const; + bool operator!=(const QPlaceContactDetail &other) const; + + QString label() const; + void setLabel(const QString &label); + + QString value() const; + void setValue(const QString &value); + + void clear(); + +private: + QSharedDataPointer d_ptr; + +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceContactDetail) + +#endif diff --git a/src/location/places/qplacecontactdetail_p.h b/src/location/places/qplacecontactdetail_p.h new file mode 100644 index 0000000..5d4002e --- /dev/null +++ b/src/location/places/qplacecontactdetail_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECONTACTDETAIL_P_H +#define QPLACECONTACTDETAIL_P_H + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceContactDetailPrivate : public QSharedData +{ +public: + QPlaceContactDetailPrivate(){} + QPlaceContactDetailPrivate(const QPlaceContactDetailPrivate &other); + virtual ~QPlaceContactDetailPrivate(){} + + bool operator== (const QPlaceContactDetailPrivate &other) const; + + QString label; + QString value; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacecontent.cpp b/src/location/places/qplacecontent.cpp new file mode 100644 index 0000000..a3334c6 --- /dev/null +++ b/src/location/places/qplacecontent.cpp @@ -0,0 +1,265 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacecontent.h" +#include "qplacecontent_p.h" + +#include + +QT_USE_NAMESPACE + +template<> QPlaceContentPrivate *QSharedDataPointer::clone() +{ + return d->clone(); +} + +inline QPlaceContentPrivate *QPlaceContent::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QPlaceContentPrivate *QPlaceContent::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +bool QPlaceContentPrivate::compare(const QPlaceContentPrivate *other) const +{ + return supplier == other->supplier + && user == other->user + && attribution == other->attribution; +} + +/*! + \class QPlaceContent + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceContent class serves as the base class for rich content types. + + Rich content such as \l {QPlaceImage}{images}, \l {QPlaceReview}{reviews} + and \l {QPlaceEditorial}{editorials} inherit + from the QPlaceContent class which contains common properties such as + an attribution string and content contributor, which may take the form of a + \l {QPlaceUser}{user} and/or \l {QPlaceSupplier}{supplier}. It is possible that + a user from a supplier is contributing content, hence both fields could + be filled in simultaneously. + + \b {Note:} Some providers may \e {require} that the attribution string be displayed + to the user whenever a piece of content is viewed. + + Conversion between QPlaceContent and it's subclasses can be easily performed without + casting. Due to the way it has been implemented, object slicing is not an issue, + the following code is valid: + \snippet places/requesthandler.h Content conversion + + The rich content of a place is typically made available as paginated items. The ability + to convert between QPlaceContent and it's subclasses means that code which handles + the mechanics of paging can be easily shared for each of the sub types. + + At present the QPlaceContent class is not extensible by 3rd parties. + + Note: The Places API considers content objects to be 'retrieve-only' objects. + Submission of content to a provider is not a supported use case. + \sa QPlaceImage, QPlaceReview, QPlaceEditorial +*/ + +/*! + \typedef QPlaceContent::Collection + Synonym for QMap. The key of the map is an \c int representing the + index of the content. The value is the content object itself. + + The \c {Collection} is intended to be a container where content items, that have been retrieved + as pages, can be stored. This enables a developer to skip pages, for example indexes 0-9 may be + stored in the collection, if the user skips to indexes 80-99, these can be stored in the + collection as well. +*/ + +/*! + \enum QPlaceContent::Type + Defines the type of content. + \value NoType + The content object is default constructed, any other content type may be assigned + to this content object. + \value ImageType + The content object is an image. + \value ReviewType + The content object is a review. + \value EditorialType + The content object is an editorial +*/ + +/*! + Constructs an default content object which has no type. +*/ +QPlaceContent::QPlaceContent() + :d_ptr(0) +{ +} + +/*! + Constructs a new copy of \a other. +*/ +QPlaceContent::QPlaceContent(const QPlaceContent &other) + :d_ptr(other.d_ptr) +{ +} + +/*! + Assigns the \a other content object to this and returns a reference + to this content object. +*/ +QPlaceContent &QPlaceContent::operator=(const QPlaceContent &other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Destroys the content object. +*/ +QPlaceContent::~QPlaceContent() +{ +} + +/*! + Returns the content type. +*/ +QPlaceContent::Type QPlaceContent::type() const +{ + if (!d_ptr) + return NoType; + return d_ptr->type(); +} + +/*! + Returns true if this content object is equivalent to \a other, + otherwise returns false. +*/ +bool QPlaceContent::operator==(const QPlaceContent &other) const +{ + // An invalid content object is only equal to another invalid content object + if (!d_ptr) + return !other.d_ptr; + + if (type() != other.type()) + return false; + + return d_ptr->compare(other.d_ptr); +} + +/*! + Returns true if this content object is not equivalent to \a other, + otherwise returns false. +*/ +bool QPlaceContent::operator!=(const QPlaceContent &other) const +{ + return !(*this == other); +} + +/*! + Returns the supplier who contributed this content. +*/ +QPlaceSupplier QPlaceContent::supplier() const +{ + Q_D(const QPlaceContent); + + return d->supplier; +} + +/*! + Sets the \a supplier of the content. +*/ +void QPlaceContent::setSupplier(const QPlaceSupplier &supplier) +{ + Q_D(QPlaceContent); + + d->supplier = supplier; +} + +/*! + Returns the user who contributed this content. +*/ +QPlaceUser QPlaceContent::user() const +{ + Q_D(const QPlaceContent); + return d->user; +} + +/*! + Sets the \a user who contributed this content. +*/ +void QPlaceContent::setUser(const QPlaceUser &user) +{ + Q_D(QPlaceContent); + d->user = user; +} + +/*! + Returns a rich text attribution string. + + \b {Note}: Some providers may require that the attribution + of a particular content item always be displayed + when the content item is shown. +*/ +QString QPlaceContent::attribution() const +{ + Q_D(const QPlaceContent); + return d->attribution; +} + +/*! + Sets a rich text \a attribution string for this content item. +*/ +void QPlaceContent::setAttribution(const QString &attribution) +{ + Q_D(QPlaceContent); + d->attribution = attribution; +} + +/*! + \internal + Constructs a new content object from the given pointer \a d. +*/ +QPlaceContent::QPlaceContent(QPlaceContentPrivate *d) + :d_ptr(d) +{ +} diff --git a/src/location/places/qplacecontent.h b/src/location/places/qplacecontent.h new file mode 100644 index 0000000..87a293d --- /dev/null +++ b/src/location/places/qplacecontent.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QPLACECONTENT_H +#define QPLACECONTENT_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#define Q_DECLARE_CONTENT_D_FUNC(Class) \ + inline Class##Private *d_func(); \ + inline const Class##Private *d_func() const;\ + friend class Class##Private; + +#define Q_DECLARE_CONTENT_COPY_CTOR(Class) \ + Class(const QPlaceContent &other); + +class QPlaceUser; +class QPlaceSupplier; +class QPlaceContentPrivate; +class Q_LOCATION_EXPORT QPlaceContent +{ +public: + typedef QMap Collection; + + enum Type { + NoType = 0, + ImageType, + ReviewType, + EditorialType + }; + + QPlaceContent(); + QPlaceContent(const QPlaceContent &other); + virtual ~QPlaceContent(); + + QPlaceContent &operator=(const QPlaceContent &other); + + bool operator==(const QPlaceContent &other) const; + bool operator!=(const QPlaceContent &other) const; + + QPlaceContent::Type type() const; + + QPlaceSupplier supplier() const; + void setSupplier(const QPlaceSupplier &supplier); + + QPlaceUser user() const; + void setUser(const QPlaceUser &user); + + QString attribution() const; + void setAttribution(const QString &attribution); + +protected: + explicit QPlaceContent(QPlaceContentPrivate *d); + QSharedDataPointer d_ptr; + +private: + inline QPlaceContentPrivate *d_func(); + inline const QPlaceContentPrivate *d_func() const; + + friend class QPlaceContentPrivate; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceContent) +Q_DECLARE_METATYPE(QPlaceContent::Type) + +#endif + diff --git a/src/location/places/qplacecontent_p.h b/src/location/places/qplacecontent_p.h new file mode 100644 index 0000000..23b176b --- /dev/null +++ b/src/location/places/qplacecontent_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECONTENT_P_H +#define QPLACECONTENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qplacecontent.h" +#include "qplacesupplier.h" +#include "qplaceuser.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + + +#define Q_IMPLEMENT_CONTENT_D_FUNC(Class) \ + Class##Private *Class::d_func() { return reinterpret_cast(d_ptr.data()); } \ + const Class##Private *Class::d_func() const { return reinterpret_cast(d_ptr.constData()); } \ + +#define Q_IMPLEMENT_CONTENT_COPY_CTOR(Class) \ + Class::Class(const QPlaceContent &other) : QPlaceContent() { Class##Private::copyIfPossible(d_ptr, other); } + +#define Q_DEFINE_CONTENT_PRIVATE_HELPER(Class, ContentType) \ + virtual QPlaceContentPrivate *clone() const { return new Class##Private(*this); } \ + virtual QPlaceContent::Type type() const {return ContentType;} \ + static void copyIfPossible(QSharedDataPointer &d_ptr, const QPlaceContent &other) \ + { \ + if (other.type() == ContentType) \ + d_ptr = extract_d(other); \ + else \ + d_ptr = new Class##Private; \ + } + +class QPlaceContentPrivate : public QSharedData +{ +public: + QPlaceContentPrivate(){} + virtual ~QPlaceContentPrivate(){} + + virtual bool compare(const QPlaceContentPrivate *other) const; + virtual QPlaceContentPrivate *clone() const = 0; + virtual QPlaceContent::Type type() const = 0; + + /* Helper functions for C++ protection rules */ + static const QSharedDataPointer &extract_d(const QPlaceContent &other) {return other.d_ptr;} + + QPlaceSupplier supplier; + QPlaceUser user; + QString attribution; +}; + +template<> QPlaceContentPrivate *QSharedDataPointer::clone(); + +QT_END_NAMESPACE + +#endif + diff --git a/src/location/places/qplacecontentreply.cpp b/src/location/places/qplacecontentreply.cpp new file mode 100644 index 0000000..f603019 --- /dev/null +++ b/src/location/places/qplacecontentreply.cpp @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacecontentreply.h" +#include "qplacereply_p.h" + +QT_BEGIN_NAMESPACE +class QPlaceContentReplyPrivate : public QPlaceReplyPrivate +{ +public: + QPlaceContentReplyPrivate() + : totalCount(0) + { } + + QPlaceContent::Collection contentCollection; + int totalCount; + QPlaceContentRequest contentRequest; + QPlaceContentRequest previousPageRequest; + QPlaceContentRequest nextPageRequest; +}; + +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +/*! + \class QPlaceContentReply + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-replies + \since 5.6 + + \brief The QPlaceContentReply class manages a content retrieval operation started by an + instance of QPlaceManager. + + See \l {Fetching Rich Content} for an example on how to use a content reply. + \sa QPlaceContentRequest, QPlaceManager +*/ + +/*! + Constructs a content reply with a given \a parent. +*/ +QPlaceContentReply::QPlaceContentReply(QObject *parent) + : QPlaceReply(new QPlaceContentReplyPrivate, parent) +{ +} + +/*! + Destroys the reply. +*/ +QPlaceContentReply::~QPlaceContentReply() +{ +} + + /*! + Returns the collection of content retrieved. +*/ +QPlaceContent::Collection QPlaceContentReply::content() const +{ + Q_D(const QPlaceContentReply); + return d->contentCollection; +} + +/*! + Returns the type of reply. +*/ +QPlaceReply::Type QPlaceContentReply::type() const +{ + return QPlaceReply::ContentReply; +} + +/*! + Sets the \a content of the reply. +*/ +void QPlaceContentReply::setContent(const QPlaceContent::Collection &content) +{ + Q_D(QPlaceContentReply); + d->contentCollection = content; +} + +/*! + Returns the total number of content objects for a place. If the total number of + content objects cannot be counted, a value of -1 is returned. This count only + refers to the total count for a single content type, that is, the content type that + was specified when content was requested with the QPlaceManager. +*/ +int QPlaceContentReply::totalCount() const +{ + Q_D(const QPlaceContentReply); + return d->totalCount; +} + +/*! + Sets the \a total number of content objects for a place. +*/ +void QPlaceContentReply::setTotalCount(int total) +{ + Q_D(QPlaceContentReply); + d->totalCount = total; +} + +/*! + Returns the content request that was used to generate this reply. +*/ +QPlaceContentRequest QPlaceContentReply::request() const +{ + Q_D(const QPlaceContentReply); + return d->contentRequest; +} + +/*! + Returns a place content request that can be used to request the previous batch of place content + results. +*/ +QPlaceContentRequest QPlaceContentReply::previousPageRequest() const +{ + Q_D(const QPlaceContentReply); + return d->previousPageRequest; +} + +/*! + Returns a place content request that can be used to request the next batch of place content + results. +*/ +QPlaceContentRequest QPlaceContentReply::nextPageRequest() const +{ + Q_D(const QPlaceContentReply); + return d->nextPageRequest; +} + +/*! + Sets the content \a request used to generate this this reply. +*/ +void QPlaceContentReply::setRequest(const QPlaceContentRequest &request) +{ + Q_D(QPlaceContentReply); + d->contentRequest = request; +} + +/*! + Sets the place content request that can be used to request the previous batch of place content + results to \a previous. +*/ +void QPlaceContentReply::setPreviousPageRequest(const QPlaceContentRequest &previous) +{ + Q_D(QPlaceContentReply); + d->previousPageRequest = previous; +} + +/*! + Sets the place content request that can be used to request the next batch of place content + results to \a next. +*/ +void QPlaceContentReply::setNextPageRequest(const QPlaceContentRequest &next) +{ + Q_D(QPlaceContentReply); + d->nextPageRequest = next; +} diff --git a/src/location/places/qplacecontentreply.h b/src/location/places/qplacecontentreply.h new file mode 100644 index 0000000..fa96a0e --- /dev/null +++ b/src/location/places/qplacecontentreply.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECONTENTREPLY_H +#define QPLACECONTENTREPLY_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceContentReplyPrivate; +class Q_LOCATION_EXPORT QPlaceContentReply : public QPlaceReply +{ + Q_OBJECT + +public: + explicit QPlaceContentReply(QObject *parent = Q_NULLPTR); + virtual ~QPlaceContentReply(); + + QPlaceReply::Type type() const; + + QPlaceContent::Collection content() const; + + int totalCount() const; + + QPlaceContentRequest request() const; + + QPlaceContentRequest previousPageRequest() const; + QPlaceContentRequest nextPageRequest() const; + +protected: + void setContent(const QPlaceContent::Collection &content); + void setTotalCount(int total); + void setRequest(const QPlaceContentRequest &request); + void setPreviousPageRequest(const QPlaceContentRequest &previous); + void setNextPageRequest(const QPlaceContentRequest &next); + +private: + Q_DISABLE_COPY(QPlaceContentReply) + Q_DECLARE_PRIVATE(QPlaceContentReply) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacecontentrequest.cpp b/src/location/places/qplacecontentrequest.cpp new file mode 100644 index 0000000..5a211ae --- /dev/null +++ b/src/location/places/qplacecontentrequest.cpp @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacecontentrequest_p.h" +#include "qplacecontentrequest.h" +#include "qgeocoordinate.h" + +QT_BEGIN_NAMESPACE + +QPlaceContentRequestPrivate::QPlaceContentRequestPrivate() +: QSharedData(), contentType(QPlaceContent::NoType), limit(-1) +{ +} + +QPlaceContentRequestPrivate::QPlaceContentRequestPrivate(const QPlaceContentRequestPrivate &other) +: QSharedData(other), contentType(other.contentType), placeId(other.placeId), + contentContext(other.contentContext), limit(other.limit) +{ +} + +QPlaceContentRequestPrivate::~QPlaceContentRequestPrivate() +{ +} + +bool QPlaceContentRequestPrivate::operator==(const QPlaceContentRequestPrivate &other) const +{ + return contentType == other.contentType + && limit == other.limit; +} + +void QPlaceContentRequestPrivate::clear() +{ + contentType = QPlaceContent::NoType; + limit = -1; +} + +/*! + \class QPlaceContentRequest + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-requests + \since 5.6 + + \brief The QPlaceContentRequest class represents the parameters of a content request. + + The QPlaceContentRequest class is used in conjunction with a QPlaceManager to + retrieve rich content like images and reviews in a paginated fashion. + The following code would request a set of 5 images from the 10th index: + + \snippet places/requesthandler.h Content request + \dots + \dots + \snippet places/requesthandler.h Content handler + + \sa QPlaceContentReply +*/ + +/*! + Constructs a new request object. +*/ +QPlaceContentRequest::QPlaceContentRequest() + : d_ptr(new QPlaceContentRequestPrivate()) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QPlaceContentRequest::QPlaceContentRequest(const QPlaceContentRequest &other) + : d_ptr(other.d_ptr) +{ +} + +/*! + Destroys the request object +*/ +QPlaceContentRequest::~QPlaceContentRequest() +{ +} + +/*! + Assigns \a other to this content request and returns a reference + to this content request. +*/ +QPlaceContentRequest &QPlaceContentRequest::operator= (const QPlaceContentRequest & other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns true if \a other is equal to this content request, + otherwise returns false. +*/ +bool QPlaceContentRequest::operator== (const QPlaceContentRequest &other) const +{ + Q_D(const QPlaceContentRequest); + return *d == *other.d_func(); +} + +/*! + Returns true if \a other is not equal to this content request, + otherwise returns false. +*/ +bool QPlaceContentRequest::operator!= (const QPlaceContentRequest &other) const +{ + Q_D(const QPlaceContentRequest); + return !(*d == *other.d_func()); +} + +/*! + Returns the type of content to be requested, for example reviews or images +*/ +QPlaceContent::Type QPlaceContentRequest::contentType() const +{ + Q_D(const QPlaceContentRequest); + return d->contentType; +} + +/*! + Sets the \a type of content to be requested. +*/ +void QPlaceContentRequest::setContentType(QPlaceContent::Type type) +{ + Q_D(QPlaceContentRequest); + d->contentType = type; +} + +/*! + Returns the identifier of the place content is to be fetched for. +*/ +QString QPlaceContentRequest::placeId() const +{ + Q_D(const QPlaceContentRequest); + return d->placeId; +} + +/*! + Sets the identifier of the place to fetch content for to \a identifier. +*/ +void QPlaceContentRequest::setPlaceId(const QString &identifier) +{ + Q_D(QPlaceContentRequest); + d->placeId = identifier; +} + +/*! + Returns backend specific additional content context associated with this place content request. +*/ +QVariant QPlaceContentRequest::contentContext() const +{ + Q_D(const QPlaceContentRequest); + return d->contentContext; +} + +/*! + Sets the content context to \a context. + + \note This method is intended to be used by geo service plugins when returning place content + results. + + The content context is used by backends to store additional content context related to the + content request. Other relevant fields should also be filled in. For example, if the content + request is for image content the content type should also be set with \l setContentType(). The + content context allows additional context to be kept which is not directly accessible via the + Qt Location API. + + The content context can be of any type storable in a QVariant. The value of the content context + is not intended to be used directly by applications. +*/ +void QPlaceContentRequest::setContentContext(const QVariant &context) +{ + Q_D(QPlaceContentRequest); + d->contentContext = context; +} + +/*! + Returns the maximum number of content items to retrieve. + + A negative value for limit means that it is undefined. It is left up to the backend + provider to choose an appropriate number of items to return. + + The default limit is -1. +*/ +int QPlaceContentRequest::limit() const +{ + Q_D(const QPlaceContentRequest); + return d->limit; +} + +/*! + Set the maximum number of content items to retrieve to + \a limit. +*/ +void QPlaceContentRequest::setLimit(int limit) +{ + Q_D(QPlaceContentRequest); + d->limit = limit; +} + +/*! + Clears the content request. +*/ +void QPlaceContentRequest::clear() +{ + Q_D(QPlaceContentRequest); + d->clear(); +} + +inline QPlaceContentRequestPrivate *QPlaceContentRequest::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QPlaceContentRequestPrivate *QPlaceContentRequest::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +QT_END_NAMESPACE diff --git a/src/location/places/qplacecontentrequest.h b/src/location/places/qplacecontentrequest.h new file mode 100644 index 0000000..51b88f3 --- /dev/null +++ b/src/location/places/qplacecontentrequest.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECONTENTREQUEST_H +#define QPLACECONTENTREQUEST_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceContentRequestPrivate; + +class Q_LOCATION_EXPORT QPlaceContentRequest +{ +public: + QPlaceContentRequest(); + QPlaceContentRequest(const QPlaceContentRequest &other); + ~QPlaceContentRequest(); + + QPlaceContentRequest &operator=(const QPlaceContentRequest &other); + + bool operator==(const QPlaceContentRequest &other) const; + bool operator!=(const QPlaceContentRequest &other) const; + + QPlaceContent::Type contentType() const; + void setContentType(QPlaceContent::Type type); + + QString placeId() const; + void setPlaceId(const QString &identifier); + + QVariant contentContext() const; + void setContentContext(const QVariant &context); + + int limit() const; + void setLimit(int limit); + + void clear(); + +private: + QSharedDataPointer d_ptr; + inline QPlaceContentRequestPrivate *d_func(); + inline const QPlaceContentRequestPrivate *d_func() const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacecontentrequest_p.h b/src/location/places/qplacecontentrequest_p.h new file mode 100644 index 0000000..d9ba370 --- /dev/null +++ b/src/location/places/qplacecontentrequest_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECONTENTREQUEST_P_H +#define QPLACECONTENTREQUEST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceContentRequestPrivate : public QSharedData +{ +public: + QPlaceContentRequestPrivate(); + QPlaceContentRequestPrivate(const QPlaceContentRequestPrivate &other); + ~QPlaceContentRequestPrivate(); + + QPlaceContentRequestPrivate &operator=(const QPlaceContentRequestPrivate &other); + bool operator==(const QPlaceContentRequestPrivate &other) const; + + void clear(); + + QPlaceContent::Type contentType; + QString placeId; + QVariant contentContext; + int limit; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacedetailsreply.cpp b/src/location/places/qplacedetailsreply.cpp new file mode 100644 index 0000000..84ea9f8 --- /dev/null +++ b/src/location/places/qplacedetailsreply.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacedetailsreply.h" +#include "qplacereply_p.h" + +QT_BEGIN_NAMESPACE +class QPlaceDetailsReplyPrivate : public QPlaceReplyPrivate +{ +public: + QPlaceDetailsReplyPrivate() {} + ~QPlaceDetailsReplyPrivate() {} + QPlace result; +}; + +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +/*! + \class QPlaceDetailsReply + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-replies + \since 5.6 + + \brief The QPlaceDetailsReply class manages a place details fetch operation started by an + instance of QPlaceManager. + + See \l {QML Places API#Fetching Place Details}{Fetching Place Details} for an example on how to use a details reply. + \sa QPlaceManager +*/ + +/*! + Constructs a details reply with a given \a parent. +*/ +QPlaceDetailsReply::QPlaceDetailsReply(QObject *parent) + : QPlaceReply(new QPlaceDetailsReplyPrivate, parent) +{ +} + +/*! + Destroys the details reply. +*/ +QPlaceDetailsReply::~QPlaceDetailsReply() +{ +} + +/*! + Returns the type of reply. +*/ +QPlaceReply::Type QPlaceDetailsReply::type() const +{ + return QPlaceReply::DetailsReply; +} + + /*! + Returns the place that was fetched. +*/ +QPlace QPlaceDetailsReply::place() const +{ + Q_D(const QPlaceDetailsReply); + return d->result; +} + +/*! + Sets the fetched \a place of the reply. +*/ +void QPlaceDetailsReply::setPlace(const QPlace &place) +{ + Q_D(QPlaceDetailsReply); + d->result = place; +} diff --git a/src/location/places/qplacedetailsreply.h b/src/location/places/qplacedetailsreply.h new file mode 100644 index 0000000..09593a1 --- /dev/null +++ b/src/location/places/qplacedetailsreply.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEDETAILSREPLY_H +#define QPLACEDETAILSREPLY_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceDetailsReplyPrivate; +class Q_LOCATION_EXPORT QPlaceDetailsReply : public QPlaceReply +{ + Q_OBJECT +public: + explicit QPlaceDetailsReply(QObject *parent = Q_NULLPTR); + virtual ~QPlaceDetailsReply(); + + QPlaceReply::Type type() const; + + QPlace place() const; + +protected: + void setPlace(const QPlace &place); + +private: + Q_DISABLE_COPY(QPlaceDetailsReply) + Q_DECLARE_PRIVATE(QPlaceDetailsReply) +}; + +QT_END_NAMESPACE + +#endif // QPLACEDETAILSREPLY_H diff --git a/src/location/places/qplaceeditorial.cpp b/src/location/places/qplaceeditorial.cpp new file mode 100644 index 0000000..bd1cb64 --- /dev/null +++ b/src/location/places/qplaceeditorial.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceeditorial.h" +#include "qplaceeditorial_p.h" + +QT_USE_NAMESPACE + +QPlaceEditorialPrivate::QPlaceEditorialPrivate() +: QPlaceContentPrivate() +{ +} + +QPlaceEditorialPrivate::QPlaceEditorialPrivate(const QPlaceEditorialPrivate &other) +: QPlaceContentPrivate(other), text(other.text), contentTitle(other.contentTitle), + language(other.language) +{ +} + +QPlaceEditorialPrivate::~QPlaceEditorialPrivate() +{ +} + +bool QPlaceEditorialPrivate::compare(const QPlaceContentPrivate *other) const +{ + const QPlaceEditorialPrivate *od = static_cast(other); + return QPlaceContentPrivate::compare(other) + && text == od->text + && contentTitle == od->contentTitle + && language == od->language; +} + +/*! + \class QPlaceEditorial + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceEditorial class represents a publisher's article describing a place. + + Each QPlaceEditorial has a title, text and language; in addition to those properties + inherited from QPlaceContent. + + Note: The Places API only supports editorials as 'retrieve-only' objects. Submitting editorials + to a provider is not a supported use case. + + \sa QPlaceContent +*/ + +/*! + Constructs a new editorial object. +*/ +QPlaceEditorial::QPlaceEditorial() +: QPlaceContent(new QPlaceEditorialPrivate) +{ +} + +/*! + Destructor. +*/ +QPlaceEditorial::~QPlaceEditorial() +{ +} + +/*! + \fn QPlaceEditorial::QPlaceEditorial(const QPlaceContent &other) + Constructs a copy of \a other if possible, otherwise constructs a default editorial object. +*/ +Q_IMPLEMENT_CONTENT_COPY_CTOR(QPlaceEditorial) + +Q_IMPLEMENT_CONTENT_D_FUNC(QPlaceEditorial) + +/*! + Returns a textual description of the place. + + Depending upon the provider, the + editorial text could be either rich(HTML based) text or plain text. +*/ +QString QPlaceEditorial::text() const +{ + Q_D(const QPlaceEditorial); + return d->text; +} + +/*! + Sets the \a text of the editorial. +*/ +void QPlaceEditorial::setText(const QString &text) +{ + Q_D(QPlaceEditorial); + d->text = text; +} + +/*! + Returns the title of the editorial. +*/ +QString QPlaceEditorial::title() const +{ + Q_D(const QPlaceEditorial); + return d->contentTitle; +} + +/*! + Sets the \a title of the editorial. +*/ +void QPlaceEditorial::setTitle(const QString &title) +{ + Q_D(QPlaceEditorial); + d->contentTitle = title; +} + +/*! + Returns the language of the editorial. Typically this would be a language code + in the 2 letter ISO 639-1 format. +*/ +QString QPlaceEditorial::language() const +{ + Q_D(const QPlaceEditorial); + return d->language; +} + +/*! + Sets the \a language of the editorial. Typically this would be a language code + in the 2 letter ISO 639-1 format. +*/ +void QPlaceEditorial::setLanguage(const QString &language) +{ + Q_D(QPlaceEditorial); + d->language = language; +} diff --git a/src/location/places/qplaceeditorial.h b/src/location/places/qplaceeditorial.h new file mode 100644 index 0000000..ecfe8db --- /dev/null +++ b/src/location/places/qplaceeditorial.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEEDITORIAL_H +#define QPLACEEDITORIAL_H + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceEditorialPrivate; + +class Q_LOCATION_EXPORT QPlaceEditorial : public QPlaceContent +{ +public: + QPlaceEditorial(); +#ifdef Q_QDOC + QPlaceEditorial(const QPlaceContent &other); +#else + Q_DECLARE_CONTENT_COPY_CTOR(QPlaceEditorial) +#endif + + virtual ~QPlaceEditorial(); + + QString text() const; + void setText(const QString &text); + QString title() const; + void setTitle(const QString &data); + QString language() const; + void setLanguage(const QString &data); + +private: + Q_DECLARE_CONTENT_D_FUNC(QPlaceEditorial) +}; + +QT_END_NAMESPACE + +#endif // QPLACEEDITORIAL_H diff --git a/src/location/places/qplaceeditorial_p.h b/src/location/places/qplaceeditorial_p.h new file mode 100644 index 0000000..f502db7 --- /dev/null +++ b/src/location/places/qplaceeditorial_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEDESCRIPTION_P_H +#define QPLACEDESCRIPTION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include "qplacecontent_p.h" + +QT_BEGIN_NAMESPACE + +class QPlaceEditorialPrivate : public QPlaceContentPrivate +{ +public: + QPlaceEditorialPrivate(); + QPlaceEditorialPrivate(const QPlaceEditorialPrivate &other); + + ~QPlaceEditorialPrivate(); + + bool compare(const QPlaceContentPrivate *other) const; + + Q_DEFINE_CONTENT_PRIVATE_HELPER(QPlaceEditorial, QPlaceContent::EditorialType) + + QString text; + QString contentTitle; + QString language; +}; + +QT_END_NAMESPACE + +#endif // QPLACEDESCRIPTION_P_H diff --git a/src/location/places/qplaceicon.cpp b/src/location/places/qplaceicon.cpp new file mode 100644 index 0000000..c3a2b85 --- /dev/null +++ b/src/location/places/qplaceicon.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceicon.h" +#include "qplaceicon_p.h" +#include "qplacemanager.h" +#include "qplacemanagerengine.h" + +QT_USE_NAMESPACE + +QPlaceIconPrivate::QPlaceIconPrivate() + : QSharedData(), manager(0) +{ +} + +QPlaceIconPrivate::QPlaceIconPrivate(const QPlaceIconPrivate &other) + : QSharedData(other), + manager(other.manager), + parameters(other.parameters) +{ +} + +QPlaceIconPrivate::~QPlaceIconPrivate() +{ +} + +QPlaceIconPrivate &QPlaceIconPrivate::operator=(const QPlaceIconPrivate &other) +{ + if (this == &other) + return *this; + + manager = other.manager; + parameters = other.parameters; + + return *this; +} + +bool QPlaceIconPrivate::operator == (const QPlaceIconPrivate &other) const +{ + return manager == other.manager + && parameters == other.parameters; +} + +/*! + \class QPlaceIcon + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceIcon class represents an icon. + + The typical usage of an icon is to use the url() function to specify + a preferred icon size. + + \snippet places/requesthandler.h icon + + The icons are typically backend dependent, if a manager backend does not support a given size, the URL of the icon that most + closely matches those parameters is returned. + + The icon class also has a key-value set of parameters. The precise key one + needs to use depends on the \l {Qt Location#Plugin References and Parameters}{plugin} + being used. These parameters influence which icon URL is returned by + the manager and may also be used to specify icon URL locations when + saving icons. + + If there is only ever one image for an icon, then QPlaceIcon::SingleUrl can be used as a parameter + key with a QUrl as the associated value. If this key is set, then the url() function will always return the specified URL + and not defer to any manager. +*/ + +/*! + \variable QPlaceIcon::SingleUrl + \brief Parameter key for an icon that only has a single image URL. + + The parameter value to be used with this key is a QUrl. An icon with this parameter set will + always return the specified URL regardless of the requested size when url() is called. +*/ +const QString QPlaceIcon::SingleUrl(QLatin1String("singleUrl")); + +/*! + Constructs an icon. +*/ +QPlaceIcon::QPlaceIcon() + : d(new QPlaceIconPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QPlaceIcon::QPlaceIcon(const QPlaceIcon &other) + : d(other.d) +{ +} + +/*! + Destroys the icon. +*/ +QPlaceIcon::~QPlaceIcon() +{ +} + +/*! + Assigns \a other to this icon and returns a reference to this icon. +*/ +QPlaceIcon &QPlaceIcon::operator=(const QPlaceIcon &other) +{ + if (this == &other) + return *this; + + d = other.d; + return *this; +} + +/*! + Returns true if this icon is equal to \a other, otherwise returns false. +*/ +bool QPlaceIcon::operator==(const QPlaceIcon &other) const +{ + return *d == *(other.d); +} + +/*! + \fn QPlaceIcon::operator!=(const QPlaceIcon &other) const + + Returns true if \a other is not equal to this icon, otherwise returns false. +*/ + +/*! + Returns an icon URL according to the given \a size. + + If no manager has been assigned to the icon, and the parameters do not contain the QPlaceIcon::SingleUrl key, a default constructed QUrl + is returned. +*/ +QUrl QPlaceIcon::url(const QSize &size) const +{ + if (d->parameters.contains(QPlaceIcon::SingleUrl)) { + QVariant value = d->parameters.value(QPlaceIcon::SingleUrl); + if (value.type() == QVariant::Url) + return value.toUrl(); + else if (value.type() == QVariant::String) + return QUrl::fromUserInput(value.toString()); + + return QUrl(); + } + + if (!d->manager) + return QUrl(); + + return d->manager->d->constructIconUrl(*this, size); +} + +/*! + Returns a set of parameters for the icon that are manager/plugin specific. + These parameters are used by the manager to return the appropriate + URL when url() is called and to specify locations to save to + when saving icons. + + Consult the \l {Qt Location#Plugin References and Parameters}{plugin documentation} + for what parameters are supported and how they should be used. +*/ +QVariantMap QPlaceIcon::parameters() const +{ + return d->parameters; +} + +/*! + Sets the parameters of the icon to \a parameters. +*/ +void QPlaceIcon::setParameters(const QVariantMap ¶meters) +{ + d->parameters = parameters; +} + +/*! + Returns the manager that this icon is associated with. +*/ +QPlaceManager *QPlaceIcon::manager() const +{ + return d->manager; +} + +/*! + Sets the \a manager that this icon is associated with. The icon does not take + ownership of the pointer. +*/ +void QPlaceIcon::setManager(QPlaceManager *manager) +{ + d->manager = manager; +} + +/*! + Returns a boolean indicating whether the all the fields of the icon are empty or not. +*/ +bool QPlaceIcon::isEmpty() const +{ + return (d->manager == 0 + && d->parameters.isEmpty()); +} diff --git a/src/location/places/qplaceicon.h b/src/location/places/qplaceicon.h new file mode 100644 index 0000000..c603572 --- /dev/null +++ b/src/location/places/qplaceicon.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEICON_H +#define QPLACEICON_H + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceManager; + +class QPlaceIconPrivate; +class Q_LOCATION_EXPORT QPlaceIcon +{ +public: + static const QString SingleUrl; + + QPlaceIcon(); + QPlaceIcon(const QPlaceIcon &other); + + ~QPlaceIcon(); + + QPlaceIcon &operator=(const QPlaceIcon &other); + bool operator == (const QPlaceIcon &other) const; + bool operator != (const QPlaceIcon &other) const { + return !(*this == other); + } + + QUrl url(const QSize &size = QSize()) const; + + QPlaceManager *manager() const; + void setManager(QPlaceManager *manager); + + QVariantMap parameters() const; + void setParameters(const QVariantMap ¶meters); + + bool isEmpty() const; + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceIcon) + +#endif diff --git a/src/location/places/qplaceicon_p.h b/src/location/places/qplaceicon_p.h new file mode 100644 index 0000000..8f94102 --- /dev/null +++ b/src/location/places/qplaceicon_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEICON_P_H +#define QPLACEICON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceManager; +class QPlaceIconPrivate: public QSharedData +{ +public: + QPlaceIconPrivate(); + QPlaceIconPrivate(const QPlaceIconPrivate &other); + ~QPlaceIconPrivate(); + + QPlaceIconPrivate &operator=(const QPlaceIconPrivate &other); + bool operator == (const QPlaceIconPrivate &other) const; + + QPlaceManager *manager; + QVariantMap parameters; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplaceidreply.cpp b/src/location/places/qplaceidreply.cpp new file mode 100644 index 0000000..7fb8bbe --- /dev/null +++ b/src/location/places/qplaceidreply.cpp @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceidreply.h" +#include "qplacereply_p.h" + +QT_BEGIN_NAMESPACE +class QPlaceIdReplyPrivate : public QPlaceReplyPrivate +{ +public: + QPlaceIdReplyPrivate(QPlaceIdReply::OperationType operationType) + : operationType(operationType) {} + ~QPlaceIdReplyPrivate() {} + QString id; + QPlaceIdReply::OperationType operationType; +}; + +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +/*! + \class QPlaceIdReply + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-replies + \since 5.6 + + \brief The QPlaceIdReply class manages operations which return an identifier such as + saving and removal operations of places and categories. + + The QPlaceIdReply can be considered a multipurpose reply in that it can + be used to save places, save categories, remove places and remove categories. + In each case it returns an identifier of the place or category that was added, modified or removed. + + See \l {Saving a place cpp}{Saving a place} for an example of how to use an identifier reply. + \sa QPlaceManager +*/ + +/*! + \enum QPlaceIdReply::OperationType + Defines the type of operation that was used to generate this reply. + \value SavePlace The reply was created for a save place operation + \value RemovePlace The reply was created for a remove place operation. + \value SaveCategory The reply was created for a save category operation + \value RemoveCategory The reply was created for a remove category operation. +*/ + +/*! + Constructs a reply which contains the identifier of the object operated upon. The reply is for the given \a operationType and with \a parent. +*/ +QPlaceIdReply::QPlaceIdReply(QPlaceIdReply::OperationType operationType, QObject *parent) + : QPlaceReply(new QPlaceIdReplyPrivate(operationType), parent) {} + +/*! + Destroys the reply. +*/ +QPlaceIdReply::~QPlaceIdReply() +{ +} + +/*! + Returns the type of reply. +*/ +QPlaceReply::Type QPlaceIdReply::type() const +{ + return QPlaceReply::IdReply; +} + +/*! + Returns the operation type of the reply. This means whether this + identifier reply was for a save place operation, + remove category operation and so on. +*/ +QPlaceIdReply::OperationType QPlaceIdReply::operationType() const +{ + Q_D(const QPlaceIdReply); + return d->operationType; +} + +/*! + Returns the relevant identifier for the operation. For example for a save place operation, + the identifier is that of the saved place. For a category removal operation, + it is the identifier of the category that was removed. +*/ +QString QPlaceIdReply::id() const +{ + Q_D(const QPlaceIdReply); + return d->id; +} + +/*! + Sets the \a identifier of the reply. +*/ +void QPlaceIdReply::setId(const QString &identifier) +{ + Q_D(QPlaceIdReply); + d->id = identifier; +} diff --git a/src/location/places/qplaceidreply.h b/src/location/places/qplaceidreply.h new file mode 100644 index 0000000..ea44828 --- /dev/null +++ b/src/location/places/qplaceidreply.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEIDREPLY_H +#define QPLACEIDREPLY_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceIdReplyPrivate; +class Q_LOCATION_EXPORT QPlaceIdReply : public QPlaceReply +{ + Q_OBJECT +public: + enum OperationType + { + SavePlace, + SaveCategory, + RemovePlace, + RemoveCategory + }; + + explicit QPlaceIdReply(OperationType operationType, QObject *parent = Q_NULLPTR); + virtual ~QPlaceIdReply(); + + QPlaceReply::Type type() const; + OperationType operationType() const; + + QString id() const; + +protected: + void setId(const QString &identifier); +private: + Q_DISABLE_COPY(QPlaceIdReply) + Q_DECLARE_PRIVATE(QPlaceIdReply) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplaceimage.cpp b/src/location/places/qplaceimage.cpp new file mode 100644 index 0000000..f3dda15 --- /dev/null +++ b/src/location/places/qplaceimage.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceimage.h" +#include "qplaceimage_p.h" + +QT_USE_NAMESPACE + +QPlaceImagePrivate::QPlaceImagePrivate() : QPlaceContentPrivate() +{ +} + +QPlaceImagePrivate::QPlaceImagePrivate(const QPlaceImagePrivate &other) + : QPlaceContentPrivate(other) +{ + url = other.url; + id = other.id; + mimeType = other.mimeType; +} + +QPlaceImagePrivate::~QPlaceImagePrivate() +{ +} + +bool QPlaceImagePrivate::compare(const QPlaceContentPrivate *other) const +{ + const QPlaceImagePrivate *od = static_cast(other); + return QPlaceContentPrivate::compare(other) + && url == od->url && id == od->id && mimeType == od->mimeType; +} + +/*! + \class QPlaceImage + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceImage class represents a reference to an image. + + Each QPlaceImage represents a set of metadata about an image such as it's + url, identifier and MIME type. These are properties in addition to those provided + by QPlaceContent. + + Note: The Places API only supports images as 'retrieve-only' objects. Submitting + images to a provider is not a supported use case. + \sa QPlaceContent +*/ + +/*! + Constructs an new QPlaceImage. +*/ +QPlaceImage::QPlaceImage() + : QPlaceContent(new QPlaceImagePrivate) +{ +} + +/*! + Destructor. +*/ +QPlaceImage::~QPlaceImage() +{ +} + +/*! + \fn QPlaceImage::QPlaceImage(const QPlaceContent &other) + Constructs a copy of \a other if possible, otherwise constructs a default image. +*/ + +Q_IMPLEMENT_CONTENT_COPY_CTOR(QPlaceImage) + +Q_IMPLEMENT_CONTENT_D_FUNC(QPlaceImage) + +/*! + Returns the image's url. +*/ +QUrl QPlaceImage::url() const +{ + Q_D(const QPlaceImage); + return d->url; +} + +/*! + Sets the image's \a url. +*/ +void QPlaceImage::setUrl(const QUrl &url) +{ + Q_D(QPlaceImage); + d->url = url; +} + +/*! + Returns the image's identifier. +*/ +QString QPlaceImage::imageId() const +{ + Q_D(const QPlaceImage); + return d->id; +} + +/*! + Sets image's \a identifier. +*/ +void QPlaceImage::setImageId(const QString &identifier) +{ + Q_D(QPlaceImage); + d->id = identifier; +} + +/*! + Returns the image's MIME type. +*/ +QString QPlaceImage::mimeType() const +{ + Q_D(const QPlaceImage); + return d->mimeType; +} + +/*! + Sets image's MIME \a type. +*/ +void QPlaceImage::setMimeType(const QString &type) +{ + Q_D(QPlaceImage); + d->mimeType = type; +} diff --git a/src/location/places/qplaceimage.h b/src/location/places/qplaceimage.h new file mode 100644 index 0000000..732d3de --- /dev/null +++ b/src/location/places/qplaceimage.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEIMAGE_H +#define QPLACEIMAGE_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceImagePrivate; +class QUrl; + +class Q_LOCATION_EXPORT QPlaceImage : public QPlaceContent +{ +public: + QPlaceImage(); +#ifdef Q_QDOC + QPlaceImage(const QPlaceContent &other); +#else + Q_DECLARE_CONTENT_COPY_CTOR(QPlaceImage) +#endif + + virtual ~QPlaceImage(); + + QUrl url() const; + void setUrl(const QUrl &url); + + QString imageId() const; + void setImageId(const QString &identifier); + + QString mimeType() const; + void setMimeType(const QString &data); + +private: + Q_DECLARE_CONTENT_D_FUNC(QPlaceImage) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplaceimage_p.h b/src/location/places/qplaceimage_p.h new file mode 100644 index 0000000..4030b08 --- /dev/null +++ b/src/location/places/qplaceimage_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEIMAGE_P_H +#define QPLACEIMAGE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include "qplaceimage.h" +#include "qplacecontent_p.h" + +QT_BEGIN_NAMESPACE + +class QPlaceImagePrivate : public QPlaceContentPrivate +{ +public: + QPlaceImagePrivate(); + QPlaceImagePrivate(const QPlaceImagePrivate &other); + + ~QPlaceImagePrivate(); + + bool compare(const QPlaceContentPrivate *other) const; + + Q_DEFINE_CONTENT_PRIVATE_HELPER(QPlaceImage, QPlaceContent::ImageType) + + QUrl url; + QString id; + QString mimeType; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacemanager.cpp b/src/location/places/qplacemanager.cpp new file mode 100644 index 0000000..e78489f --- /dev/null +++ b/src/location/places/qplacemanager.cpp @@ -0,0 +1,489 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacemanager.h" +#include "qplacemanagerengine.h" +#include "qplacemanagerengine_p.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QPlaceManager + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-manager + \since 5.6 + + \brief The QPlaceManager class provides the interface which allows clients to access + places stored in a particular backend. + + The following table gives an overview of the functionality provided by the QPlaceManager + \table + \header + \li Functionality + \li Description + \row + \li Searching for places + \li Using set of parameters such as a search term and search area, relevant places + can be returned to the user. + \row + \li Categories + \li Places can be classified as belonging to different categories. The + manager supports access to these categories. + \row + \li Search term suggestions + \li Given a partially complete search term, a list of potential + search terms can be given. + \row + \li Recommendations + \li Given an existing place, a set of similar recommended places can + be suggested to the user. + \row + \li Rich Content + \li Rich content such as images, reviews etc can be retrieved in a paged + fashion. + \row + \li Place or Category management + \li Places and categories may be saved and removed. It is possible + for notifications to be given when this happens. + \row + \li Localization + \li Different locales may be specified to return place + data in different languages. + \endtable + + \section1 Obtaining a QPlaceManager Instance + Creation of a QPlaceManager is facilitated by the QGeoServiceProvider. + See \l {Initializing a manager} for an example on how to create a manager. + + + \section1 Asynchronous Interface + The QPlaceManager class provides an abstraction of the datastore which contains place information. + The functions provided by the QPlaceManager and primarily asynchronous and follow + a request-reply model. Typically a request is given to the manager, consisting + of a various set of parameters and a reply object is created. The reply object + has a signal to notify when the request is done, and once completed, the reply + contains the results of the request, along with any errors that occurred, if any. + + An asynchronous request is generally handled as follows: + \snippet places/requesthandler.h Simple search + \dots + \dots + \snippet places/requesthandler.h Simple search handler + + See \l {Common Operations} for a list of examples demonstrating how the QPlaceManger + is used. + + \section1 Category Initialization + Sometime during startup of an application, the initializeCategories() function should + be called to setup the categories. Initializing the categories enables the usage of the + following functions: + + \list + \li QPlaceManager::childCategories() + \li QPlaceManager::category() + \li QPlaceManager::parentCategoryId() + \li QPlaceManager::childCategoryIds(); + \endlist + + If the categories need to be refreshed or reloaded, the initializeCategories() function + may be called again. + +*/ + +/*! + Constructs a new manager with the specified \a parent and with the + implementation provided by \a engine. + + This constructor is used internally by QGeoServiceProviderFactory. Regular + users should acquire instances of QGeoRoutingManager with + QGeoServiceProvider::routingManager(); +*/ +QPlaceManager::QPlaceManager(QPlaceManagerEngine *engine, QObject *parent) + : QObject(parent), d(engine) +{ + if (d) { + d->setParent(this); + d->d_ptr->manager = this; + + qRegisterMetaType(); + + connect(d, SIGNAL(finished(QPlaceReply*)), this, SIGNAL(finished(QPlaceReply*))); + connect(d, SIGNAL(error(QPlaceReply*,QPlaceReply::Error)), + this, SIGNAL(error(QPlaceReply*,QPlaceReply::Error))); + + connect(d, SIGNAL(placeAdded(QString)), + this, SIGNAL(placeAdded(QString)), Qt::QueuedConnection); + connect(d, SIGNAL(placeUpdated(QString)), + this, SIGNAL(placeUpdated(QString)), Qt::QueuedConnection); + connect(d, SIGNAL(placeRemoved(QString)), + this, SIGNAL(placeRemoved(QString)), Qt::QueuedConnection); + + connect(d, SIGNAL(categoryAdded(QPlaceCategory,QString)), + this, SIGNAL(categoryAdded(QPlaceCategory,QString))); + connect(d, SIGNAL(categoryUpdated(QPlaceCategory,QString)), + this, SIGNAL(categoryUpdated(QPlaceCategory,QString))); + connect(d, SIGNAL(categoryRemoved(QString,QString)), + this, SIGNAL(categoryRemoved(QString,QString))); + connect(d, SIGNAL(dataChanged()), + this, SIGNAL(dataChanged()), Qt::QueuedConnection); + } else { + qFatal("The place manager engine that was set for this place manager was NULL."); + } +} + +/*! + Destroys the manager. This destructor is used internally by QGeoServiceProvider + and should never need to be called in application code. +*/ +QPlaceManager::~QPlaceManager() +{ + delete d; +} + +/*! + Returns the name of the manager +*/ +QString QPlaceManager::managerName() const +{ + return d->managerName(); +} + +/*! + Returns the manager version. +*/ +int QPlaceManager::managerVersion() const +{ + return d->managerVersion(); +} + +/*! + Retrieves a details of place corresponding to the given \a placeId. + + See \l {QML Places API#Fetching Place Details}{Fetching Place Details} for an example of usage. +*/ +QPlaceDetailsReply *QPlaceManager::getPlaceDetails(const QString &placeId) const +{ + return d->getPlaceDetails(placeId); +} + +/*! + Retrieves content for a place according to the parameters specified in \a request. + + See \l {Fetching Rich Content} for an example of usage. +*/ +QPlaceContentReply *QPlaceManager::getPlaceContent(const QPlaceContentRequest &request) const +{ + return d->getPlaceContent(request); +} + +/*! + Searches for places according to the parameters specified in \a request. + + See \l {Discovery/Search} for an example of usage. +*/ +QPlaceSearchReply *QPlaceManager::search(const QPlaceSearchRequest &request) const +{ + return d->search(request); +} + +/*! + Requests a set of search term suggestions according to the parameters specified in \a request. + The \a request can hold the incomplete search term, along with other data such + as a search area to narrow down relevant results. + + See \l {Search Suggestions} for an example of usage. +*/ +QPlaceSearchSuggestionReply *QPlaceManager::searchSuggestions(const QPlaceSearchRequest &request) const +{ + return d->searchSuggestions(request); +} + +/*! + Saves a specified \a place. + + See \l {Saving a place cpp} for an example of usage. +*/ +QPlaceIdReply *QPlaceManager::savePlace(const QPlace &place) +{ + return d->savePlace(place); +} + +/*! + Removes the place corresponding to \a placeId from the manager. + + See \l {Removing a place cpp} for an example of usage. +*/ +QPlaceIdReply *QPlaceManager::removePlace(const QString &placeId) +{ + return d->removePlace(placeId); +} + +/*! + Saves a \a category that is a child of the category specified by \a parentId. + An empty \a parentId means \a category is saved as a top level category. + + See \l {Saving a category} for an example of usage. +*/ +QPlaceIdReply *QPlaceManager::saveCategory(const QPlaceCategory &category, const QString &parentId) +{ + return d->saveCategory(category, parentId); +} + +/*! + Removes the category corresponding to \a categoryId from the manager. + + See \l {Removing a category} for an example of usage. +*/ +QPlaceIdReply *QPlaceManager::removeCategory(const QString &categoryId) +{ + return d->removeCategory(categoryId); +} + +/*! + Initializes the categories of the manager. + + See \l {Using Categories} for an example of usage. +*/ +QPlaceReply *QPlaceManager::initializeCategories() +{ + return d->initializeCategories(); +} + +/*! + Returns the parent category identifier of the category corresponding to \a categoryId. +*/ +QString QPlaceManager::parentCategoryId(const QString &categoryId) const +{ + return d->parentCategoryId(categoryId); +} + +/*! + Returns the child category identifiers of the category corresponding to \a parentId. + If \a parentId is empty then all top level category identifiers are returned. +*/ +QStringList QPlaceManager::childCategoryIds(const QString &parentId) const +{ + return d->childCategoryIds(parentId); +} + +/*! + Returns the category corresponding to the given \a categoryId. +*/ +QPlaceCategory QPlaceManager::category(const QString &categoryId) const +{ + return d->category(categoryId); +} + +/*! + Returns a list of categories that are children of the category corresponding to \a parentId. + If \a parentId is empty, all the top level categories are returned. +*/ +QList QPlaceManager::childCategories(const QString &parentId) const +{ + return d->childCategories(parentId); +} + +/*! + Returns a list of preferred locales. The locales are used as a hint to the manager for what language + place and category details should be returned in. + + If the first specified locale cannot be accommodated, the manager falls back to the next and so forth. + Some manager backends may not support a set of locales which are rigidly defined. An arbitrary + example is that some places in France could have French and English localizations, while + certain areas in America may only have the English localization available. In this example, + the set of supported locales is context dependent on the search location. + + If the manager cannot accommodate any of the preferred locales, the manager falls + back to using a supported language that is backend specific. + + Support for locales may vary from provider to provider. For those that do support it, + by default, the global default locale is set as the manager's only locale. + + For managers that do not support locales, the locale list is always empty. +*/ +QList QPlaceManager::locales() const +{ + return d->locales(); +} + +/*! + Convenience function which sets the manager's list of preferred locales + to a single \a locale. +*/ +void QPlaceManager::setLocale(const QLocale &locale) +{ + QList locales; + locales << locale; + d->setLocales(locales); +} + +/*! + Set the list of preferred \a locales. +*/ +void QPlaceManager::setLocales(const QList &locales) +{ + d->setLocales(locales); +} + +/*! + Returns a pruned or modified version of the \a original place + which is suitable to be saved into this manager. + + Only place details that are supported by this manager is + present in the modified version. Manager specific data such + as the place id, is not copied over from the \a original. +*/ +QPlace QPlaceManager::compatiblePlace(const QPlace &original) +{ + return d->compatiblePlace(original); +} + +/*! + Returns a reply which contains a list of places which correspond/match those + specified in the \a request. The places specified in the request come from a + different manager. +*/ +QPlaceMatchReply *QPlaceManager::matchingPlaces(const QPlaceMatchRequest &request) const +{ + return d->matchingPlaces(request); +} + +/*! + \fn void QPlaceManager::finished(QPlaceReply *reply) + + This signal is emitted when \a reply has finished processing. + + If reply->error() equals QPlaceReply::NoError then the processing + finished successfully. + + This signal and QPlaceReply::finished() will be emitted at the same time. + + \note Do not delete the \a reply object in the slot connected to this signal. + Use deleteLater() instead. +*/ + +/*! + \fn void QPlaceManager::error(QPlaceReply *reply, QPlaceReply::Error error, const QString &errorString) + + This signal is emitted when an error has been detected in the processing of + \a reply. The QPlaceManager::finished() signal will probably follow. + + The error will be described by the error code \a error. If \a errorString is + not empty it will contain a textual description of the error meant for developers + and not end users. + + This signal and QPlaceReply::error() will be emitted at the same time. + + \note Do not delete the \a reply object in the slot connected to this signal. + Use deleteLater() instead. +*/ + +/*! + \fn void QPlaceManager::placeAdded(const QString &placeId) + + This signal is emitted if a place has been added to the manager engine's datastore. + The particular added place is specified by \a placeId. + + This signal is only emitted by managers that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManager::placeUpdated(const QString &placeId) + + This signal is emitted if a place has been modified in the manager's datastore. + The particular modified place is specified by \a placeId. + + This signal is only emitted by managers that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManager::placeRemoved(const QString &placeId) + + This signal is emitted if a place has been removed from the manager's datastore. + The particular place that has been removed is specified by \a placeId. + + This signal is only emitted by managers that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManager::categoryAdded(const QPlaceCategory &category, const QString &parentId) + + This signal is emitted if a \a category has been added to the manager's datastore. + The parent of the \a category is specified by \a parentId. + + This signal is only emitted by managers that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManager::categoryUpdated(const QPlaceCategory &category, const QString &parentId) + + This signal is emitted if a \a category has been modified in the manager's datastore. + The parent of the modified category is specified by \a parentId. + + This signal is only emitted by managers that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManager::categoryRemoved(const QString &categoryId, const QString &parentId) + + This signal is emitted when the category corresponding to \a categoryId has + been removed from the manager's datastore. The parent of the removed category + is specified by \a parentId. + + This signal is only emitted by managers that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn QPlaceManager::dataChanged() + This signal is emitted by the manager if there are large scale changes to its + underlying datastore and the manager considers these changes radical enough + to require clients to reload all data. + + If the signal is emitted, no other signals will be emitted for the associated changes. + + This signal is only emitted by managers that support the QPlaceManager::NotificationsFeature. +*/ + +QT_END_NAMESPACE diff --git a/src/location/places/qplacemanager.h b/src/location/places/qplacemanager.h new file mode 100644 index 0000000..bd4b167 --- /dev/null +++ b/src/location/places/qplacemanager.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEMANAGER_H +#define QPLACEMANAGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceManagerEngine; +class QPlaceSearchRequest; +class QPlaceSearchReply; + +class Q_LOCATION_EXPORT QPlaceManager : public QObject +{ + Q_OBJECT +public: + ~QPlaceManager(); + + QString managerName() const; + int managerVersion() const; + + QPlaceDetailsReply *getPlaceDetails(const QString &placeId) const; + + QPlaceContentReply *getPlaceContent(const QPlaceContentRequest &request) const; + + QPlaceSearchReply *search(const QPlaceSearchRequest &query) const; + + QPlaceSearchSuggestionReply *searchSuggestions(const QPlaceSearchRequest &request) const; + + QPlaceIdReply *savePlace(const QPlace &place); + QPlaceIdReply *removePlace(const QString &placeId); + + QPlaceIdReply *saveCategory(const QPlaceCategory &category, const QString &parentId = QString()); + QPlaceIdReply *removeCategory(const QString &categoryId); + + QPlaceReply *initializeCategories(); + QString parentCategoryId(const QString &categoryId) const; + QStringList childCategoryIds(const QString &parentId = QString()) const; + + QPlaceCategory category(const QString &categoryId) const; + QList childCategories(const QString &parentId = QString()) const; + + QList locales() const; + void setLocale(const QLocale &locale); + void setLocales(const QList &locale); + + QPlace compatiblePlace(const QPlace &place); + + QPlaceMatchReply *matchingPlaces(const QPlaceMatchRequest &request) const; + +Q_SIGNALS: + void finished(QPlaceReply *reply); + void error(QPlaceReply *, QPlaceReply::Error error, const QString &errorString = QString()); + + void placeAdded(const QString &placeId); + void placeUpdated(const QString &placeId); + void placeRemoved(const QString &placeId); + + void categoryAdded(const QPlaceCategory &category, const QString &parentId); + void categoryUpdated(const QPlaceCategory &category, const QString &parentId); + void categoryRemoved(const QString &categoryId, const QString &parentId); + void dataChanged(); + +private: + explicit QPlaceManager(QPlaceManagerEngine *engine, QObject *parent = Q_NULLPTR); + Q_DISABLE_COPY(QPlaceManager) + + QPlaceManagerEngine *d; + + friend class QGeoServiceProvider; + friend class QGeoServiceProviderPrivate; + friend class QPlaceIcon; +}; + +QT_END_NAMESPACE + +#endif // QPLACEMANAGER_H diff --git a/src/location/places/qplacemanagerengine.cpp b/src/location/places/qplacemanagerengine.cpp new file mode 100644 index 0000000..c1b5f23 --- /dev/null +++ b/src/location/places/qplacemanagerengine.cpp @@ -0,0 +1,462 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacemanagerengine.h" +#include "qplacemanagerengine_p.h" +#include "unsupportedreplies_p.h" + +#include + +#include "qplaceicon.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QPlaceManagerEngine + \inmodule QtLocation + \ingroup QtLocation-impl + \ingroup QtLocation-places + \ingroup QtLocation-places-manager + \since 5.6 + + \brief The QPlaceManagerEngine class provides an interface for + implementers of QGeoServiceProvider plugins who want to provide access to place + functionality. + + Application developers need not concern themselves with the QPlaceManagerEngine. + Backend implementers however will need to derive from QPlaceManagerEngine and provide + implementations for the abstract virtual functions. + + For more information on writing a backend see the \l {Places Backend} documentation. + + \sa QPlaceManager +*/ + +/*! + Constructs a new engine with the specified \a parent, using \a parameters to pass any + implementation specific data to the engine. +*/ +QPlaceManagerEngine::QPlaceManagerEngine(const QVariantMap ¶meters, + QObject *parent) +: QObject(parent), d_ptr(new QPlaceManagerEnginePrivate) +{ + qRegisterMetaType(); + qRegisterMetaType(); + Q_UNUSED(parameters) +} + +/*! + Destroys this engine. +*/ +QPlaceManagerEngine::~QPlaceManagerEngine() +{ + delete d_ptr; +} + +/*! + \internal + Sets the name which this engine implementation uses to distinguish itself + from the implementations provided by other plugins to \a managerName. + + This function does not need to be called by engine implementers, + it is implicitly called by QGeoServiceProvider to set the manager + name to be the same as the provider's. +*/ +void QPlaceManagerEngine::setManagerName(const QString &managerName) +{ + d_ptr->managerName = managerName; +} + +/*! + Returns the name which this engine implementation uses to distinguish + itself from the implementations provided by other plugins. + + The manager name is automatically set to be the same + as the QGeoServiceProviderFactory::providerName(). +*/ +QString QPlaceManagerEngine::managerName() const +{ + return d_ptr->managerName; +} + +/*! + \internal + Sets the version of this engine implementation to \a managerVersion. + + The combination of managerName() and managerVersion() should be unique + amongst plugin implementations. +*/ +void QPlaceManagerEngine::setManagerVersion(int managerVersion) +{ + d_ptr->managerVersion = managerVersion; +} + +/*! + Returns the version of this engine implementation. + + The manager version is automatically set to be the same + as the QGeoServiceProviderFactory::providerVersion(). +*/ +int QPlaceManagerEngine::managerVersion() const +{ + return d_ptr->managerVersion; +} + +/*! + Retrieves details of place corresponding to the given \a placeId. +*/ +QPlaceDetailsReply *QPlaceManagerEngine::getPlaceDetails(const QString &placeId) +{ + Q_UNUSED(placeId) + + return new QPlaceDetailsReplyUnsupported(this); +} + +/*! + Retrieves content for a place according to the parameters specified in \a request. +*/ +QPlaceContentReply *QPlaceManagerEngine::getPlaceContent(const QPlaceContentRequest &request) +{ + Q_UNUSED(request) + + return new QPlaceContentReplyUnsupported(this); +} + +/*! + Searches for places according to the parameters specified in \a request. +*/ +QPlaceSearchReply *QPlaceManagerEngine::search(const QPlaceSearchRequest &request) +{ + Q_UNUSED(request) + + return new QPlaceSearchReplyUnsupported(QPlaceReply::UnsupportedError, + QStringLiteral("Place search is not supported."), this); +} + +/*! + Requests a set of search term suggestions according to the parameters specified in \a request. +*/ +QPlaceSearchSuggestionReply *QPlaceManagerEngine::searchSuggestions( + const QPlaceSearchRequest &request) +{ + Q_UNUSED(request) + + return new QPlaceSearchSuggestionReplyUnsupported(this); +} + +/*! + Saves a specified \a place to the manager engine's datastore. +*/ +QPlaceIdReply *QPlaceManagerEngine::savePlace(const QPlace &place) +{ + Q_UNUSED(place) + + return new QPlaceIdReplyUnsupported(QStringLiteral("Save place is not supported"), + QPlaceIdReply::SavePlace, this); +} + +/*! + Removes the place corresponding to \a placeId from the manager engine's datastore. +*/ +QPlaceIdReply *QPlaceManagerEngine::removePlace(const QString &placeId) +{ + Q_UNUSED(placeId) + + return new QPlaceIdReplyUnsupported(QStringLiteral("Remove place is not supported"), + QPlaceIdReply::RemovePlace, this); +} + +/*! + Saves a \a category that is a child of the category specified by \a parentId. An empty + \a parentId means \a category is saved as a top level category. +*/ +QPlaceIdReply *QPlaceManagerEngine::saveCategory(const QPlaceCategory &category, + const QString &parentId) +{ + Q_UNUSED(category) + Q_UNUSED(parentId) + + return new QPlaceIdReplyUnsupported(QStringLiteral("Save category is not supported"), + QPlaceIdReply::SaveCategory, this); +} + +/*! + Removes the category corresponding to \a categoryId from the manager engine's datastore. +*/ + +QPlaceIdReply *QPlaceManagerEngine::removeCategory(const QString &categoryId) +{ + Q_UNUSED(categoryId) + + return new QPlaceIdReplyUnsupported(QStringLiteral("Remove category is not supported"), + QPlaceIdReply::RemoveCategory, this); +} + +/*! + Initializes the categories of the manager engine. +*/ +QPlaceReply *QPlaceManagerEngine::initializeCategories() +{ + return new QPlaceReplyUnsupported(QStringLiteral("Categories are not supported."), this); +} + +/*! + Returns the parent category identifier of the category corresponding to \a categoryId. +*/ +QString QPlaceManagerEngine::parentCategoryId(const QString &categoryId) const +{ + Q_UNUSED(categoryId) + + return QString(); +} + +/*! + Returns the child category identifiers of the category corresponding to \a categoryId. If + \a categoryId is empty then all top level category identifiers are returned. +*/ +QStringList QPlaceManagerEngine::childCategoryIds(const QString &categoryId) const +{ + Q_UNUSED(categoryId) + + return QStringList(); +} + +/*! + Returns the category corresponding to the given \a categoryId. +*/ +QPlaceCategory QPlaceManagerEngine::category(const QString &categoryId) const +{ + Q_UNUSED(categoryId) + + return QPlaceCategory(); +} + +/*! + Returns a list of categories that are children of the category corresponding to \a parentId. + If \a parentId is empty, all the top level categories are returned. +*/ +QList QPlaceManagerEngine::childCategories(const QString &parentId) const +{ + Q_UNUSED(parentId) + + return QList(); +} + +/*! + Returns a list of preferred locales. The locales are used as a hint to the manager engine for + what language place and category details should be returned in. + + If the first specified locale cannot be accommodated, the manager engine falls back to the next + and so forth. + + Support for locales may vary from provider to provider. For those that do support it, by + default, the \l {QLocale::setDefault()}{global default locale} will be used. If the manager + engine has no locales assigned to it, it implicitly uses the global default locale. For + engines that do not support locales, the locale list is always empty. +*/ +QList QPlaceManagerEngine::locales() const +{ + return QList(); +} + +/*! + Set the list of preferred \a locales. +*/ +void QPlaceManagerEngine::setLocales(const QList &locales) +{ + Q_UNUSED(locales) +} + +/*! + Returns the manager instance used to create this engine. +*/ +QPlaceManager *QPlaceManagerEngine::manager() const +{ + return d_ptr->manager; +} + +/*! + Returns a pruned or modified version of the \a original place + which is suitable to be saved by the manager engine. + + Only place details that are supported by this manager is + present in the modified version. Manager specific data such + as the place id, is not copied over from the \a original. +*/ +QPlace QPlaceManagerEngine::compatiblePlace(const QPlace &original) const +{ + Q_UNUSED(original); + return QPlace(); +} + +/*! + Returns a reply which contains a list of places which correspond/match those + specified in \a request. +*/ +QPlaceMatchReply * QPlaceManagerEngine::matchingPlaces(const QPlaceMatchRequest &request) +{ + Q_UNUSED(request) + + return new QPlaceMatchReplyUnsupported(this); +} + +/*! + QUrl QPlaceManagerEngine::constructIconUrl(const QPlaceIcon &icon, const QSize &size) + + Constructs an icon url from a given \a icon, \a size. The URL of the icon + image that most closely matches the given parameters is returned. +*/ +QUrl QPlaceManagerEngine::constructIconUrl(const QPlaceIcon &icon, const QSize &size) const +{ + Q_UNUSED(icon); + Q_UNUSED(size); + + return QUrl(); +} + +QPlaceManagerEnginePrivate::QPlaceManagerEnginePrivate() + : managerVersion(-1), manager(0) +{ +} + +QPlaceManagerEnginePrivate::~QPlaceManagerEnginePrivate() +{ +} + +/*! + \fn void QPlaceManagerEngine::finished(QPlaceReply *reply) + + This signal is emitted when \a reply has finished processing. + + If reply->error() equals QPlaceReply::NoError then the processing + finished successfully. + + This signal and QPlaceReply::finished() will be emitted at the same time. + + \note Do not delete the \a reply object in the slot connected to this signal. + Use deleteLater() instead. +*/ + +/*! + \fn void QPlaceManagerEngine::error(QPlaceReply * reply, QPlaceReply::Error error, const QString &errorString = QString()); + + This signal is emitted when an error has been detected in the processing of + \a reply. The QPlaceManager::finished() signal will probably follow. + + The error will be described by the error code \a error. If \a errorString is + not empty it will contain a textual description of the error meant for developers + and not end users. + + This signal and QPlaceReply::error() will be emitted at the same time. + + \note Do not delete the \a reply object in the slot connected to this signal. + Use deleteLater() instead. +*/ + +/*! + \fn void QPlaceManagerEngine::placeAdded(const QString &placeId) + + This signal is emitted if a place has been added to the manager engine's datastore. + The particular added place is specified by \a placeId. + + This signal is only emitted by manager engines that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManagerEngine::placeUpdated(const QString &placeId) + + This signal is emitted if a place has been modified in the manager engine's datastore. + The particular modified place is specified by \a placeId. + + This signal is only emitted by manager engines that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManagerEngine::placeRemoved(const QString &placeId) + + This signal is emitted if a place has been removed from the manager engine's datastore. + The particular place that has been removed is specified by \a placeId. + + This signal is only emitted by manager engines that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManagerEngine::categoryAdded(const QPlaceCategory &category, const QString &parentId) + + This signal is emitted if a \a category has been added to the manager engine's datastore. + The parent of the \a category is specified by \a parentId. + + This signal is only emitted by manager engines that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManagerEngine::categoryUpdated(const QPlaceCategory &category, const QString &parentId) + + This signal is emitted if a \a category has been modified in the manager engine's datastore. + The parent of the modified category is specified by \a parentId. + + This signal is only emitted by manager engines that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + \fn void QPlaceManagerEngine::categoryRemoved(const QString &categoryId, const QString &parentId) + + This signal is emitted when the category corresponding to \a categoryId has + been removed from the manager engine's datastore. The parent of the removed category + is specified by \a parentId. + + This signal is only emitted by manager engines that support the QPlaceManager::NotificationsFeature. + \sa dataChanged() +*/ + +/*! + * \fn QPlaceManagerEngine::dataChanged() + + This signal is emitted by the engine if there are large scale changes to its + underlying datastore and the engine considers these changes radical enough + to require clients to reload all data. + + If the signal is emitted, no other signals will be emitted for the associated changes. +*/ + +QT_END_NAMESPACE + +#include "moc_qplacemanagerengine.cpp" diff --git a/src/location/places/qplacemanagerengine.h b/src/location/places/qplacemanagerengine.h new file mode 100644 index 0000000..0c11278 --- /dev/null +++ b/src/location/places/qplacemanagerengine.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEMANAGERENGINE_H +#define QPLACEMANAGERENGINE_H + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceManagerEnginePrivate; +class QPlaceMatchReply; +class QPlaceMatchRequest; +class QPlaceSearchReply; +class QPlaceSearchRequest; +class QPlaceSearchSuggestionReply; + +class Q_LOCATION_EXPORT QPlaceManagerEngine : public QObject +{ + Q_OBJECT + +public: + explicit QPlaceManagerEngine(const QVariantMap ¶meters, QObject *parent = Q_NULLPTR); + virtual ~QPlaceManagerEngine(); + + QString managerName() const; + int managerVersion() const; + + virtual QPlaceDetailsReply *getPlaceDetails(const QString &placeId); + + virtual QPlaceContentReply *getPlaceContent(const QPlaceContentRequest &request); + + virtual QPlaceSearchReply *search(const QPlaceSearchRequest &request); + + virtual QPlaceSearchSuggestionReply *searchSuggestions(const QPlaceSearchRequest &request); + + virtual QPlaceIdReply *savePlace(const QPlace &place); + virtual QPlaceIdReply *removePlace(const QString &placeId); + + virtual QPlaceIdReply *saveCategory(const QPlaceCategory &category, const QString &parentId); + virtual QPlaceIdReply *removeCategory(const QString &categoryId); + + virtual QPlaceReply *initializeCategories(); + virtual QString parentCategoryId(const QString &categoryId) const; + virtual QStringList childCategoryIds(const QString &categoryId) const; + virtual QPlaceCategory category(const QString &categoryId) const; + + virtual QList childCategories(const QString &parentId) const; + + virtual QList locales() const; + virtual void setLocales(const QList &locales); + + virtual QUrl constructIconUrl(const QPlaceIcon &icon, const QSize &size) const; + + virtual QPlace compatiblePlace(const QPlace &original) const; + + virtual QPlaceMatchReply *matchingPlaces(const QPlaceMatchRequest &request); + +Q_SIGNALS: + void finished(QPlaceReply *reply); + void error(QPlaceReply *, QPlaceReply::Error error, const QString &errorString = QString()); + + void placeAdded(const QString &placeId); + void placeUpdated(const QString &placeId); + void placeRemoved(const QString &placeId); + + void categoryAdded(const QPlaceCategory &category, const QString &parentCategoryId); + void categoryUpdated(const QPlaceCategory &category, const QString &parentCategoryId); + void categoryRemoved(const QString &categoryId, const QString &parentCategoryId); + void dataChanged(); + +protected: + QPlaceManager *manager() const; + +private: + void setManagerName(const QString &managerName); + void setManagerVersion(int managerVersion); + + QPlaceManagerEnginePrivate *d_ptr; + Q_DISABLE_COPY(QPlaceManagerEngine) + + friend class QGeoServiceProviderPrivate; + friend class QPlaceManager; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacemanagerengine_p.h b/src/location/places/qplacemanagerengine_p.h new file mode 100644 index 0000000..1a1359a --- /dev/null +++ b/src/location/places/qplacemanagerengine_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEMANAGERENGINE_P_H +#define QPLACEMANAGERENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceManagerEnginePrivate +{ +public: + QPlaceManagerEnginePrivate(); + ~QPlaceManagerEnginePrivate(); + + QString managerName; + int managerVersion; + QPlaceManager *manager; + +private: + Q_DISABLE_COPY(QPlaceManagerEnginePrivate) +}; + +QT_END_NAMESPACE + +#endif // QPLACEMANAGERENGINE_P_H diff --git a/src/location/places/qplacematchreply.cpp b/src/location/places/qplacematchreply.cpp new file mode 100644 index 0000000..9794b3f --- /dev/null +++ b/src/location/places/qplacematchreply.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacematchreply.h" +#include "qplacereply_p.h" + + +QT_BEGIN_NAMESPACE +class QPlaceMatchReplyPrivate : public QPlaceReplyPrivate +{ +public: + QPlaceMatchReplyPrivate(){} + QList places; + QPlaceMatchRequest request; +}; + +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +/*! + \class QPlaceMatchReply + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-replies + \since 5.6 + + \brief The QPlaceMatchReply class manages a place matching operation started by an + instance of QPlaceManager. + + If the operation is successful, the number of places in the reply matches those + in the request. If a particular place in the request is not found, a default + constructed place is used as a place holder in the reply. In this way, there + is always a one is to one relationship between input places in the request, + and output places in the reply. + + If the operation is not successful the number of places is always zero. + + See \l {Matching places between managers} for an example on how to use + a match reply. + + \sa QPlaceMatchRequest, QPlaceManager +*/ + +/*! + Constructs a match reply with a given \a parent. +*/ +QPlaceMatchReply::QPlaceMatchReply(QObject *parent) + : QPlaceReply(new QPlaceMatchReplyPrivate, parent) +{ +} + +/*! + Destroys the match reply. +*/ +QPlaceMatchReply::~QPlaceMatchReply() +{ +} + +/*! + Returns the type of reply. +*/ +QPlaceReply::Type QPlaceMatchReply::type() const +{ + return QPlaceReply::MatchReply; +} + + /*! + Returns a list of matching places; +*/ +QList QPlaceMatchReply::places() const +{ + Q_D(const QPlaceMatchReply); + return d->places; +} + +/*! + Sets the list of matching \a places. +*/ +void QPlaceMatchReply::setPlaces(const QList &places) +{ + Q_D(QPlaceMatchReply); + d->places = places; +} + +/*! + Returns the match request that was used to generate this reply. +*/ +QPlaceMatchRequest QPlaceMatchReply::request() const +{ + Q_D(const QPlaceMatchReply); + return d->request; +} + +/*! + Sets the match \a request used to generate this reply. +*/ +void QPlaceMatchReply::setRequest(const QPlaceMatchRequest &request) +{ + Q_D(QPlaceMatchReply); + d->request = request; +} diff --git a/src/location/places/qplacematchreply.h b/src/location/places/qplacematchreply.h new file mode 100644 index 0000000..df4b3fd --- /dev/null +++ b/src/location/places/qplacematchreply.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEMATCHREPLY_H +#define QPLACEMATCHREPLY_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceMatchReplyPrivate; +class Q_LOCATION_EXPORT QPlaceMatchReply : public QPlaceReply +{ + Q_OBJECT +public: + explicit QPlaceMatchReply(QObject *parent = Q_NULLPTR); + ~QPlaceMatchReply(); + + QPlaceReply::Type type() const; + + QList places() const; + QPlaceMatchRequest request() const; + +protected: + void setPlaces(const QList &results); + void setRequest(const QPlaceMatchRequest &request); +private: + Q_DISABLE_COPY(QPlaceMatchReply) + Q_DECLARE_PRIVATE(QPlaceMatchReply) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacematchrequest.cpp b/src/location/places/qplacematchrequest.cpp new file mode 100644 index 0000000..89343dc --- /dev/null +++ b/src/location/places/qplacematchrequest.cpp @@ -0,0 +1,259 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacematchrequest.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceMatchRequestPrivate : public QSharedData +{ +public: + QPlaceMatchRequestPrivate(); + QPlaceMatchRequestPrivate(const QPlaceMatchRequestPrivate &other); + ~QPlaceMatchRequestPrivate(); + + QPlaceMatchRequestPrivate &operator=(const QPlaceMatchRequestPrivate &other); + bool operator==(const QPlaceMatchRequestPrivate &other) const; + + void clear(); + + QList places; + QVariantMap parameters; +}; + +QPlaceMatchRequestPrivate::QPlaceMatchRequestPrivate() + : QSharedData() +{ +} + +QPlaceMatchRequestPrivate::QPlaceMatchRequestPrivate(const QPlaceMatchRequestPrivate &other) + : QSharedData(other), + places(other.places), + parameters(other.parameters) +{ +} + +QPlaceMatchRequestPrivate::~QPlaceMatchRequestPrivate() +{ +} + +QPlaceMatchRequestPrivate &QPlaceMatchRequestPrivate::operator=(const QPlaceMatchRequestPrivate &other) +{ + if (this != &other) { + places = other.places; + parameters = other.parameters; + } + + return *this; +} + +bool QPlaceMatchRequestPrivate::operator==(const QPlaceMatchRequestPrivate &other) const +{ + return (places == other.places + && parameters == other.parameters); +} + +void QPlaceMatchRequestPrivate::clear() +{ + places.clear(); + parameters.clear(); +} + +/*! + \class QPlaceMatchRequest + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-requests + \since 5.6 + + \brief The QPlaceMatchRequest class is used to find places from one manager that match those from another. It represents + a set of request parameters. + + Places from another manager that may have corresponding/matching places in the current manager are assigned using setPlaces() or setResults(). + A set of further parameters are specified which determines the criteria for matching. + + The typical key for matching is the QPlaceMatchRequest::AlternativeId, the value is an alternative identifier attribute type of the format + x_id_ for example x_id_here. The provider name is name supplied to the QGeoServiceProvider instance. + + See \l {Matching places between managers} for an example on how to use a match request. + + \sa QPlaceMatchReply, QPlaceManager +*/ + +/*! + \variable QPlaceMatchRequest::AlternativeId + The key to specify that matching is to be accomplished via an alternative place identifier. +*/ +const QString QPlaceMatchRequest::AlternativeId(QLatin1String("alternativeId")); + +/*! + Default constructor. Constructs a new request object. +*/ +QPlaceMatchRequest::QPlaceMatchRequest() + : d_ptr(new QPlaceMatchRequestPrivate()) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QPlaceMatchRequest::QPlaceMatchRequest(const QPlaceMatchRequest &other) + : d_ptr(other.d_ptr) +{ +} + +/*! + Destroys the request object. +*/ +QPlaceMatchRequest::~QPlaceMatchRequest() +{ +} + +/*! + Assigns \a other to this search request and returns a reference + to this match request. +*/ +QPlaceMatchRequest &QPlaceMatchRequest::operator= (const QPlaceMatchRequest & other) +{ + if (this == &other) + return *this; + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns true if \a other is equal to this match request, + otherwise returns false. +*/ +bool QPlaceMatchRequest::operator== (const QPlaceMatchRequest &other) const +{ + Q_D(const QPlaceMatchRequest); + return *d == *other.d_func(); +} + +/*! + Returns true if \a other is not equal to this match request, + otherwise returns false. +*/ +bool QPlaceMatchRequest::operator!= (const QPlaceMatchRequest &other) const +{ + Q_D(const QPlaceMatchRequest); + return !(*d == *other.d_func()); +} + + +/*! + Returns a list of places which are to be matched. +*/ +QList QPlaceMatchRequest::places() const +{ + Q_D(const QPlaceMatchRequest); + return d->places; +} + +/*! + Sets a list of \a places which are to be matched. + + \sa setResults() +*/ +void QPlaceMatchRequest::setPlaces(const QList places) +{ + Q_D(QPlaceMatchRequest); + d->places = places; +} + +/*! + Convenience function which uses a set of search \a results to set + the places which should be matched. + + \sa setPlaces() +*/ +void QPlaceMatchRequest::setResults(const QList &results) +{ + Q_D(QPlaceMatchRequest); + QList places; + foreach (const QPlaceSearchResult &result, results) { + if (result.type() == QPlaceSearchResult::PlaceResult) { + QPlaceResult placeResult = result; + places.append(placeResult.place()); + } + } + + d->places = places; +} + +/*! + Returns the parameters for matching places. +*/ +QVariantMap QPlaceMatchRequest::parameters() const +{ + Q_D(const QPlaceMatchRequest); + return d->parameters; +} + +/*! + Sets the \a parameters for matching places. +*/ +void QPlaceMatchRequest::setParameters(const QVariantMap ¶meters) +{ + Q_D(QPlaceMatchRequest); + d->parameters = parameters; +} + +/*! + Clears the match request. +*/ +void QPlaceMatchRequest::clear() +{ + Q_D(QPlaceMatchRequest); + d->clear(); +} + +inline QPlaceMatchRequestPrivate *QPlaceMatchRequest::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QPlaceMatchRequestPrivate *QPlaceMatchRequest::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +QT_END_NAMESPACE diff --git a/src/location/places/qplacematchrequest.h b/src/location/places/qplacematchrequest.h new file mode 100644 index 0000000..24f9d9f --- /dev/null +++ b/src/location/places/qplacematchrequest.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEMATCHREQUEST_H +#define QPLACEMATCHREQUEST_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceMatchRequestPrivate; + +class Q_LOCATION_EXPORT QPlaceMatchRequest +{ +public: + static const QString AlternativeId; + + QPlaceMatchRequest(); + QPlaceMatchRequest(const QPlaceMatchRequest &other); + + + QPlaceMatchRequest &operator=(const QPlaceMatchRequest &other); + + bool operator==(const QPlaceMatchRequest &other) const; + bool operator!=(const QPlaceMatchRequest &other) const; + + ~QPlaceMatchRequest(); + + QList places() const; + void setPlaces(const QList places); + + void setResults(const QList &results); + + QVariantMap parameters() const; + void setParameters(const QVariantMap ¶meters); + + void clear(); + +private: + QSharedDataPointer d_ptr; + inline QPlaceMatchRequestPrivate *d_func(); + inline const QPlaceMatchRequestPrivate *d_func() const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplaceproposedsearchresult.cpp b/src/location/places/qplaceproposedsearchresult.cpp new file mode 100644 index 0000000..20a6be9 --- /dev/null +++ b/src/location/places/qplaceproposedsearchresult.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Aaron McCarthy +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceproposedsearchresult.h" +#include "qplaceproposedsearchresult_p.h" + +QT_BEGIN_NAMESPACE + +QPlaceProposedSearchResultPrivate::QPlaceProposedSearchResultPrivate() +{ +} + +QPlaceProposedSearchResultPrivate::QPlaceProposedSearchResultPrivate(const QPlaceProposedSearchResultPrivate &other) +: QPlaceSearchResultPrivate(other), searchRequest(other.searchRequest) +{ +} + +QPlaceProposedSearchResultPrivate::~QPlaceProposedSearchResultPrivate() +{ +} + +bool QPlaceProposedSearchResultPrivate::compare(const QPlaceSearchResultPrivate *other) const +{ + const QPlaceProposedSearchResultPrivate *od = static_cast(other); + return QPlaceSearchResultPrivate::compare(other) && searchRequest == od->searchRequest; +} + +/*! + \class QPlaceProposedSearchResult + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since Qt Location 5.2 + + \brief The QPlaceProposedSearchResult class represents a search result containing a proposed search. + + \sa QPlaceSearchResult +*/ + +/*! + Constructs a new proposed search result. +*/ +QPlaceProposedSearchResult::QPlaceProposedSearchResult() +: QPlaceSearchResult(new QPlaceProposedSearchResultPrivate) +{ +} + +/*! + \fn QPlaceProposedSearchResult::QPlaceProposedSearchResult(const QPlaceSearchRequest &other) + + Contructs a copy of \a other if possible, otherwise constructs a default proposed search + result. +*/ +Q_IMPLEMENT_SEARCHRESULT_COPY_CTOR(QPlaceProposedSearchResult) + +Q_IMPLEMENT_SEARCHRESULT_D_FUNC(QPlaceProposedSearchResult) + +/*! + Destroys the proposed search result. +*/ +QPlaceProposedSearchResult::~QPlaceProposedSearchResult() +{ +} + +/*! + Returns a place search request that can be used to perform an additional proposed search. +*/ +QPlaceSearchRequest QPlaceProposedSearchResult::searchRequest() const +{ + Q_D(const QPlaceProposedSearchResult); + return d->searchRequest; +} + +/*! + Sets the proposed search request to \a request. +*/ +void QPlaceProposedSearchResult::setSearchRequest(const QPlaceSearchRequest &request) +{ + Q_D(QPlaceProposedSearchResult); + d->searchRequest = request; +} + +QT_END_NAMESPACE diff --git a/src/location/places/qplaceproposedsearchresult.h b/src/location/places/qplaceproposedsearchresult.h new file mode 100644 index 0000000..d949717 --- /dev/null +++ b/src/location/places/qplaceproposedsearchresult.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Aaron McCarthy +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROPOSEDSEARCHRESULT_H +#define QPROPOSEDSEARCHRESULT_H + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceProposedSearchResultPrivate; + +class Q_LOCATION_EXPORT QPlaceProposedSearchResult : public QPlaceSearchResult +{ +public: + QPlaceProposedSearchResult(); + +#ifdef Q_QDOC + QPlaceProposedSearchResult(const QPlaceSearchRequest &other); +#else + Q_DECLARE_SEARCHRESULT_COPY_CTOR(QPlaceProposedSearchResult) +#endif + + ~QPlaceProposedSearchResult(); + + QPlaceSearchRequest searchRequest() const; + void setSearchRequest(const QPlaceSearchRequest &request); + +private: + Q_DECLARE_SEARCHRESULT_D_FUNC(QPlaceProposedSearchResult) +}; + +Q_DECLARE_TYPEINFO(QPlaceProposedSearchResult, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +#endif // QPROPOSEDSEARCHRESULT_H diff --git a/src/location/places/qplaceproposedsearchresult_p.h b/src/location/places/qplaceproposedsearchresult_p.h new file mode 100644 index 0000000..46f9510 --- /dev/null +++ b/src/location/places/qplaceproposedsearchresult_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Aaron McCarthy +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROPOSEDSEARCHRESULT_P_H +#define QPROPOSEDSEARCHRESULT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qplacesearchresult_p.h" + +QT_BEGIN_NAMESPACE + +class QPlaceProposedSearchResultPrivate : public QPlaceSearchResultPrivate +{ +public: + QPlaceProposedSearchResultPrivate(); + QPlaceProposedSearchResultPrivate(const QPlaceProposedSearchResultPrivate &other); + + ~QPlaceProposedSearchResultPrivate(); + + bool compare(const QPlaceSearchResultPrivate *other) const Q_DECL_OVERRIDE; + + Q_DEFINE_SEARCHRESULT_PRIVATE_HELPER(QPlaceProposedSearchResult, QPlaceSearchResult::ProposedSearchResult) + + QPlaceSearchRequest searchRequest; +}; + +QT_END_NAMESPACE + +#endif // QPROPOSEDSEARCHRESULT_P_H diff --git a/src/location/places/qplaceratings.cpp b/src/location/places/qplaceratings.cpp new file mode 100644 index 0000000..391db37 --- /dev/null +++ b/src/location/places/qplaceratings.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceratings.h" +#include "qplaceratings_p.h" + +QT_USE_NAMESPACE + +QPlaceRatingsPrivate::QPlaceRatingsPrivate() + : QSharedData(), average(0), maximum(0), count(0) +{ +} + +QPlaceRatingsPrivate::QPlaceRatingsPrivate(const QPlaceRatingsPrivate &other) +: QSharedData(), average(0), maximum(other.maximum), count(other.count) +{ +} + +QPlaceRatingsPrivate::~QPlaceRatingsPrivate() +{ +} + +bool QPlaceRatingsPrivate::operator==(const QPlaceRatingsPrivate &other) const +{ + return average == other.average && maximum == other.maximum && count == other.count; +} + +bool QPlaceRatingsPrivate::isEmpty() const +{ + return count == 0 && average == 0 && maximum == 0; +} + +/*! + \class QPlaceRatings + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceRatings class holds rating information about a place. + + Rating information is used to describe how good a place is conceived to be. + Typically this information is visualized as a number of stars. + The average() function returns an aggregated ratings value out of a possible + maximum as given by the maximum() function. + + \snippet places/requesthandler.h Ratings +*/ + +/*! + Constructs a new ratings object. +*/ +QPlaceRatings::QPlaceRatings() + : d(new QPlaceRatingsPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QPlaceRatings::QPlaceRatings(const QPlaceRatings &other) + :d(other.d) +{ +} + +/*! + Destroys the ratings object. +*/ +QPlaceRatings::~QPlaceRatings() +{ +} + +/*! + Assigns \a other to this ratings object and returns + a reference to this ratings object. +*/ +QPlaceRatings &QPlaceRatings::operator=(const QPlaceRatings &other) +{ + if (this == &other) + return *this; + + d = other.d; + return *this; +} + +/*! + Returns true if \a other is equal to this ratings object, + otherwise returns false. +*/ +bool QPlaceRatings::operator==(const QPlaceRatings &other) const +{ + return (*(d.constData()) == *(other.d.constData())); +} + +/*! + \fn bool QPlaceRatings::operator!=(const QPlaceRatings &other) const + + Returns true if \a other is not equal to this ratings object, + otherwise returns false. +*/ + +/*! + Returns the average value of individual ratings. +*/ +qreal QPlaceRatings::average() const +{ + return d->average; +} + +/*! + Sets the \a average value of the ratings. +*/ +void QPlaceRatings::setAverage(qreal average) +{ + d->average = average; +} + +/*! + Returns the maximum possible rating value. +*/ +qreal QPlaceRatings::maximum() const +{ + return d->maximum; +} + +/*! + Sets the maximum possible rating value to \a max. +*/ +void QPlaceRatings::setMaximum(qreal max) +{ + d->maximum = max; +} + +/*! + Returns the total number of individual ratings. +*/ +int QPlaceRatings::count() const +{ + return d->count; +} + +/*! + Sets the total number of individual ratings to \a count. +*/ +void QPlaceRatings::setCount(int count) +{ + d->count = count; +} + +/*! + Returns true if all fields of the place ratings are 0; otherwise returns false. +*/ +bool QPlaceRatings::isEmpty() const +{ + return d->isEmpty(); +} diff --git a/src/location/places/qplaceratings.h b/src/location/places/qplaceratings.h new file mode 100644 index 0000000..3cda5be --- /dev/null +++ b/src/location/places/qplaceratings.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACERATINGS_H +#define QPLACERATINGS_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceRatingsPrivate; + +class Q_LOCATION_EXPORT QPlaceRatings +{ +public: + QPlaceRatings(); + QPlaceRatings(const QPlaceRatings &other); + + ~QPlaceRatings(); + + QPlaceRatings &operator=(const QPlaceRatings &other); + + bool operator==(const QPlaceRatings &other) const; + bool operator!=(const QPlaceRatings &other) const { + return !(other == *this); + } + + qreal average() const; + void setAverage(qreal average); + + int count() const; + void setCount(int count); + + qreal maximum() const; + void setMaximum(qreal max); + + bool isEmpty() const; + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceRatings) + +#endif diff --git a/src/location/places/qplaceratings_p.h b/src/location/places/qplaceratings_p.h new file mode 100644 index 0000000..21d441a --- /dev/null +++ b/src/location/places/qplaceratings_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACERATINGS_P_H +#define QPLACERATINGS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceRatingsPrivate : public QSharedData +{ +public: + QPlaceRatingsPrivate(); + QPlaceRatingsPrivate(const QPlaceRatingsPrivate &other); + + ~QPlaceRatingsPrivate(); + + bool operator==(const QPlaceRatingsPrivate &other) const; + + bool isEmpty() const; + + qreal average; + qreal maximum; + int count; +}; + +QT_END_NAMESPACE + +#endif // QPLACERATING_P_H diff --git a/src/location/places/qplacereply.cpp b/src/location/places/qplacereply.cpp new file mode 100644 index 0000000..0590871 --- /dev/null +++ b/src/location/places/qplacereply.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacereply.h" +#include "qplacereply_p.h" + +QT_USE_NAMESPACE + +/*! + \class QPlaceReply + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-replies + \since 5.6 + + \brief The QPlaceReply class manages an operation started by an instance of QPlaceManager and + serves as a base class for more specialized replies. + + The QPlaceReply and each of its specialized subclasses manage the + state and results of their corresponding operations. The QPlaceReply itself is used + for operations that have no results, that is, it only necessary to know if the operation + succeeded or failed. + + The finished() signal can be used to monitor the progress of an operation. + Once an operation is complete, the error() and errorString() methods provide information + on whether the operation completed successfully. If successful, the reply + will contain the results for that operation, that is, each subclass will have appropriate + functions to retrieve the results of an operation. + + \sa QPlaceManager +*/ + +/*! + \enum QPlaceReply::Error + + Describes an error which occurred during an operation. + \value NoError + No error has occurred + \value PlaceDoesNotExistError + A specified place could not be found + \value CategoryDoesNotExistError + A specified category could not be found + \value CommunicationError + An error occurred communicating with the service provider. + \value ParseError + The response from the service provider or an import file was in an unrecognizable format + \value PermissionsError + The operation failed because of insufficient permissions. + \value UnsupportedError + The operation was not supported by the service provider. + \value BadArgumentError. + A parameter that was provided was invalid. + \value CancelError + The operation was canceled. + \value UnknownError + An error occurred which does not fit into any of the other categories. +*/ + +/*! + \enum QPlaceReply::Type + + Describes the reply's type. + \value Reply + This is a generic reply. + \value DetailsReply + This is a reply for the retrieval of place details + \value SearchReply + This is a reply for the place search operation. + \value SearchSuggestionReply + This is a reply for a search suggestion operation. + \value ContentReply + This is a reply for content associated with a place. + \value IdReply + This is a reply that returns an identifier of a place or category. + Typically used for place or category save and remove operations. + \value MatchReply + This is a reply that returns places that match + those from another provider. +*/ + +/*! + Constructs a reply object with a given \a parent. +*/ +QPlaceReply::QPlaceReply(QObject *parent) + : QObject(parent),d_ptr(new QPlaceReplyPrivate) +{ +} + +/*! + \internal +*/ +QPlaceReply::QPlaceReply(QPlaceReplyPrivate *dd, QObject *parent) + : QObject(parent),d_ptr(dd) +{ +} + +/*! + Destroys the reply object. +*/ +QPlaceReply::~QPlaceReply() +{ + if (!isFinished()) { + abort(); + } + delete d_ptr; +} + +/*! + Return true if the reply has completed. +*/ +bool QPlaceReply::isFinished() const +{ + return d_ptr->isFinished; +} + +/*! + Returns the type of the reply. +*/ +QPlaceReply::Type QPlaceReply::type() const +{ + return QPlaceReply::Reply; +} + +/*! + Sets the status of whether the reply is \a finished + or not. This function does not cause the finished() signal + to be emitted. +*/ +void QPlaceReply::setFinished(bool finished) +{ + d_ptr->isFinished = finished; +} + +/*! + Sets the \a error and \a errorString of the reply. + This function does not cause the QPlaceReply::error(QPlaceReply::Error, const QString &errorString) + signal to be emitted. +*/ +void QPlaceReply::setError(QPlaceReply::Error error, const QString &errorString) +{ + d_ptr->error = error; + d_ptr->errorString = errorString; +} + +/*! + Returns the error string of the reply. The error string is intended to be + used by developers only and is not fit to be displayed to an end user. + + If no error has occurred, the string is empty. +*/ +QString QPlaceReply::errorString() const +{ + return d_ptr->errorString; +} + +/*! + Returns the error code. +*/ +QPlaceReply::Error QPlaceReply::error() const +{ + return d_ptr->error; +} + +/*! + Aborts the operation. +*/ +void QPlaceReply::abort() +{ +} + +/*! + \fn void QPlaceReply::finished() + + This signal is emitted when this reply has finished processing. + + If error() equals QPlaceReply::NoError then the processing + finished successfully. + + This signal and QPlaceManager::finished() will be + emitted at the same time. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ + +/*! + \fn void QPlaceReply::error(QPlaceReply::Error error, const QString &errorString) + + This signal is emitted when an error has been detected in the processing of + this reply. The finished() signal will probably follow. + + The error will be described by the error code \a error. If \a errorString is + not empty it will contain a textual description of the error meant for + developers and not end users. + + This signal and QPlaceManager::error() will be emitted at the same time. + + \note Do not delete this reply object in the slot connected to this + signal. Use deleteLater() instead. +*/ diff --git a/src/location/places/qplacereply.h b/src/location/places/qplacereply.h new file mode 100644 index 0000000..374c68b --- /dev/null +++ b/src/location/places/qplacereply.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEREPLY_H +#define QPLACEREPLY_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceReplyPrivate; +class Q_LOCATION_EXPORT QPlaceReply : public QObject +{ + Q_OBJECT +public: + enum Error { + NoError, + PlaceDoesNotExistError, + CategoryDoesNotExistError, + CommunicationError, + ParseError, + PermissionsError, + UnsupportedError, + BadArgumentError, + CancelError, + UnknownError + }; + + enum Type { + Reply, + DetailsReply, + SearchReply, + SearchSuggestionReply, + ContentReply, + IdReply, + MatchReply + }; + + explicit QPlaceReply(QObject *parent = Q_NULLPTR); + ~QPlaceReply(); + + bool isFinished() const; + + virtual Type type() const; + + QString errorString() const; + QPlaceReply::Error error() const; + +public Q_SLOTS: + virtual void abort(); + +Q_SIGNALS: + void finished(); + void error(QPlaceReply::Error error, const QString &errorString = QString()); + +protected: + explicit QPlaceReply(QPlaceReplyPrivate *, QObject *parent = Q_NULLPTR); + void setFinished(bool finished); + void setError(QPlaceReply::Error error, const QString &errorString); + QPlaceReplyPrivate *d_ptr; + +private: + Q_DISABLE_COPY(QPlaceReply) +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceReply::Error) +Q_DECLARE_METATYPE(QPlaceReply *) + +#endif // QPLACEREPLY_H diff --git a/src/location/places/qplacereply_p.h b/src/location/places/qplacereply_p.h new file mode 100644 index 0000000..9e6c096 --- /dev/null +++ b/src/location/places/qplacereply_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEREPLY_P_H +#define QPLACEREPLY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qplacereply.h" + +QT_BEGIN_NAMESPACE + +class QPlaceReplyPrivate +{ +public: + QPlaceReplyPrivate() : + isFinished(false), + error(QPlaceReply::NoError), + errorString(QString()){} + virtual ~QPlaceReplyPrivate(){} + bool isFinished; + QPlaceReply::Error error; + QString errorString; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplaceresult.cpp b/src/location/places/qplaceresult.cpp new file mode 100644 index 0000000..fe89eff --- /dev/null +++ b/src/location/places/qplaceresult.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceresult.h" +#include "qplaceresult_p.h" +#include + +QT_USE_NAMESPACE + +QPlaceResultPrivate::QPlaceResultPrivate() + : QPlaceSearchResultPrivate(), distance(qQNaN()), sponsored(false) +{ +} + +QPlaceResultPrivate::QPlaceResultPrivate(const QPlaceResultPrivate &other) +: QPlaceSearchResultPrivate(other), distance(other.distance), place(other.place), + sponsored(other.sponsored) +{ +} + +QPlaceResultPrivate::~QPlaceResultPrivate() +{ +} + +bool QPlaceResultPrivate::compare(const QPlaceSearchResultPrivate *other) const +{ + const QPlaceResultPrivate *od = static_cast(other); + return QPlaceSearchResultPrivate::compare(other) + && ((qIsNaN(distance) && qIsNaN(od->distance)) + || qFuzzyCompare(distance, od->distance)) + && place == od->place + && sponsored == od->sponsored; +} + +/*! + \class QPlaceResult + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceResult class represents a search result containing a place. + + The PlaceResult holds the distance to the place from the center of the search request, + an instance of the place and an indication of whether the result is + sponsored or \l {http://en.wikipedia.org/wiki/Organic_search}{organic}. + + The intended usage is that a QPlaceSearchResult can be converted into a QPlaceResult + like so: + + \snippet places/requesthandler.h Convert search result + + The implementation is handled in such a way that object slicing is not an issue. + + \sa QPlaceSearchResult +*/ + +/*! + Constructs a new place result object. +*/ +QPlaceResult::QPlaceResult() +: QPlaceSearchResult(new QPlaceResultPrivate) +{ +} + +/*! + Destructor. +*/ +QPlaceResult::~QPlaceResult() +{ +} + +/*! + \fn QPlaceResult::QPlaceResult(const QPlaceSearchResult &other) + Constructs a copy of \a other if possible, otherwise constructs a default place result. +*/ +Q_IMPLEMENT_SEARCHRESULT_COPY_CTOR(QPlaceResult) + +Q_IMPLEMENT_SEARCHRESULT_D_FUNC(QPlaceResult) + +/*! + Returns the distance of the place to the search center. This + field is only relevant provided the search request contained + a search area with a search center. Otherwise, + the distance is NaN indicating an undefined distance. The default value + for distance is NaN. +*/ +qreal QPlaceResult::distance() const +{ + Q_D(const QPlaceResult); + return d->distance; +} + +/*! + Set the \a distance of the search result's place from a search center. +*/ +void QPlaceResult::setDistance(qreal distance) +{ + Q_D(QPlaceResult); + d->distance = distance; +} + +/*! + Returns the place of the search result. +*/ +QPlace QPlaceResult::place() const +{ + Q_D(const QPlaceResult); + return d->place; +} + +/*! + Sets the \a place that this result refers to. +*/ +void QPlaceResult::setPlace(const QPlace &place) +{ + Q_D(QPlaceResult); + d->place = place; +} + +/*! + Returns true if the result is a sponsored result. + + \sa setSponsored() +*/ +bool QPlaceResult::isSponsored() const +{ + Q_D(const QPlaceResult); + return d->sponsored; +} + +/*! + Sets whether the result is a \a sponsored result or not. + + \sa isSponsored() +*/ +void QPlaceResult::setSponsored(bool sponsored) +{ + Q_D(QPlaceResult); + d->sponsored = sponsored; +} diff --git a/src/location/places/qplaceresult.h b/src/location/places/qplaceresult.h new file mode 100644 index 0000000..9d11724 --- /dev/null +++ b/src/location/places/qplaceresult.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACERESULT_H +#define QPLACERESULT_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceResultPrivate; + +class Q_LOCATION_EXPORT QPlaceResult : public QPlaceSearchResult +{ +public: + QPlaceResult(); + +#ifdef Q_QDOC + QPlaceResult::QPlaceResult(const QPlaceSearchResult &other); +#else + Q_DECLARE_SEARCHRESULT_COPY_CTOR(QPlaceResult) +#endif + + virtual ~QPlaceResult(); + + qreal distance() const; + void setDistance(qreal distance); + + QPlace place() const; + void setPlace(const QPlace &place); + + bool isSponsored() const; + void setSponsored(bool sponsored); + +private: + Q_DECLARE_SEARCHRESULT_D_FUNC(QPlaceResult) +}; + +Q_DECLARE_TYPEINFO(QPlaceResult, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplaceresult_p.h b/src/location/places/qplaceresult_p.h new file mode 100644 index 0000000..0e0bf8c --- /dev/null +++ b/src/location/places/qplaceresult_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACERESULT_P_H +#define QPLACERESULT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qplacesearchresult_p.h" + +QT_BEGIN_NAMESPACE + +class QPlaceResultPrivate : public QPlaceSearchResultPrivate +{ +public: + QPlaceResultPrivate(); + QPlaceResultPrivate(const QPlaceResultPrivate &other); + + ~QPlaceResultPrivate(); + + bool compare(const QPlaceSearchResultPrivate *other) const Q_DECL_OVERRIDE; + + Q_DEFINE_SEARCHRESULT_PRIVATE_HELPER(QPlaceResult, QPlaceSearchResult::PlaceResult) + + qreal distance; + QPlace place; + bool sponsored; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacereview.cpp b/src/location/places/qplacereview.cpp new file mode 100644 index 0000000..ca79b07 --- /dev/null +++ b/src/location/places/qplacereview.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacereview.h" +#include "qplacereview_p.h" + +QT_BEGIN_NAMESPACE + +QPlaceReviewPrivate::QPlaceReviewPrivate() +: QPlaceContentPrivate(), rating(0) +{ +} + +QPlaceReviewPrivate::QPlaceReviewPrivate(const QPlaceReviewPrivate &other) + : QPlaceContentPrivate(other) +{ + dateTime = other.dateTime; + text = other.text; + language = other.language; + rating = other.rating; + reviewId = other.reviewId; + title = other.title; +} + +QPlaceReviewPrivate::~QPlaceReviewPrivate() +{ +} + +bool QPlaceReviewPrivate::compare(const QPlaceContentPrivate *other) const +{ + const QPlaceReviewPrivate *od = static_cast(other); + return QPlaceContentPrivate::compare(other) && + dateTime == od->dateTime && + text == od->text && + language == od->language && + rating == od->rating && + reviewId == od->reviewId && + title == od->title; +} + +/*! + \class QPlaceReview + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceReview class represents a review of a place. + + Each QPlaceReview has a number of properties such as + a title, text, date of submission and rating; in addition to those properties + inherited from QPlaceContent. + + Note: The Places API only supports reviews as 'retrieve-only' objects. Submitting reviews + to a provider is not a supported use case. + + \sa QPlaceContent, QPlaceEditorial +*/ + +/*! + Constructs a new review object. +*/ +QPlaceReview::QPlaceReview() + : QPlaceContent(new QPlaceReviewPrivate) +{ +} + +/*! + \fn QPlaceReview::QPlaceReview(const QPlaceContent &other) + Constructs a copy of \a other, otherwise constructs a default review object. +*/ +Q_IMPLEMENT_CONTENT_COPY_CTOR(QPlaceReview) + + +/*! + Destroys the review. +*/ +QPlaceReview::~QPlaceReview() +{ +} + +Q_IMPLEMENT_CONTENT_D_FUNC(QPlaceReview) + +/*! + Returns the date and time that the review was submitted. +*/ +QDateTime QPlaceReview::dateTime() const +{ + Q_D(const QPlaceReview); + return d->dateTime; +} + +/*! + Sets the date and time that the review was submitted to \a dateTime. +*/ +void QPlaceReview::setDateTime(const QDateTime &dateTime) +{ + Q_D(QPlaceReview); + d->dateTime = dateTime; +} + +/*! + Returns a textual description of the place. + + Depending on the provider the text could be rich (HTML based) or plain text. +*/ +QString QPlaceReview::text() const +{ + Q_D(const QPlaceReview); + return d->text; +} + +/*! + Sets \a text of the review. +*/ +void QPlaceReview::setText(const QString &text) +{ + Q_D(QPlaceReview); + d->text = text; +} + +/*! + Returns the language of the review. Typically this would be a language code + in the 2 letter ISO 639-1 format. +*/ +QString QPlaceReview::language() const +{ + Q_D(const QPlaceReview); + return d->language; +} + +/*! + Sets the \a language of the review. Typically this would be a language code + in the 2 letter ISO 639-1 format. +*/ +void QPlaceReview::setLanguage(const QString &language) +{ + Q_D(QPlaceReview); + d->language = language; +} + +/*! + Returns this review's rating of the place. +*/ +qreal QPlaceReview::rating() const +{ + Q_D(const QPlaceReview); + return d->rating; +} + +/*! + Sets the review's \a rating of the place. +*/ +void QPlaceReview::setRating(qreal rating) +{ + Q_D(QPlaceReview); + d->rating = rating; +} + +/*! + Returns the review's identifier. +*/ +QString QPlaceReview::reviewId() const +{ + Q_D(const QPlaceReview); + return d->reviewId; +} + +/*! + Sets the \a identifier of the review. +*/ +void QPlaceReview::setReviewId(const QString &identifier) +{ + Q_D(QPlaceReview); + d->reviewId = identifier; +} + +/*! + Returns the title of the review. +*/ +QString QPlaceReview::title() const +{ + Q_D(const QPlaceReview); + return d->title; +} + +/*! + Sets the \a title of the review. +*/ +void QPlaceReview::setTitle(const QString &title) +{ + Q_D(QPlaceReview); + d->title = title; +} + +QT_END_NAMESPACE diff --git a/src/location/places/qplacereview.h b/src/location/places/qplacereview.h new file mode 100644 index 0000000..2c9791c --- /dev/null +++ b/src/location/places/qplacereview.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEREVIEW_H +#define QPLACEREVIEW_H + +#include + +QT_BEGIN_NAMESPACE + +class QDateTime; +class QPlaceReviewPrivate; + +class Q_LOCATION_EXPORT QPlaceReview : public QPlaceContent +{ +public: + QPlaceReview(); +#ifdef Q_QDOC + QPlaceReview(const QPlaceContent &other); +#else + Q_DECLARE_CONTENT_COPY_CTOR(QPlaceReview) +#endif + virtual ~QPlaceReview(); + + QDateTime dateTime() const; + void setDateTime(const QDateTime &dt); + QString text() const; + void setText(const QString &text); + QString language() const; + void setLanguage(const QString &data); + + qreal rating() const; + void setRating(qreal data); + QString reviewId() const; + void setReviewId(const QString &identifier); + QString title() const; + void setTitle(const QString &data); + +private: + Q_DECLARE_CONTENT_D_FUNC(QPlaceReview) +}; + +QT_END_NAMESPACE + +#endif // QPLACEREVIEW_H diff --git a/src/location/places/qplacereview_p.h b/src/location/places/qplacereview_p.h new file mode 100644 index 0000000..345aa19 --- /dev/null +++ b/src/location/places/qplacereview_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEREVIEW_P_H +#define QPLACEREVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include "qplacecontent_p.h" + +QT_BEGIN_NAMESPACE + +class QPlaceReviewPrivate : public QPlaceContentPrivate +{ +public: + QPlaceReviewPrivate(); + QPlaceReviewPrivate(const QPlaceReviewPrivate &other); + + ~QPlaceReviewPrivate(); + + bool compare(const QPlaceContentPrivate *other) const; + + Q_DEFINE_CONTENT_PRIVATE_HELPER(QPlaceReview, QPlaceContent::ReviewType); + + QDateTime dateTime; + QString text; + QString language; + qreal rating; + QString reviewId; + QString title; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacesearchreply.cpp b/src/location/places/qplacesearchreply.cpp new file mode 100644 index 0000000..06eef4e --- /dev/null +++ b/src/location/places/qplacesearchreply.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceSearchReplyPrivate : public QPlaceReplyPrivate +{ +public: + QPlaceSearchReplyPrivate(){} + QList results; + QPlaceSearchRequest searchRequest; + QPlaceSearchRequest previousPageRequest; + QPlaceSearchRequest nextPageRequest; +}; + +/*! + \class QPlaceSearchReply + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-replies + \since 5.6 + + \brief The QPlaceSearchReply class manages a place search operation started by an + instance of QPlaceManager. + + See \l {Discovery/Search} for an example on how to use a search reply. + \sa QPlaceSearchRequest, QPlaceManager +*/ + +/*! + Constructs a search reply with a given \a parent. +*/ +QPlaceSearchReply::QPlaceSearchReply(QObject *parent) + : QPlaceReply(new QPlaceSearchReplyPrivate, parent) +{ +} + +/*! + Destroys the search reply. +*/ +QPlaceSearchReply::~QPlaceSearchReply() +{ +} + +/*! + Returns the type of reply. +*/ +QPlaceReply::Type QPlaceSearchReply::type() const +{ + return QPlaceReply::SearchReply; +} + + /*! + Returns a list of search results; +*/ +QList QPlaceSearchReply::results() const +{ + Q_D(const QPlaceSearchReply); + return d->results; +} + +/*! + Sets the list of search \a results. +*/ +void QPlaceSearchReply::setResults(const QList &results) +{ + Q_D(QPlaceSearchReply); + d->results = results; +} + +/*! + Returns the search request that was used to generate this reply. +*/ +QPlaceSearchRequest QPlaceSearchReply::request() const +{ + Q_D(const QPlaceSearchReply); + return d->searchRequest; +} + +/*! + Returns a place search request which can be used to request the previous page of search + results. An empty place search request is returned if there is no previous page of results. + + \sa nextPageRequest(), setPreviousPageRequest() +*/ +QPlaceSearchRequest QPlaceSearchReply::previousPageRequest() const +{ + Q_D(const QPlaceSearchReply); + return d->previousPageRequest; +} + +/*! + Returns a place search request which can be used to request the next page of search results. An + empty place search request is returned if there is no next page of results. + + \sa previousPageRequest(), setNextPageRequest() +*/ +QPlaceSearchRequest QPlaceSearchReply::nextPageRequest() const +{ + Q_D(const QPlaceSearchReply); + return d->nextPageRequest; +} + +/*! + Sets the search \a request used to generate this reply. +*/ +void QPlaceSearchReply::setRequest(const QPlaceSearchRequest &request) +{ + Q_D(QPlaceSearchReply); + d->searchRequest = request; +} + +/*! + Sets the previous page of search results request to \a previous. + + \sa previousPageRequest() +*/ +void QPlaceSearchReply::setPreviousPageRequest(const QPlaceSearchRequest &previous) +{ + Q_D(QPlaceSearchReply); + d->previousPageRequest = previous; +} + +/*! + Sets the next page of search results request to \a next. + + \sa nextPageRequest() +*/ +void QPlaceSearchReply::setNextPageRequest(const QPlaceSearchRequest &next) +{ + Q_D(QPlaceSearchReply); + d->nextPageRequest = next; +} + +QT_END_NAMESPACE diff --git a/src/location/places/qplacesearchreply.h b/src/location/places/qplacesearchreply.h new file mode 100644 index 0000000..52c8889 --- /dev/null +++ b/src/location/places/qplacesearchreply.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESEARCHREPLY_H +#define QPLACESEARCHREPLY_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceSearchResult; +class QPlaceSearchReplyPrivate; + +class Q_LOCATION_EXPORT QPlaceSearchReply : public QPlaceReply +{ + Q_OBJECT +public: + explicit QPlaceSearchReply(QObject *parent = Q_NULLPTR); + ~QPlaceSearchReply(); + + QPlaceReply::Type type() const; + + QList results() const; + QPlaceSearchRequest request() const; + + QPlaceSearchRequest previousPageRequest() const; + QPlaceSearchRequest nextPageRequest() const; + +protected: + void setResults(const QList &results); + void setRequest(const QPlaceSearchRequest &request); + void setPreviousPageRequest(const QPlaceSearchRequest &previous); + void setNextPageRequest(const QPlaceSearchRequest &next); + +private: + Q_DISABLE_COPY(QPlaceSearchReply) + Q_DECLARE_PRIVATE(QPlaceSearchReply) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacesearchrequest.cpp b/src/location/places/qplacesearchrequest.cpp new file mode 100644 index 0000000..c2d993e --- /dev/null +++ b/src/location/places/qplacesearchrequest.cpp @@ -0,0 +1,440 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacesearchrequest.h" +#include "qgeocoordinate.h" +#include "qgeoshape.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceSearchRequestPrivate : public QSharedData +{ +public: + QPlaceSearchRequestPrivate(); + QPlaceSearchRequestPrivate(const QPlaceSearchRequestPrivate &other); + ~QPlaceSearchRequestPrivate(); + + QPlaceSearchRequestPrivate &operator=(const QPlaceSearchRequestPrivate &other); + bool operator==(const QPlaceSearchRequestPrivate &other) const; + + void clear(); + + QString searchTerm; + QList categories; + QGeoShape searchArea; + QString recommendationId; + QLocation::VisibilityScope visibilityScope; + QPlaceSearchRequest::RelevanceHint relevanceHint; + int limit; + QVariant searchContext; +}; + +QPlaceSearchRequestPrivate::QPlaceSearchRequestPrivate() +: QSharedData(), + visibilityScope(QLocation::UnspecifiedVisibility), + relevanceHint(QPlaceSearchRequest::UnspecifiedHint), + limit(-1) +{ +} + +QPlaceSearchRequestPrivate::QPlaceSearchRequestPrivate(const QPlaceSearchRequestPrivate &other) + : QSharedData(other), + searchTerm(other.searchTerm), + categories(other.categories), + searchArea(other.searchArea), + recommendationId(other.recommendationId), + visibilityScope(other.visibilityScope), + relevanceHint(other.relevanceHint), + limit(other.limit), + searchContext(other.searchContext) +{ +} + +QPlaceSearchRequestPrivate::~QPlaceSearchRequestPrivate() +{ +} + +QPlaceSearchRequestPrivate &QPlaceSearchRequestPrivate::operator=(const QPlaceSearchRequestPrivate &other) +{ + if (this != &other) { + searchTerm = other.searchTerm; + categories = other.categories; + searchArea = other.searchArea; + recommendationId = other.recommendationId; + visibilityScope = other.visibilityScope; + relevanceHint = other.relevanceHint; + limit = other.limit; + searchContext = other.searchContext; + } + + return *this; +} + +bool QPlaceSearchRequestPrivate::operator==(const QPlaceSearchRequestPrivate &other) const +{ + return searchTerm == other.searchTerm && + categories == other.categories && + searchArea == other.searchArea && + recommendationId == other.recommendationId && + visibilityScope == other.visibilityScope && + relevanceHint == other.relevanceHint && + limit == other.limit && + searchContext == other.searchContext; +} + +void QPlaceSearchRequestPrivate::clear() +{ + limit = -1; + searchTerm.clear(); + categories.clear(); + searchArea = QGeoShape(); + recommendationId.clear(); + visibilityScope = QLocation::UnspecifiedVisibility; + relevanceHint = QPlaceSearchRequest::UnspecifiedHint; + searchContext.clear(); +} + +/*! + \class QPlaceSearchRequest + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-requests + \since 5.6 + + \brief The QPlaceSearchRequest class represents the set of parameters for a search request. + + A typical search request may look like the following: + \snippet places/requesthandler.h Search request + + Note that specifying a search center can be done by setting a circular search area that has + a center but no radius. The default radius is set to -1, which indicates an undefined radius. The provider will + interpret this as being free to choose its own default radius. + + The QPlaceSearchRequest is primarily used with the QPlaceManager to + \l {QPlaceManager::search()} {search for places}, however it is also + used to provide parameters for \l {QPlaceManager::searchSuggestions()}{generating search term suggestions}. + Note that in this context only some of the parameters may be relevant. For example, the search area + is useful in narrowing down relevant search suggestions, while other parameters such as relevance hint + are not so applicable. + + Also be aware that providers may vary by which parameters they support for example some providers may not support + paging while others do, some providers may honor relevance hints while others may completely ignore them, + see the \l {Qt Location#Plugin References and Parameters}{plugin documentation} for more + details. +*/ + +/*! + \enum QPlaceSearchRequest::RelevanceHint + + Defines hints to help rank place results. + \value UnspecifiedHint + No explicit hint has been specified. + \value DistanceHint + Distance to a search center is relevant for the user. Closer places + are more highly weighted. This hint is only useful + if a circular search area is used in the query. + \value LexicalPlaceNameHint + Alphabetic ordering of places according to name is relevant to the user. +*/ + +/*! + Default constructor. Constructs an new request object. +*/ +QPlaceSearchRequest::QPlaceSearchRequest() + : d_ptr(new QPlaceSearchRequestPrivate()) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QPlaceSearchRequest::QPlaceSearchRequest(const QPlaceSearchRequest &other) + : d_ptr(other.d_ptr) +{ +} + +/*! + Destroys the request object. +*/ +QPlaceSearchRequest::~QPlaceSearchRequest() +{ +} + +/*! + Assigns \a other to this search request and returns a reference + to this search request. +*/ +QPlaceSearchRequest &QPlaceSearchRequest::operator= (const QPlaceSearchRequest & other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns true if \a other is equal to this search request, + otherwise returns false. +*/ +bool QPlaceSearchRequest::operator== (const QPlaceSearchRequest &other) const +{ + Q_D(const QPlaceSearchRequest); + return *d == *other.d_func(); +} + +/*! + Returns true if \a other is not equal to this search request, + otherwise returns false. +*/ +bool QPlaceSearchRequest::operator!= (const QPlaceSearchRequest &other) const +{ + Q_D(const QPlaceSearchRequest); + return !(*d == *other.d_func()); +} + +/*! + Returns the search term. +*/ +QString QPlaceSearchRequest::searchTerm() const +{ + Q_D(const QPlaceSearchRequest); + return d->searchTerm; +} + +/*! + Sets the search \a term. +*/ +void QPlaceSearchRequest::setSearchTerm(const QString &term) +{ + Q_D(QPlaceSearchRequest); + d->searchTerm = term; +} + +/*! + Return the categories to be used in the search request. + Places need only to belong to one of the categories + to be considered a match by the request. +*/ +QList QPlaceSearchRequest::categories() const +{ + Q_D(const QPlaceSearchRequest); + return d->categories; +} + +/*! + Sets the search request to search by a single \a category + + \sa setCategories() +*/ +void QPlaceSearchRequest::setCategory(const QPlaceCategory &category) +{ + Q_D(QPlaceSearchRequest); + d->categories.clear(); + + if (!category.categoryId().isEmpty()) + d->categories.append(category); +} + +/*! + Sets the search request to search from the list of given \a categories. + Any places returned during the search will match at least one of the \a + categories. + + \sa setCategory() +*/ +void QPlaceSearchRequest::setCategories(const QList &categories) +{ + Q_D(QPlaceSearchRequest); + d->categories = categories; +} + +/*! + Returns the search area which will be used to limit search results. The default search area is + an invalid QGeoShape, indicating that no specific search area is defined. +*/ +QGeoShape QPlaceSearchRequest::searchArea() const +{ + Q_D(const QPlaceSearchRequest); + return d->searchArea; +} + +/*! + Sets the search request to search within the given \a area. +*/ +void QPlaceSearchRequest::setSearchArea(const QGeoShape &area) +{ + Q_D(QPlaceSearchRequest); + d->searchArea = area; +} + +/*! + Returns the place id which will be used to search for recommendations + for similar places. +*/ +QString QPlaceSearchRequest::recommendationId() const +{ + Q_D(const QPlaceSearchRequest); + return d->recommendationId; +} + +/*! + Sets the \a placeId which will be used to search for recommendations. +*/ +void QPlaceSearchRequest::setRecommendationId(const QString &placeId) +{ + Q_D(QPlaceSearchRequest); + d->recommendationId = placeId; +} + +/*! + Returns backend specific additional search context associated with this place search request. + The search context is typically set as part of a + \l {QPlaceSearchResult::ProposedSearchResult}{proposed search results}. +*/ +QVariant QPlaceSearchRequest::searchContext() const +{ + Q_D(const QPlaceSearchRequest); + return d->searchContext; +} + +/*! + Sets the search context to \a context. + + \note This method is intended to be used by geo service plugins when returning search results + of type \l QPlaceSearchResult::ProposedSearchResult. + + The search context is used by backends to store additional search context related to the search + request. Other relevant fields should also be filled in. For example, if the search context + encodes a text search the search term should also be set with \l setSearchTerm(). The search + context allows additional search context to be kept which is not directly accessible via the + Qt Location API. + + The search context can be of any type storable in a QVariant. The value of the search context + is not intended to be use directly by applications. +*/ +void QPlaceSearchRequest::setSearchContext(const QVariant &context) +{ + Q_D(QPlaceSearchRequest); + d->searchContext = context; +} + +/*! + Returns the visibility scope used when searching for places. The default value is + QLocation::UnspecifiedVisibility meaning that no explicit scope has been assigned. + Places of any scope may be returned during the search. +*/ +QLocation::VisibilityScope QPlaceSearchRequest::visibilityScope() const +{ + Q_D(const QPlaceSearchRequest); + return d->visibilityScope; +} + +/*! + Sets the visibility \a scope used when searching for places. +*/ +void QPlaceSearchRequest::setVisibilityScope(QLocation::VisibilityScope scope) +{ + Q_D(QPlaceSearchRequest); + d->visibilityScope = scope; +} + +/*! + Returns the relevance hint of the request. The hint is given to the provider + to help but not dictate the ranking of results. For example providing a distance hint + may give closer places a higher ranking but it doesn't necessarily mean + that he results will be ordered strictly according to distance. +*/ +QPlaceSearchRequest::RelevanceHint QPlaceSearchRequest::relevanceHint() const +{ + Q_D(const QPlaceSearchRequest); + return d->relevanceHint; +} + +/*! + Sets the relevance \a hint to be used when searching for a place. +*/ +void QPlaceSearchRequest::setRelevanceHint(QPlaceSearchRequest::RelevanceHint hint) +{ + Q_D(QPlaceSearchRequest); + d->relevanceHint = hint; +} + +/*! + Returns the maximum number of search results to retrieve. + + A negative value for limit means that it is undefined. It is left up to the backend + provider to choose an appropriate number of results to return. The default limit is -1. +*/ +int QPlaceSearchRequest::limit() const +{ + Q_D(const QPlaceSearchRequest); + return d->limit; +} + +/*! + Set the maximum number of search results to retrieve to \a limit. +*/ +void QPlaceSearchRequest::setLimit(int limit) +{ + Q_D(QPlaceSearchRequest); + d->limit = limit; +} + +/*! + Clears the search request. +*/ +void QPlaceSearchRequest::clear() +{ + Q_D(QPlaceSearchRequest); + d->clear(); +} + +inline QPlaceSearchRequestPrivate *QPlaceSearchRequest::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QPlaceSearchRequestPrivate *QPlaceSearchRequest::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +QT_END_NAMESPACE diff --git a/src/location/places/qplacesearchrequest.h b/src/location/places/qplacesearchrequest.h new file mode 100644 index 0000000..0965450 --- /dev/null +++ b/src/location/places/qplacesearchrequest.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESEARCHREQUEST_H +#define QPLACESEARCHREQUEST_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoShape; +class QPlaceSearchRequestPrivate; + +class Q_LOCATION_EXPORT QPlaceSearchRequest +{ +public: + enum RelevanceHint { + UnspecifiedHint, + DistanceHint, + LexicalPlaceNameHint + }; + + QPlaceSearchRequest(); + QPlaceSearchRequest(const QPlaceSearchRequest &other); + + + QPlaceSearchRequest &operator=(const QPlaceSearchRequest &other); + + bool operator==(const QPlaceSearchRequest &other) const; + bool operator!=(const QPlaceSearchRequest &other) const; + + ~QPlaceSearchRequest(); + + QString searchTerm() const; + void setSearchTerm(const QString &term); + + QList categories() const; + void setCategory(const QPlaceCategory &category); + void setCategories(const QList &categories); + + QGeoShape searchArea() const; + void setSearchArea(const QGeoShape &area); + + QString recommendationId() const; + void setRecommendationId(const QString &recommendationId); + + QVariant searchContext() const; + void setSearchContext(const QVariant &context); + + QLocation::VisibilityScope visibilityScope() const; + void setVisibilityScope(QLocation::VisibilityScope visibilityScopes); + + RelevanceHint relevanceHint() const; + void setRelevanceHint(RelevanceHint hint); + + int limit() const; + void setLimit(int limit); + + void clear(); + +private: + QSharedDataPointer d_ptr; + inline QPlaceSearchRequestPrivate *d_func(); + inline const QPlaceSearchRequestPrivate *d_func() const; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceSearchRequest::RelevanceHint) + +#endif // QPLACESEARCHQUERY_H diff --git a/src/location/places/qplacesearchresult.cpp b/src/location/places/qplacesearchresult.cpp new file mode 100644 index 0000000..d8ddc50 --- /dev/null +++ b/src/location/places/qplacesearchresult.cpp @@ -0,0 +1,214 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacesearchresult.h" +#include "qplacesearchresult_p.h" +#include "qplaceresult.h" +#include + +QT_USE_NAMESPACE + +template<> QPlaceSearchResultPrivate *QSharedDataPointer::clone() +{ + return d->clone(); +} + +inline QPlaceSearchResultPrivate *QPlaceSearchResult::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QPlaceSearchResultPrivate *QPlaceSearchResult::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +bool QPlaceSearchResultPrivate::compare(const QPlaceSearchResultPrivate *other) const +{ + return title == other->title + && icon == other->icon; +} + +/*! + \class QPlaceSearchResult + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceSearchResult class is the base class for search results. + + A list of search results can be retrieved from the QPlaceSearchReply after it has + successfully completed the request. Common to all search results are the + \l {QPlaceSearchResult::title()} {title} and \l {QPlaceSearchResult::icon()}{icon}, + which can be used to present the search result to the user. + + The intended usage is that depending on the \l {QPlaceSearchResult::type()} {type}, + the search result can be converted to a more detailed subclass like so: + + \snippet places/requesthandler.h Convert search result + + The implementation is handled in such a way that object slicing is not an issue. + It is not expected that client applications or backend plugins instantiate + a QPlaceSearchResult directly, but rather client applications simply convert + to search result subclasses and backend plugins only instantiate subclasses. + + \sa QPlaceResult +*/ + +/*! + \enum QPlaceSearchResult::SearchResultType + + Defines the type of search result + + \value UnknownSearchResult The contents of the search result are unknown. + \value PlaceResult The search result contains a place. + \value ProposedSearchResult The search result contains a proposed search which may be relevant. +*/ + +/*! + Constructs a new search result. +*/ +QPlaceSearchResult::QPlaceSearchResult() + : d_ptr(new QPlaceSearchResultPrivate) +{ +} + +/*! + Constructs a copy of \a other +*/ +QPlaceSearchResult::QPlaceSearchResult(const QPlaceSearchResult &other) + :d_ptr(other.d_ptr) +{ +} + +/*! + Destroys the search result. +*/ +QPlaceSearchResult::~QPlaceSearchResult() +{ +} + +/*! + Assigns \a other to this search result and returns a reference to this + search result. +*/ +QPlaceSearchResult &QPlaceSearchResult::operator =(const QPlaceSearchResult &other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns true if \a other is equal to this search result, otherwise + returns false. +*/ +bool QPlaceSearchResult::operator==(const QPlaceSearchResult &other) const +{ + // An unknown object is only equal to another unknown search result + if (!d_ptr) + return !other.d_ptr; + + if (type() != other.type()) + return false; + + return d_ptr->compare(other.d_ptr); +} + +/*! + \fn bool QPlaceSearchResult::operator!=(const QPlaceSearchResult &other) const + Returns true if \a other not equal to this search result, otherwise + returns false. +*/ + +/*! + Returns the result type. +*/ +QPlaceSearchResult::SearchResultType QPlaceSearchResult::type() const +{ + if (!d_ptr) + return UnknownSearchResult; + return d_ptr->type(); +} + +/*! + Returns the title of the search result. This string can be used to display the search result + to the user. +*/ +QString QPlaceSearchResult::title() const +{ + Q_D(const QPlaceSearchResult); + return d->title; +} + +/*! + Sets the title of the search result to \a title. +*/ +void QPlaceSearchResult::setTitle(const QString &title) +{ + Q_D(QPlaceSearchResult); + d->title = title; +} + +/*! + Returns an icon that can be used to represent the search result. +*/ +QPlaceIcon QPlaceSearchResult::icon() const +{ + Q_D(const QPlaceSearchResult); + return d->icon; +} + +/*! + Sets the icon of the search result to \a icon. +*/ +void QPlaceSearchResult::setIcon(const QPlaceIcon &icon) +{ + Q_D(QPlaceSearchResult); + d->icon = icon; +} + +/*! + \internal + Constructs a new search result from the given pointer \a d. +*/ +QPlaceSearchResult::QPlaceSearchResult(QPlaceSearchResultPrivate *d) + :d_ptr(d) +{ +} diff --git a/src/location/places/qplacesearchresult.h b/src/location/places/qplacesearchresult.h new file mode 100644 index 0000000..436060d --- /dev/null +++ b/src/location/places/qplacesearchresult.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESEARCHRESULT_H +#define QPLACESEARCHRESULT_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#define Q_DECLARE_SEARCHRESULT_D_FUNC(Class) \ + inline Class##Private *d_func(); \ + inline const Class##Private *d_func() const;\ + friend class Class##Private; + +#define Q_DECLARE_SEARCHRESULT_COPY_CTOR(Class) \ + Class(const QPlaceSearchResult &other); + +class QPlaceSearchRequest; +class QPlaceSearchResultPrivate; +class QPlaceIcon; + +class Q_LOCATION_EXPORT QPlaceSearchResult +{ +public: + QPlaceSearchResult(); + QPlaceSearchResult(const QPlaceSearchResult &other); + + virtual ~QPlaceSearchResult(); + + QPlaceSearchResult &operator=(const QPlaceSearchResult &other); + + bool operator==(const QPlaceSearchResult &other) const; + bool operator!=(const QPlaceSearchResult &other) const { + return !(other == *this); + } + + enum SearchResultType { + UnknownSearchResult = 0, + PlaceResult, + ProposedSearchResult + }; + + SearchResultType type() const; + + QString title() const; + void setTitle(const QString &title); + + QPlaceIcon icon() const; + void setIcon(const QPlaceIcon &icon); + +protected: + explicit QPlaceSearchResult(QPlaceSearchResultPrivate *d); + QSharedDataPointer d_ptr; + +private: + inline QPlaceSearchResultPrivate *d_func(); + inline const QPlaceSearchResultPrivate *d_func() const; + + friend class QPlaceSearchResultPrivate; +}; + +Q_DECLARE_TYPEINFO(QPlaceSearchResult, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceSearchResult) +Q_DECLARE_METATYPE(QPlaceSearchResult::SearchResultType) + +#endif // QPLACESEARCHRESULT_H diff --git a/src/location/places/qplacesearchresult_p.h b/src/location/places/qplacesearchresult_p.h new file mode 100644 index 0000000..e7c1aaf --- /dev/null +++ b/src/location/places/qplacesearchresult_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESEARCHRESULT_P_H +#define QPLACESEARCHRESULT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qplacesearchresult.h" +#include "qplacesearchrequest.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +// defines must be in sync with class below +#define Q_IMPLEMENT_SEARCHRESULT_D_FUNC(Class) \ + Class##Private *Class::d_func() { return reinterpret_cast(d_ptr.data()); } \ + const Class##Private *Class::d_func() const { return reinterpret_cast(d_ptr.constData()); } \ + +#define Q_IMPLEMENT_SEARCHRESULT_COPY_CTOR(Class) \ + Class::Class(const QPlaceSearchResult &other) : QPlaceSearchResult() { Class##Private::copyIfPossible(d_ptr, other); } + +#define Q_DEFINE_SEARCHRESULT_PRIVATE_HELPER(Class, ResultType) \ + virtual QPlaceSearchResultPrivate *clone() const Q_DECL_OVERRIDE { return new Class##Private(*this); } \ + virtual QPlaceSearchResult::SearchResultType type() const Q_DECL_OVERRIDE {return ResultType;} \ + static void copyIfPossible(QSharedDataPointer &d_ptr, const QPlaceSearchResult &other) \ + { \ + if (other.type() == ResultType) \ + d_ptr = extract_d(other); \ + else \ + d_ptr = new Class##Private; \ + } + +class QPlaceSearchResultPrivate : public QSharedData +{ +public: + QPlaceSearchResultPrivate() {} + virtual ~QPlaceSearchResultPrivate() {} + + virtual bool compare(const QPlaceSearchResultPrivate *other) const; + + static const QSharedDataPointer + &extract_d(const QPlaceSearchResult &other) { return other.d_ptr; } + + virtual QPlaceSearchResultPrivate *clone() const { return new QPlaceSearchResultPrivate(*this); } + virtual QPlaceSearchResult::SearchResultType type() const { return QPlaceSearchResult::UnknownSearchResult; } + static void copyIfPossible(QSharedDataPointer &d_ptr, const QPlaceSearchResult &other) + { + if (other.type() == QPlaceSearchResult::UnknownSearchResult) + d_ptr = extract_d(other); + else + d_ptr = new QPlaceSearchResultPrivate; + } + + QString title; + QPlaceIcon icon; +}; + +template<> QPlaceSearchResultPrivate *QSharedDataPointer::clone(); + +QT_END_NAMESPACE + +#endif // QPLACESEARCHRESULT_P_H diff --git a/src/location/places/qplacesearchsuggestionreply.cpp b/src/location/places/qplacesearchsuggestionreply.cpp new file mode 100644 index 0000000..9bfc5f0 --- /dev/null +++ b/src/location/places/qplacesearchsuggestionreply.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacesearchsuggestionreply.h" +#include "qplacereply_p.h" + +QT_BEGIN_NAMESPACE + +class QPlaceSearchSuggestionReplyPrivate : public QPlaceReplyPrivate +{ +public: + QPlaceSearchSuggestionReplyPrivate(){} + QStringList suggestions; +}; + +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +/*! + \class QPlaceSearchSuggestionReply + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-replies + \since 5.6 + + \brief The QPlaceSearchSuggestionReply class manages a search suggestion operation started by an + instance of QPlaceManager. + + On successful completion of the operation, the reply will contain a list of search term + suggestions. + See \l {Search Suggestions} for an example on how to use a search suggestion reply. + + \sa QPlaceManager +*/ + +/*! + Constructs a search suggestion reply with a given \a parent. +*/ +QPlaceSearchSuggestionReply::QPlaceSearchSuggestionReply(QObject *parent) + : QPlaceReply(new QPlaceSearchSuggestionReplyPrivate, parent) +{ +} + +/*! + Destroys the reply. +*/ +QPlaceSearchSuggestionReply::~QPlaceSearchSuggestionReply() +{ +} + +/*! + Returns the search term suggestions. +*/ +QStringList QPlaceSearchSuggestionReply::suggestions() const +{ + Q_D(const QPlaceSearchSuggestionReply); + return d->suggestions; +} + +/*! + Returns type of reply. +*/ +QPlaceReply::Type QPlaceSearchSuggestionReply::type() const +{ + return QPlaceReply::SearchSuggestionReply; +} + +/*! + Sets the search term \a suggestions. +*/ +void QPlaceSearchSuggestionReply::setSuggestions(const QStringList &suggestions) +{ + Q_D(QPlaceSearchSuggestionReply); + d->suggestions = suggestions; +} diff --git a/src/location/places/qplacesearchsuggestionreply.h b/src/location/places/qplacesearchsuggestionreply.h new file mode 100644 index 0000000..3909b14 --- /dev/null +++ b/src/location/places/qplacesearchsuggestionreply.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESEARCHSUGGESTIONREPLY_H +#define QPLACESEARCHSUGGESTIONREPLY_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceSearchSuggestionReplyPrivate; + +class Q_LOCATION_EXPORT QPlaceSearchSuggestionReply : public QPlaceReply +{ + Q_OBJECT +public: + explicit QPlaceSearchSuggestionReply(QObject *parent = Q_NULLPTR); + ~QPlaceSearchSuggestionReply(); + + QStringList suggestions() const; + Type type() const; + +protected: + void setSuggestions(const QStringList &suggestions); + +private: + Q_DISABLE_COPY(QPlaceSearchSuggestionReply) + Q_DECLARE_PRIVATE(QPlaceSearchSuggestionReply) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/qplacesupplier.cpp b/src/location/places/qplacesupplier.cpp new file mode 100644 index 0000000..aa9e122 --- /dev/null +++ b/src/location/places/qplacesupplier.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacesupplier.h" +#include "qplacesupplier_p.h" + +QT_USE_NAMESPACE + +QPlaceSupplierPrivate::QPlaceSupplierPrivate() : QSharedData() +{ +} + +QPlaceSupplierPrivate::QPlaceSupplierPrivate(const QPlaceSupplierPrivate &other) + : QSharedData() +{ + this->name = other.name; + this->supplierId = other.supplierId; + this->url = other.url; + this->icon = other.icon; +} + +QPlaceSupplierPrivate::~QPlaceSupplierPrivate() +{ +} + +bool QPlaceSupplierPrivate::operator==(const QPlaceSupplierPrivate &other) const +{ + return ( + this->name == other.name + && this->supplierId == other.supplierId + && this->url == other.url + && this->icon == other.icon + ); +} + +bool QPlaceSupplierPrivate::isEmpty() const +{ + return (name.isEmpty() + && supplierId.isEmpty() + && url.isEmpty() + && icon.isEmpty() + ); +} + +/*! + \class QPlaceSupplier + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceSupplier class represents a supplier of a place or content associated + with a place. + + Each instance represents a set of data about a supplier, which can include + supplier's name, url and icon. The supplier is typically a business or organization. + + Note: The Places API only supports suppliers as 'retrieve-only' objects. Submitting + suppliers to a provider is not a supported use case. +*/ + +/*! + Constructs a new supplier object. +*/ +QPlaceSupplier::QPlaceSupplier() + : d(new QPlaceSupplierPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QPlaceSupplier::QPlaceSupplier(const QPlaceSupplier &other) + :d(other.d) +{ +} + +/*! + Destroys the supplier object. +*/ +QPlaceSupplier::~QPlaceSupplier() +{ +} + +/*! + Assigns \a other to this supplier and returns a reference to this + supplier. +*/ +QPlaceSupplier &QPlaceSupplier::operator=(const QPlaceSupplier &other) +{ + if (this == &other) + return *this; + + d = other.d; + return *this; +} + +/*! + Returns true if this supplier is equal to \a other, + otherwise returns false. +*/ +bool QPlaceSupplier::operator==(const QPlaceSupplier &other) const +{ + return (*(d.constData()) == *(other.d.constData())); +} + +/*! + \fn QPlaceSupplier::operator!=(const QPlaceSupplier &other) const + + Returns true if this supplier is not equal to \a other, + otherwise returns false. +*/ + +/*! + Returns the name of the supplier which can be displayed to the user. + + The name can potentially be localized. The language is dependent on the + entity that sets it, typically this is the QPlaceManager. + The QPlaceManager::locales() field defines what language is used. +*/ +QString QPlaceSupplier::name() const +{ + return d->name; +} + +/*! + Sets the \a name of the supplier. +*/ +void QPlaceSupplier::setName(const QString &name) +{ + d->name = name; +} + +/*! + Returns the identifier of the supplier. The identifier is unique + to the manager backend which provided the supplier and is generally + not suitable for displaying to the user. +*/ +QString QPlaceSupplier::supplierId() const +{ + return d->supplierId; +} + +/*! + Sets the \a identifier of the supplier. +*/ +void QPlaceSupplier::setSupplierId(const QString &identifier) +{ + d->supplierId = identifier; +} + +/*! + Returns the URL of the supplier's website. +*/ +QUrl QPlaceSupplier::url() const +{ + return d->url; +} + +/*! + Sets the \a url of the supplier's website. +*/ +void QPlaceSupplier::setUrl(const QUrl &url) +{ + d->url = url; +} + +/*! + Returns the icon of the supplier. +*/ +QPlaceIcon QPlaceSupplier::icon() const +{ + return d->icon; +} + +/*! + Sets the \a icon of the supplier. +*/ +void QPlaceSupplier::setIcon(const QPlaceIcon &icon) +{ + d->icon = icon; +} + +/*! + Returns true if all fields of the place supplier are 0; otherwise returns false. +*/ +bool QPlaceSupplier::isEmpty() const +{ + return d->isEmpty(); +} diff --git a/src/location/places/qplacesupplier.h b/src/location/places/qplacesupplier.h new file mode 100644 index 0000000..21fcf57 --- /dev/null +++ b/src/location/places/qplacesupplier.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESUPPLIER_H +#define QPLACESUPPLIER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QUrl; +class QPlaceSupplierPrivate; + +class Q_LOCATION_EXPORT QPlaceSupplier +{ +public: + QPlaceSupplier(); + QPlaceSupplier(const QPlaceSupplier &other); + ~QPlaceSupplier(); + + QPlaceSupplier &operator=(const QPlaceSupplier &other); + + bool operator==(const QPlaceSupplier &other) const; + bool operator!=(const QPlaceSupplier &other) const { + return !(other == *this); + } + + QString name() const; + void setName(const QString &data); + + QString supplierId() const; + void setSupplierId(const QString &identifier); + + QUrl url() const; + void setUrl(const QUrl &data); + + QPlaceIcon icon() const; + void setIcon(const QPlaceIcon &icon); + + bool isEmpty() const; + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceSupplier) + +#endif // QPLACESUPPLIER_H diff --git a/src/location/places/qplacesupplier_p.h b/src/location/places/qplacesupplier_p.h new file mode 100644 index 0000000..ab09349 --- /dev/null +++ b/src/location/places/qplacesupplier_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESUPPLIER_P_H +#define QPLACESUPPLIER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include "qplaceicon.h" + +QT_BEGIN_NAMESPACE + +class QPlaceSupplierPrivate : public QSharedData +{ +public: + QPlaceSupplierPrivate(); + QPlaceSupplierPrivate(const QPlaceSupplierPrivate &other); + + ~QPlaceSupplierPrivate(); + + bool operator==(const QPlaceSupplierPrivate &other) const; + + bool isEmpty() const; + + QString name; + QString supplierId; + QUrl url; + QPlaceIcon icon; +}; + +QT_END_NAMESPACE + +#endif // QPLACESUPPLIER_P_H diff --git a/src/location/places/qplaceuser.cpp b/src/location/places/qplaceuser.cpp new file mode 100644 index 0000000..fbfba62 --- /dev/null +++ b/src/location/places/qplaceuser.cpp @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceuser.h" +#include "qplaceuser_p.h" + +QT_USE_NAMESPACE + +QPlaceUserPrivate::QPlaceUserPrivate() + : QSharedData() +{ +} + +QPlaceUserPrivate::QPlaceUserPrivate(const QPlaceUserPrivate &other) + : QSharedData(), userId(other.userId), name(other.name) +{ +} + +QPlaceUserPrivate::~QPlaceUserPrivate() +{ +} + +bool QPlaceUserPrivate::operator==(const QPlaceUserPrivate &other) const +{ + return userId == other.userId && name == other.name; +} + +/*! + \class QPlaceUser + \inmodule QtLocation + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.6 + + \brief The QPlaceUser class represents an individual user. +*/ + +/*! + Constructs a new user object. +*/ +QPlaceUser::QPlaceUser() + : d(new QPlaceUserPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QPlaceUser::QPlaceUser(const QPlaceUser &other) + :d(other.d) +{ +} + +/*! + Destroys the user object. +*/ +QPlaceUser::~QPlaceUser() +{ +} + +/*! + Assigns \a other to this user and returns a reference to this user. +*/ +QPlaceUser &QPlaceUser::operator=(const QPlaceUser &other) +{ + if (this == &other) + return *this; + + d = other.d; + return *this; +} + +/*! + \fn bool QPlaceUser::operator!=(const QPlaceUser &other) const + + Returns true if \a other is not equal to this user, + otherwise returns false. +*/ + +/*! + Returns true if this user is equal to \a other. + Otherwise returns false. +*/ +bool QPlaceUser::operator==(const QPlaceUser &other) const +{ + return (*d) == *(other.d); +} + +/*! + Returns the identifier of the user. +*/ +QString QPlaceUser::userId() const +{ + return d->userId; +} + +/*! + Sets the \a identifier of the user. +*/ +void QPlaceUser::setUserId(const QString &identifier) +{ + d->userId = identifier; +} + +/*! + Returns the name of the user. +*/ +QString QPlaceUser::name() const +{ + return d->name; +} + +/*! + Sets the \a name of the user. +*/ + +void QPlaceUser::setName(const QString &name) +{ + d->name = name; +} diff --git a/src/location/places/qplaceuser.h b/src/location/places/qplaceuser.h new file mode 100644 index 0000000..1709783 --- /dev/null +++ b/src/location/places/qplaceuser.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEUSER_H +#define QPLACEUSER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceUserPrivate; + +class Q_LOCATION_EXPORT QPlaceUser +{ +public: + QPlaceUser(); + QPlaceUser(const QPlaceUser &other); + ~QPlaceUser(); + + QPlaceUser &operator=(const QPlaceUser &other); + + bool operator==(const QPlaceUser &other) const; + bool operator!=(const QPlaceUser &other) const { + return !(other == *this); + } + + QString userId() const; + void setUserId(const QString &identifier); + + QString name() const; + void setName(const QString &name); + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPlaceUser) + +#endif diff --git a/src/location/places/qplaceuser_p.h b/src/location/places/qplaceuser_p.h new file mode 100644 index 0000000..f49110b --- /dev/null +++ b/src/location/places/qplaceuser_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEUSER_P_H +#define QPLACEUSER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceUserPrivate : public QSharedData +{ +public: + QPlaceUserPrivate(); + QPlaceUserPrivate(const QPlaceUserPrivate &other); + + ~QPlaceUserPrivate(); + + bool operator==(const QPlaceUserPrivate &other) const; + + QString userId; + QString name; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/places/unsupportedreplies_p.h b/src/location/places/unsupportedreplies_p.h new file mode 100644 index 0000000..b23a3b3 --- /dev/null +++ b/src/location/places/unsupportedreplies_p.h @@ -0,0 +1,227 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef UNSUPPORTEDREPLIES_P_H +#define UNSUPPORTEDREPLIES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qplacedetailsreply.h" +#include "qplacecontentreply.h" +#include "qplacesearchreply.h" +#include "qplacesearchsuggestionreply.h" +#include "qplaceidreply.h" + +#include "qplacematchreply.h" +#include "qplacemanagerengine.h" + +class Q_LOCATION_EXPORT QPlaceDetailsReplyUnsupported : public QPlaceDetailsReply +{ + Q_OBJECT + +public: + QPlaceDetailsReplyUnsupported(QPlaceManagerEngine *parent) + : QPlaceDetailsReply(parent) + { + setError(QPlaceReply::UnsupportedError, + QStringLiteral("Getting place details is not supported.")); + setFinished(true); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(parent, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this), + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + QMetaObject::invokeMethod(parent, "finished", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this)); + } +}; + +class Q_LOCATION_EXPORT QPlaceContentReplyUnsupported : public QPlaceContentReply +{ + Q_OBJECT + +public: + QPlaceContentReplyUnsupported(QPlaceManagerEngine *parent) + : QPlaceContentReply(parent) + { + setError(QPlaceReply::UnsupportedError, + QStringLiteral("Place content is not supported.")); + setFinished(true); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(parent, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this), + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + QMetaObject::invokeMethod(parent, "finished", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this)); + } +}; + +class Q_LOCATION_EXPORT QPlaceSearchReplyUnsupported : public QPlaceSearchReply +{ + Q_OBJECT + +public: + QPlaceSearchReplyUnsupported(QPlaceReply::Error errorCode, const QString &message, + QPlaceManagerEngine *parent) + : QPlaceSearchReply(parent) + { + setError(errorCode, message); + setFinished(true); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(parent, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this), + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + QMetaObject::invokeMethod(parent, "finished", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this)); + } +}; + +class Q_LOCATION_EXPORT QPlaceSearchSuggestionReplyUnsupported : public QPlaceSearchSuggestionReply +{ + Q_OBJECT + +public: + QPlaceSearchSuggestionReplyUnsupported(QPlaceManagerEngine *parent) + : QPlaceSearchSuggestionReply(parent) + { + setError(QPlaceReply::UnsupportedError, + QStringLiteral("Place search suggestions are not supported.")); + setFinished(true); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(parent, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this), + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + QMetaObject::invokeMethod(parent, "finished", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this)); + } +}; + +class Q_LOCATION_EXPORT QPlaceIdReplyUnsupported : public QPlaceIdReply +{ + Q_OBJECT + +public: + QPlaceIdReplyUnsupported(const QString &message, QPlaceIdReply::OperationType type, + QPlaceManagerEngine *parent) + : QPlaceIdReply(type, parent) + { + setError(QPlaceReply::UnsupportedError, message); + setFinished(true); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(parent, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this), + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + QMetaObject::invokeMethod(parent, "finished", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this)); + } +}; + +class Q_LOCATION_EXPORT QPlaceReplyUnsupported : public QPlaceReply +{ + Q_OBJECT + +public: + QPlaceReplyUnsupported(const QString &message, QPlaceManagerEngine *parent) + : QPlaceReply(parent) + { + setError(QPlaceReply::UnsupportedError, message); + setFinished(true); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(parent, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this), + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + QMetaObject::invokeMethod(parent, "finished", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this)); + } +}; + +class Q_LOCATION_EXPORT QPlaceMatchReplyUnsupported : public QPlaceMatchReply +{ + Q_OBJECT + +public: + QPlaceMatchReplyUnsupported(QPlaceManagerEngine *parent) + : QPlaceMatchReply(parent) + { + setError(QPlaceReply::UnsupportedError, + QStringLiteral("Place matching is not supported.")); + setFinished(true); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(parent, "error", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this), + Q_ARG(QPlaceReply::Error, error()), + Q_ARG(QString, errorString())); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + QMetaObject::invokeMethod(parent, "finished", Qt::QueuedConnection, + Q_ARG(QPlaceReply *, this)); + } +}; + +#endif diff --git a/src/location/qlocation.cpp b/src/location/qlocation.cpp new file mode 100644 index 0000000..2afed10 --- /dev/null +++ b/src/location/qlocation.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +QT_BEGIN_NAMESPACE + +namespace QLocation { + +/*! + \namespace QLocation + \inmodule QtLocation + \keyword QLocation Namespace + + \brief The QLocation namespace contains miscellaneous identifiers used throughout the + QtLocation module. +*/ + +/*! + \enum QLocation::Visibility + + Defines the visibility of a QPlace or QPlaceCategory. + + \value UnspecifiedVisibility No explicit visibility has been defined. + \value DeviceVisibility Places and categories with DeviceVisibility are only stored on + the local device. + \value PrivateVisibility Places and categories with PrivateVisibility are only visible + to the current user. The data may be stored either locally or + on a remote service or both. + \value PublicVisibility Places and categories with PublicVisibility are visible to + everyone. + + A particular manager may support one or more visibility scopes. For example a manager from one provider may only provide places + that are public to everyone, whilst another may provide both public and private places. + + \note The meaning of unspecified visibility depends on the context it is used. + + When \e saving a place or category, the + default visibility is unspecified meaning that the manager chooses an appropriate visibility scope for the item. + + When \e searching for places, unspecified means that places of any scope is returned. +*/ +} + +QT_END_NAMESPACE diff --git a/src/location/qlocation.h b/src/location/qlocation.h new file mode 100644 index 0000000..d30a3a3 --- /dev/null +++ b/src/location/qlocation.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCATION_H +#define QLOCATION_H + +#if 0 +#pragma qt_class(QLocation) +#endif + +#include + +QT_BEGIN_NAMESPACE + +namespace QLocation { + +enum Visibility { + UnspecifiedVisibility = 0x00, + DeviceVisibility = 0x01, + PrivateVisibility = 0x02, + PublicVisibility = 0x04 +}; + +Q_DECLARE_FLAGS(VisibilityScope, Visibility) + +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QLocation::VisibilityScope) + +QT_END_NAMESPACE + +#endif // QLOCATION_H diff --git a/src/location/qlocationglobal.h b/src/location/qlocationglobal.h new file mode 100644 index 0000000..7992c09 --- /dev/null +++ b/src/location/qlocationglobal.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QLOCATIONGLOBAL_H +#define QLOCATIONGLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QT_BUILD_LOCATION_LIB) +# define Q_LOCATION_EXPORT Q_DECL_EXPORT +# else +# define Q_LOCATION_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_LOCATION_EXPORT +#endif + +QT_END_NAMESPACE + +#endif // QLOCATIONGLOBAL_H + diff --git a/src/plugins/geoservices/geoservices.pro b/src/plugins/geoservices/geoservices.pro new file mode 100644 index 0000000..3d0971f --- /dev/null +++ b/src/plugins/geoservices/geoservices.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS = nokia osm mapbox diff --git a/src/plugins/geoservices/mapbox/mapbox.pro b/src/plugins/geoservices/mapbox/mapbox.pro new file mode 100644 index 0000000..d4797e3 --- /dev/null +++ b/src/plugins/geoservices/mapbox/mapbox.pro @@ -0,0 +1,22 @@ +TARGET = qtgeoservices_mapbox + +QT += location-private positioning-private network + +HEADERS += \ + qgeoserviceproviderpluginmapbox.h \ + qgeotiledmappingmanagerenginemapbox.h \ + qgeotilefetchermapbox.h \ + qgeomapreplymapbox.h + +SOURCES += \ + qgeoserviceproviderpluginmapbox.cpp \ + qgeotiledmappingmanagerenginemapbox.cpp \ + qgeotilefetchermapbox.cpp \ + qgeomapreplymapbox.cpp + +OTHER_FILES += \ + mapbox_plugin.json + +PLUGIN_TYPE = geoservices +PLUGIN_CLASS_NAME = QGeoServiceProviderFactoryMapbox +load(qt_plugin) diff --git a/src/plugins/geoservices/mapbox/mapbox_plugin.json b/src/plugins/geoservices/mapbox/mapbox_plugin.json new file mode 100644 index 0000000..0b8d08a --- /dev/null +++ b/src/plugins/geoservices/mapbox/mapbox_plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["mapbox"], + "Provider": "mapbox", + "Version": 100, + "Experimental": false, + "Features": [ + "OnlineMappingFeature" + ] +} diff --git a/src/plugins/geoservices/mapbox/qgeomapreplymapbox.cpp b/src/plugins/geoservices/mapbox/qgeomapreplymapbox.cpp new file mode 100644 index 0000000..5fe9caa --- /dev/null +++ b/src/plugins/geoservices/mapbox/qgeomapreplymapbox.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Canonical Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomapreplymapbox.h" + +#include + +QGeoMapReplyMapbox::QGeoMapReplyMapbox(QNetworkReply *reply, const QGeoTileSpec &spec, const QString &format, QObject *parent) +: QGeoTiledMapReply(spec, parent), m_reply(reply), m_format (format) +{ + connect(m_reply, SIGNAL(finished()), this, SLOT(networkReplyFinished())); + connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(networkReplyError(QNetworkReply::NetworkError))); +} + +QGeoMapReplyMapbox::~QGeoMapReplyMapbox() +{ + if (m_reply) { + m_reply->deleteLater(); + m_reply = 0; + } +} + +void QGeoMapReplyMapbox::abort() +{ + if (!m_reply) + return; + + m_reply->abort(); +} + +QNetworkReply *QGeoMapReplyMapbox::networkReply() const +{ + return m_reply; +} + +void QGeoMapReplyMapbox::networkReplyFinished() +{ + if (!m_reply) + return; + + if (m_reply->error() != QNetworkReply::NoError) + return; + + setMapImageData(m_reply->readAll()); + setMapImageFormat(m_format); + setFinished(true); + + m_reply->deleteLater(); + m_reply = 0; +} + +void QGeoMapReplyMapbox::networkReplyError(QNetworkReply::NetworkError error) +{ + if (!m_reply) + return; + + if (error != QNetworkReply::OperationCanceledError) + setError(QGeoTiledMapReply::CommunicationError, m_reply->errorString()); + + setFinished(true); + m_reply->deleteLater(); + m_reply = 0; +} diff --git a/src/plugins/geoservices/mapbox/qgeomapreplymapbox.h b/src/plugins/geoservices/mapbox/qgeomapreplymapbox.h new file mode 100644 index 0000000..67ad61a --- /dev/null +++ b/src/plugins/geoservices/mapbox/qgeomapreplymapbox.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Canonical Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPREPLYMAPBOX_H +#define QGEOMAPREPLYMAPBOX_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoMapReplyMapbox : public QGeoTiledMapReply +{ + Q_OBJECT + +public: + explicit QGeoMapReplyMapbox(QNetworkReply *reply, const QGeoTileSpec &spec, const QString &format, QObject *parent = 0); + ~QGeoMapReplyMapbox(); + + void abort(); + + QNetworkReply *networkReply() const; + +private Q_SLOTS: + void networkReplyFinished(); + void networkReplyError(QNetworkReply::NetworkError error); + +private: + QPointer m_reply; + QString m_format; +}; + +QT_END_NAMESPACE + +#endif // QGEOMAPREPLYMAPBOX_H diff --git a/src/plugins/geoservices/mapbox/qgeoserviceproviderpluginmapbox.cpp b/src/plugins/geoservices/mapbox/qgeoserviceproviderpluginmapbox.cpp new file mode 100644 index 0000000..ec40716 --- /dev/null +++ b/src/plugins/geoservices/mapbox/qgeoserviceproviderpluginmapbox.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Canonical Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoserviceproviderpluginmapbox.h" +#include "qgeotiledmappingmanagerenginemapbox.h" + +#include + +QT_BEGIN_NAMESPACE + +QGeoCodingManagerEngine *QGeoServiceProviderFactoryMapbox::createGeocodingManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const +{ + Q_UNUSED(parameters) + Q_UNUSED(error) + Q_UNUSED(errorString) + + return 0; +} + +QGeoMappingManagerEngine *QGeoServiceProviderFactoryMapbox::createMappingManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const +{ + const QString mapId = parameters.value(QStringLiteral("mapbox.map_id")).toString(); + const QString accessToken = parameters.value(QStringLiteral("mapbox.access_token")).toString(); + + if (!mapId.isEmpty() && !accessToken.isEmpty()) { + return new QGeoTiledMappingManagerEngineMapbox(parameters, error, errorString); + } else { + *error = QGeoServiceProvider::MissingRequiredParameterError; + *errorString = tr("Mapbox plugin requires 'mapbox.map_id' and 'mapbox.access_token' parameters.\n" + "Please visit https://www.mapbox.com"); + return 0; + } +} + +QGeoRoutingManagerEngine *QGeoServiceProviderFactoryMapbox::createRoutingManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const +{ + Q_UNUSED(parameters) + Q_UNUSED(error) + Q_UNUSED(errorString) + + return 0; +} + +QPlaceManagerEngine *QGeoServiceProviderFactoryMapbox::createPlaceManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const +{ + Q_UNUSED(parameters) + Q_UNUSED(error) + Q_UNUSED(errorString) + + return 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/mapbox/qgeoserviceproviderpluginmapbox.h b/src/plugins/geoservices/mapbox/qgeoserviceproviderpluginmapbox.h new file mode 100644 index 0000000..30b4c6e --- /dev/null +++ b/src/plugins/geoservices/mapbox/qgeoserviceproviderpluginmapbox.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Canonical Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDER_MAPBOX_H +#define QGEOSERVICEPROVIDER_MAPBOX_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoServiceProviderFactoryMapbox: public QObject, public QGeoServiceProviderFactory +{ + Q_OBJECT + Q_INTERFACES(QGeoServiceProviderFactory) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0" + FILE "mapbox_plugin.json") + +public: + QGeoCodingManagerEngine *createGeocodingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + QGeoMappingManagerEngine *createMappingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + QGeoRoutingManagerEngine *createRoutingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + QPlaceManagerEngine *createPlaceManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.cpp b/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.cpp new file mode 100644 index 0000000..4be5ac2 --- /dev/null +++ b/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Canonical Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotiledmappingmanagerenginemapbox.h" +#include "qgeotilefetchermapbox.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoTiledMappingManagerEngineMapbox::QGeoTiledMappingManagerEngineMapbox(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) +: QGeoTiledMappingManagerEngine() +{ + QGeoCameraCapabilities cameraCaps; + cameraCaps.setMinimumZoomLevel(0.0); + cameraCaps.setMaximumZoomLevel(19.0); + setCameraCapabilities(cameraCaps); + + setTileSize(QSize(256, 256)); + + QList mapTypes; + mapTypes << QGeoMapType(QGeoMapType::CustomMap, tr("Custom"), tr("Mapbox custom map"), false, false, 0); + setSupportedMapTypes(mapTypes); + + QGeoTileFetcherMapbox *tileFetcher = new QGeoTileFetcherMapbox(this); + if (parameters.contains(QStringLiteral("useragent"))) { + const QByteArray ua = parameters.value(QStringLiteral("useragent")).toString().toLatin1(); + tileFetcher->setUserAgent(ua); + } + if (parameters.contains(QStringLiteral("mapbox.map_id"))) { + const QString id = parameters.value(QStringLiteral("mapbox.map_id")).toString(); + tileFetcher->setMapId(id); + } + if (parameters.contains(QStringLiteral("mapbox.format"))) { + const QString format = parameters.value(QStringLiteral("mapbox.format")).toString(); + tileFetcher->setFormat(format); + } + if (parameters.contains(QStringLiteral("mapbox.access_token"))) { + const QString token = parameters.value(QStringLiteral("mapbox.access_token")).toString(); + tileFetcher->setAccessToken(token); + } + + setTileFetcher(tileFetcher); + + *error = QGeoServiceProvider::NoError; + errorString->clear(); +} + +QGeoTiledMappingManagerEngineMapbox::~QGeoTiledMappingManagerEngineMapbox() +{ +} + +QGeoMap *QGeoTiledMappingManagerEngineMapbox::createMap() +{ + return new QGeoTiledMap(this, 0); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.h b/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.h new file mode 100644 index 0000000..379ca6b --- /dev/null +++ b/src/plugins/geoservices/mapbox/qgeotiledmappingmanagerenginemapbox.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Canonical Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEDMAPPINGMANAGERENGINEMAPBOX_H +#define QGEOTILEDMAPPINGMANAGERENGINEMAPBOX_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoTiledMappingManagerEngineMapbox : public QGeoTiledMappingManagerEngine +{ + Q_OBJECT + +public: + QGeoTiledMappingManagerEngineMapbox(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString); + ~QGeoTiledMappingManagerEngineMapbox(); + + QGeoMap *createMap(); +}; + +QT_END_NAMESPACE + +#endif // QGEOTILEDMAPPINGMANAGERENGINEMAPBOX_H diff --git a/src/plugins/geoservices/mapbox/qgeotilefetchermapbox.cpp b/src/plugins/geoservices/mapbox/qgeotilefetchermapbox.cpp new file mode 100644 index 0000000..69a832d --- /dev/null +++ b/src/plugins/geoservices/mapbox/qgeotilefetchermapbox.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Canonical Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotilefetchermapbox.h" +#include "qgeomapreplymapbox.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoTileFetcherMapbox::QGeoTileFetcherMapbox(QObject *parent) +: QGeoTileFetcher(parent), m_networkManager(new QNetworkAccessManager(this)), + m_userAgent("Qt Location based application"), + m_mapId(""), + m_format("png"), + m_replyFormat("png"), + m_accessToken("") +{ +} + +void QGeoTileFetcherMapbox::setUserAgent(const QByteArray &userAgent) +{ + m_userAgent = userAgent; +} + +void QGeoTileFetcherMapbox::setMapId(const QString &mapId) +{ + m_mapId = mapId; +} + +void QGeoTileFetcherMapbox::setFormat(const QString &format) +{ + m_format = format; + + if (m_format == "png" || m_format == "png32" || m_format == "png64" || m_format == "png128" || m_format == "png256") + m_replyFormat = "png"; + else if (m_format == "jpg70" || m_format == "jpg80" || m_format == "jpg90") + m_replyFormat = "jpg"; + else + qWarning() << "Unknown map format " << m_format; +} + +void QGeoTileFetcherMapbox::setAccessToken(const QString &accessToken) +{ + m_accessToken = accessToken; +} + +QGeoTiledMapReply *QGeoTileFetcherMapbox::getTileImage(const QGeoTileSpec &spec) +{ + QNetworkRequest request; + request.setRawHeader("User-Agent", m_userAgent); + + request.setUrl(QUrl(QStringLiteral("http://api.tiles.mapbox.com/v4/") + + m_mapId + QLatin1Char('/') + + QString::number(spec.zoom()) + QLatin1Char('/') + + QString::number(spec.x()) + QLatin1Char('/') + + QString::number(spec.y()) + QLatin1Char('.') + + m_format + QLatin1Char('?') + + QStringLiteral("access_token=") + m_accessToken)); + + QNetworkReply *reply = m_networkManager->get(request); + + return new QGeoMapReplyMapbox(reply, spec, m_replyFormat); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/mapbox/qgeotilefetchermapbox.h b/src/plugins/geoservices/mapbox/qgeotilefetchermapbox.h new file mode 100644 index 0000000..ddec289 --- /dev/null +++ b/src/plugins/geoservices/mapbox/qgeotilefetchermapbox.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Canonical Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEFETCHERMAPBOX_H +#define QGEOTILEFETCHERMAPBOX_H + +#include + +QT_BEGIN_NAMESPACE + +class QGeoTiledMappingManagerEngine; +class QNetworkAccessManager; + +class QGeoTileFetcherMapbox : public QGeoTileFetcher +{ + Q_OBJECT + +public: + QGeoTileFetcherMapbox(QObject *parent = 0); + + void setUserAgent(const QByteArray &userAgent); + void setMapId(const QString &mapId); + void setFormat(const QString &format); + void setAccessToken(const QString &accessToken); + +private: + QGeoTiledMapReply *getTileImage(const QGeoTileSpec &spec); + + QNetworkAccessManager *m_networkManager; + QByteArray m_userAgent; + QString m_mapId; + QString m_format; + QString m_replyFormat; + QString m_accessToken; +}; + +QT_END_NAMESPACE + +#endif // QGEOTILEFETCHERMAPBOX_H diff --git a/src/plugins/geoservices/nokia/logo.png b/src/plugins/geoservices/nokia/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ba4219178808c7b6866765c54e1f1b48b044fec6 GIT binary patch literal 1217 zcmV;y1U~zTP)ks2w#4tjcQ8J7 z&(m!7?!k}T&dhG+&Ac|_^~CS@gqTEvmY_L!1D=9s;Je)mr9)`)GM5oxA&55GbHQ=2 z8a%LiF)TzB2nSoiE~S7-MKBti2V+86i6vkXm9djLU>nDUoDyxpOw}ena2kA8?d8ET z&@j}L7zb(?&zC?~&=;&zWAnjs@FG-{s73Q>s@mr>Ds@vk^bJe{NuWKb7HlPEfy*Ek z0CewnFXMo(SI96f!ch|qXMaHJEO+; zbURPTDi1X*poPU4bApoSCW0#rwTY_#rTB-N=F|$*QEF@sI2;u!FIRGj##c@O-`oB=#xnE4jf?YUje*E ze+?CWeR2xSaVt7?0!OKUPrRz6@JRw!zQE7-eu5O1Avta-@tV$QC;bdFksaCyVp#CW zBnvrl`FkCV1XI9Q&mXH&G9fmcVNzDT64*rNw31a|u|>PQU!Mytj{h9|a-$TNN%vmH+z3PRSN9*^ha7 z0#L+jC={?b<=C?=6!;Oq4rp1)QrmSUs#|2zG|-VH^?lVRXM_phg3b4)+vJ84RYJ<* z)~cSF3T}Z2(8%b^W-XM=8tR(*mL$I6;G^40NEj?@Jl_DHzz}fINNBPHccXFs5hv zc}MPq`hX5pKr(9&BL_$Z?#}bDNEUr2gR8%X6|E$}UOaM6)kn_%39(VM(>P{UIXyj5 zW6LqK&M{V4F0>BQR2taqQam^d8iVz8cJs`9zW5n7et#sZ%ow9xPF%7@kBr02I)+j! z`9;(8s&+XSA7kwk$0}Bb)TK@+afUU$mdD(Yhcu6K4wooWevf5=S1bwR{Z5`mUvoNQ zGdiG?l1fMa>BPsxCpNQ&-$c7NC7L18tow`T3*mmE+Xwnh8P)!g_eQ&EYK|3c9)7ft z*Mwy(6fs5aOBXVxuT{rQ(_vGnY&9x=hYOPFbltDd6SBn{)XIC*F4QtI>M$xEVKMeO zL-FI1KL2WSNaDp-zMu0#ALIED3%@Lju@X`*vMg~`2@jLebU!wm$YkY{qujxqAl)X~ zI%W!Q-K-`i=5#_8eQ8wWt2$rz=exy~noWjQwv}iU-x4Oxee4l5C5<2HX+zYBIyE9r)K$JeAJ=g-6bOgGMZ^ fql4wxzXcco_%!)2?dJ;100000NkvXXu0mjfxv@EH literal 0 HcmV?d00001 diff --git a/src/plugins/geoservices/nokia/marclanguagecodes.h b/src/plugins/geoservices/nokia/marclanguagecodes.h new file mode 100644 index 0000000..69e309f --- /dev/null +++ b/src/plugins/geoservices/nokia/marclanguagecodes.h @@ -0,0 +1,312 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MARCLANGUAGECODES_H_ +#define MARCLANGUAGECODES_H_ + +// MARC language codes for GeoCoding service language/locale support +// http://www.loc.gov/marc/languages/language_code.html +// Order matches QLocale::Language + +QT_BEGIN_NAMESPACE + +static const unsigned char marc_language_code_list[] = + "\0\0\0" // Unused + "\0\0\0" // C + "abk" // Abkhazian + "\0\0\0" // Oromo + "aar" // Afar + "afr" // Afrikaans + "alb" // Albanian + "amh" // Amharic + "ara" // Arabic + "arm" // Armenian + "asm" // Assamese + "aym" // Aymara + "aze" // Azerbaijani + "bak" // Bashkir + "baq" // Basque + "ben" // Bengali + "\0\0\0" // Dzongkha + "bih" // Bihari + "bis" // Bislama + "bre" // Breton + "bul" // Bulgarian + "bur" // Burmese + "bel" // Belarusian + "khm" // Khmer + "cat" // Catalan + "chi" // Chinese + "cos" // Corsican + "hrv" // Croatian + "cze" // Czech + "dan" // Danish + "dut" // Dutch + "eng" // English + "epo" // Esperanto + "est" // Estonian + "fao" // Faroese + "fij" // Fijian + "fin" // Finnish + "fre" // French + "fry" // WesternFrisian + "gla" // Gaelic + "glg" // Galician + "geo" // Georgian + "ger" // German + "gre" // Greek + "\0\0\0" // Greenlandic + "grn" // Guarani + "guj" // Gujarati + "hau" // Hausa + "heb" // Hebrew + "hin" // Hindi + "hun" // Hungarian + "ice" // Icelandic + "ind" // Indonesian + "ina" // Interlingua + "ile" // Interlingue + "iku" // Inuktitut + "ipk" // Inupiak + "gle" // Irish + "ita" // Italian + "jpn" // Japanese + "jav" // Javanese + "kan" // Kannada + "kas" // Kashmiri + "kaz" // Kazakh + "kin" // Kinyarwanda + "kir" // Kirghiz + "kor" // Korean + "kur" // Kurdish + "\0\0\0" // Rundi + "lao" // Lao + "lat" // Latin + "lav" // Latvian + "lin" // Lingala + "lit" // Lithuanian + "mac" // Macedonian + "mlg" // Malagasy + "may" // Malay + "mal" // Malayalam + "mlt" // Maltese + "mao" // Maori + "mar" // Marathi + "mah" // Marshallese + "mon" // Mongolian + "nau" // NauruLanguage + "nep" // Nepali + "nor" // NorwegianBokmal + "oci" // Occitan + "ori" // Oriya + "\0\0\0" // Pashto + "per" // Persian + "pol" // Polish + "por" // Portuguese + "pan" // Punjabi + "que" // Quechua + "roh" // Romansh + "rum" // Romanian + "rus" // Russian + "smo" // Samoan + "sag" // Sango + "san" // Sanskrit + "srp" // Serbian + "oss" // Ossetic + "\0\0\0" // SouthernSotho + "\0\0\0" // Tswana + "sna" // Shona + "snd" // Sindhi + "\0\0\0" // Sinhala + "\0\0\0" // Swati + "slo" // Slovak + "slv" // Slovenian + "som" // Somali + "spa" // Spanish + "sun" // Sundanese + "swa" // Swahili + "swe" // Swedish + "srd" // Sardinian + "tgk" // Tajik + "tam" // Tamil + "tat" // Tatar + "tel" // Telugu + "tha" // Thai + "tib" // Tibetan + "tir" // Tigrinya + "tog" // Tongan + "tso" // Tsonga + "tur" // Turkish + "tuk" // Turkmen + "tah" // Tahitian + "uig" // Uigur + "ukr" // Ukrainian + "urd" // Urdu + "uzb" // Uzbek + "vie" // Vietnamese + "vol" // Volapuk + "wel" // Welsh + "wol" // Wolof + "xho" // Xhosa + "yid" // Yiddish + "yor" // Yoruba + "zha" // Zhuang + "zul" // Zulu + "nno" // NorwegianNynorsk + "bos" // Bosnian + "div" // Divehi + "glv" // Manx + "cor" // Cornish + "aka" // Akan + "kok" // Konkani + "gaa" // Ga + "ibo" // Igbo + "kam" // Kamba + "syc" // Syriac + "\0\0\0" // Blin + "\0\0\0" // Geez + "\0\0\0" // Koro + "sid" // Sidamo + "\0\0\0" // Atsam + "tig" // Tigre + "\0\0\0" // Jju + "fur" // Friulian + "ven" // Venda + "ewe" // Ewe + "\0\0\0" // Walamo + "haw" // Hawaiian + "\0\0\0" // Tyap + "\0\0\0" // Nyanja + "fil" // Filipino + "gsw" // SwissGerman + "iii" // SichuanYi + "kpe" // Kpelle + "nds" // LowGerman + "nbl" // SouthNdebele + "nso" // NorthernSotho + "sme" // NorthernSami + "\0\0\0" // Taroko + "\0\0\0" // Gusii + "\0\0\0" // Taita + "ful" // Fulah + "kik" // Kikuyu + "\0\0\0" // Samburu + "\0\0\0" // Sena + "nde" // NorthNdebele + "\0\0\0" // Rombo + "\0\0\0" // Tachelhit + "kab" // Kabyle + "nyn" // Nyankole + "\0\0\0" // Bena + "\0\0\0" // Vunjo + "bam" // Bambara + "\0\0\0" // Embu + "chr" // Cherokee + "\0\0\0" // Morisyen + "\0\0\0" // Makonde + "\0\0\0" // Langi + "lug" // Ganda + "bem" // Bemba + "\0\0\0" // Kabuverdianu + "\0\0\0" // Meru + "\0\0\0" // Kalenjin + "\0\0\0" // Nama + "\0\0\0" // Machame + "\0\0\0" // Colognian + "mas" // Masai + "\0\0\0" // Soga + "\0\0\0" // Luyia + "\0\0\0" // Asu + "\0\0\0" // Teso + "\0\0\0" // Saho + "\0\0\0" // KoyraChiini + "\0\0\0" // Rwa + "luo" // Luo + "\0\0\0" // Chiga + "\0\0\0" // CentralMoroccoTamazight + "\0\0\0" // KoyraboroSenni + "\0\0\0" // Shambala + "\0\0\0" // Bodo + "ava" // Avaric + "cha" // Chamorro + "che" // Chechen + "chu" // Church + "chv" // Chuvash + "cre" // Cree + "hat" // Haitian + "her" // Herero + "hmo" // HiriMotu + "kau" // Kanuri + "kom" // Komi + "kon" // Kongo + "\0\0\0" // Kwanyama + "lim" // Limburgish + "lub" // LubaKatanga + "ltz" // Luxembourgish + "\0\0\0" // Navaho + "ndo" // Ndonga + "oji" // Ojibwa + "pli" // Pali + "wln" // Walloon + "\0\0\0" // Aghem + "bas" // Basaa + "\0\0\0" // Zarma + "dua" // Duala + "\0\0\0" // JolaFonyi + "ewo" // Ewondo + "\0\0\0" // Bafia + "\0\0\0" // MakhuwaMeetto + "\0\0\0" // Mundang + "\0\0\0" // Kwasio + "\0\0\0" // Nuer + "\0\0\0" // Sakha + "\0\0\0" // Sangu + "\0\0\0" // CongoSwahili + "\0\0\0" // Tasawaq + "vai" // Vai + "\0\0\0" // Walser + "\0\0\0" // Yangben + "ave" // Avestan + "\0\0\0" // Asturian + "\0\0\0" // Ngomba + "\0\0\0" // Kako + "\0\0\0" // Meta + "\0\0\0" // Ngiemboon + ; + +QT_END_NAMESPACE + +#endif /* MARCLANGUAGECODES_H_ */ diff --git a/src/plugins/geoservices/nokia/nokia.pro b/src/plugins/geoservices/nokia/nokia.pro new file mode 100644 index 0000000..cd340f5 --- /dev/null +++ b/src/plugins/geoservices/nokia/nokia.pro @@ -0,0 +1,61 @@ +TARGET = qtgeoservices_nokia + +QT += location-private positioning-private network + +contains(QT_CONFIG, location-china-support) { + # China support + DEFINES += USE_CHINA_NETWORK_REGISTRATION + QT += systeminfo +} + +HEADERS += \ + qgeocodereply_nokia.h \ + qgeocodexmlparser.h \ + qgeocodingmanagerengine_nokia.h \ + qgeotiledmappingmanagerengine_nokia.h \ + qgeotilefetcher_nokia.h \ + qgeomapreply_nokia.h \ + qgeoroutereply_nokia.h \ + qgeoroutexmlparser.h \ + qgeoroutingmanagerengine_nokia.h \ + qgeoserviceproviderplugin_nokia.h \ + marclanguagecodes.h \ + qgeonetworkaccessmanager.h \ + qgeointrinsicnetworkaccessmanager.h \ + qgeouriprovider.h \ + uri_constants.h \ + qgeoerror_messages.h \ + qgeomapversion.h \ + qgeotiledmap_nokia.h + + +SOURCES += \ + qgeocodereply_nokia.cpp \ + qgeocodexmlparser.cpp \ + qgeocodingmanagerengine_nokia.cpp \ + qgeotiledmappingmanagerengine_nokia.cpp \ + qgeotilefetcher_nokia.cpp \ + qgeomapreply_nokia.cpp \ + qgeoroutereply_nokia.cpp \ + qgeoroutexmlparser.cpp \ + qgeoroutingmanagerengine_nokia.cpp \ + qgeoserviceproviderplugin_nokia.cpp \ + qgeointrinsicnetworkaccessmanager.cpp \ + qgeouriprovider.cpp \ + uri_constants.cpp \ + qgeoerror_messages.cpp \ + qgeomapversion.cpp \ + qgeotiledmap_nokia.cpp + +include(placesv2/placesv2.pri) + +RESOURCES += resource.qrc + +INCLUDEPATH += ../../../location/maps + +OTHER_FILES += \ + nokia_plugin.json + +PLUGIN_TYPE = geoservices +PLUGIN_CLASS_NAME = QGeoServiceProviderFactoryNokia +load(qt_plugin) diff --git a/src/plugins/geoservices/nokia/nokia_plugin.json b/src/plugins/geoservices/nokia/nokia_plugin.json new file mode 100644 index 0000000..1fc2827 --- /dev/null +++ b/src/plugins/geoservices/nokia/nokia_plugin.json @@ -0,0 +1,19 @@ +{ + "Keys": ["here"], + "Provider": "here", + "Version": 101, + "Experimental": false, + "Features": [ + "OnlineRoutingFeature", + "RouteUpdatesFeature", + "AlternativeRoutesFeature", + "ExcludeAreasRoutingFeature", + "OnlineGeocodingFeature", + "OnlineMappingFeature", + "OnlinePlacesFeature", + "ReverseGeocodingFeature", + "PlaceRecommendationsFeature", + "SearchSuggestionsFeature", + "LocalizedPlacesFeature" + ] +} diff --git a/src/plugins/geoservices/nokia/placesv2/jsonparserhelpers.cpp b/src/plugins/geoservices/nokia/placesv2/jsonparserhelpers.cpp new file mode 100644 index 0000000..f877e9d --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/jsonparserhelpers.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "jsonparserhelpers.h" +#include "../qplacemanagerengine_nokiav2.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoCoordinate parseCoordinate(const QJsonArray &coordinateArray) +{ + return QGeoCoordinate(coordinateArray.at(0).toDouble(), coordinateArray.at(1).toDouble()); +} + +QPlaceSupplier parseSupplier(const QJsonObject &supplierObject, + const QPlaceManagerEngineNokiaV2 *engine) +{ + Q_ASSERT(engine); + + QPlaceSupplier supplier; + supplier.setName(supplierObject.value(QStringLiteral("title")).toString()); + supplier.setUrl(supplierObject.value(QStringLiteral("href")).toString()); + + supplier.setIcon(engine->icon(supplierObject.value(QStringLiteral("icon")).toString())); + + return supplier; +} + +QPlaceCategory parseCategory(const QJsonObject &categoryObject, + const QPlaceManagerEngineNokiaV2 *engine) +{ + Q_ASSERT(engine); + + QPlaceCategory category; + + category.setName(categoryObject.value(QStringLiteral("title")).toString()); + + const QUrl href(categoryObject.value(QStringLiteral("href")).toString()); + const QString hrefPath(href.path()); + category.setCategoryId(hrefPath.mid(hrefPath.lastIndexOf(QLatin1Char('/')) + 1)); + + + category.setIcon(engine->icon(categoryObject.value(QStringLiteral("icon")).toString())); + return category; +} + +QList parseCategories(const QJsonArray &categoryArray, + const QPlaceManagerEngineNokiaV2 *engine) +{ + Q_ASSERT(engine); + + QList categoryList; + for (int i = 0; i < categoryArray.count(); ++i) + categoryList.append(parseCategory(categoryArray.at(i).toObject(), + engine)); + + return categoryList; +} + +QList parseContactDetails(const QJsonArray &contacts) +{ + QList contactDetails; + + for (int i = 0; i < contacts.count(); ++i) { + QJsonObject contact = contacts.at(i).toObject(); + + QPlaceContactDetail detail; + detail.setLabel(contact.value(QStringLiteral("label")).toString()); + detail.setValue(contact.value(QStringLiteral("value")).toString()); + + contactDetails.append(detail); + } + + return contactDetails; +} + +QPlaceImage parseImage(const QJsonObject &imageObject, + const QPlaceManagerEngineNokiaV2 *engine) +{ + Q_ASSERT(engine); + + QPlaceImage image; + + image.setAttribution(imageObject.value(QStringLiteral("attribution")).toString()); + image.setUrl(imageObject.value(QStringLiteral("src")).toString()); + image.setSupplier(parseSupplier(imageObject.value(QStringLiteral("supplier")).toObject(), + engine)); + + return image; +} + +QPlaceReview parseReview(const QJsonObject &reviewObject, + const QPlaceManagerEngineNokiaV2 *engine) +{ + Q_ASSERT(engine); + + QPlaceReview review; + + review.setDateTime(QDateTime::fromString(reviewObject.value(QStringLiteral("date")).toString())); + + if (reviewObject.contains(QStringLiteral("title"))) + review.setTitle(reviewObject.value(QStringLiteral("title")).toString()); + + if (reviewObject.contains(QStringLiteral("rating"))) + review.setRating(reviewObject.value(QStringLiteral("rating")).toDouble()); + + review.setText(reviewObject.value(QStringLiteral("description")).toString()); + + QJsonObject userObject = reviewObject.value(QStringLiteral("user")).toObject(); + + QPlaceUser user; + user.setUserId(userObject.value(QStringLiteral("id")).toString()); + user.setName(userObject.value(QStringLiteral("title")).toString()); + review.setUser(user); + + review.setAttribution(reviewObject.value(QStringLiteral("attribution")).toString()); + + review.setLanguage(reviewObject.value(QStringLiteral("language")).toString()); + + review.setSupplier(parseSupplier(reviewObject.value(QStringLiteral("supplier")).toObject(), + engine)); + + //if (reviewObject.contains(QStringLiteral("via"))) { + // QJsonObject viaObject = reviewObject.value(QStringLiteral("via")).toObject(); + //} + + return review; +} + +QPlaceEditorial parseEditorial(const QJsonObject &editorialObject, + const QPlaceManagerEngineNokiaV2 *engine) +{ + Q_ASSERT(engine); + + QPlaceEditorial editorial; + + editorial.setAttribution(editorialObject.value(QStringLiteral("attribution")).toString()); + + //if (editorialObject.contains(QStringLiteral("via"))) { + // QJsonObject viaObject = editorialObject.value(QStringLiteral("via")).toObject(); + //} + + editorial.setSupplier(parseSupplier(editorialObject.value(QStringLiteral("supplier")).toObject(), + engine)); + editorial.setLanguage(editorialObject.value(QStringLiteral("language")).toString()); + editorial.setText(editorialObject.value(QStringLiteral("description")).toString()); + + return editorial; +} + +void parseCollection(QPlaceContent::Type type, const QJsonObject &object, + QPlaceContent::Collection *collection, int *totalCount, + QPlaceContentRequest *previous, QPlaceContentRequest *next, + const QPlaceManagerEngineNokiaV2 *engine) +{ + Q_ASSERT(engine); + + if (totalCount) + *totalCount = object.value(QStringLiteral("available")).toDouble(); + + int offset = 0; + if (object.contains(QStringLiteral("offset"))) + offset = object.value(QStringLiteral("offset")).toDouble(); + + if (previous && object.contains(QStringLiteral("previous"))) { + previous->setContentType(type); + previous->setContentContext(QUrl(object.value(QStringLiteral("previous")).toString())); + } + + if (next && object.contains(QStringLiteral("next"))) { + next->setContentType(type); + next->setContentContext(QUrl(object.value(QStringLiteral("next")).toString())); + } + + if (collection) { + QJsonArray items = object.value(QStringLiteral("items")).toArray(); + for (int i = 0; i < items.count(); ++i) { + QJsonObject itemObject = items.at(i).toObject(); + + switch (type) { + case QPlaceContent::ImageType: + collection->insert(offset + i, parseImage(itemObject, engine)); + break; + case QPlaceContent::ReviewType: + collection->insert(offset + i, parseReview(itemObject, engine)); + break; + case QPlaceContent::EditorialType: + collection->insert(offset + i, parseEditorial(itemObject, engine)); + break; + case QPlaceContent::NoType: + break; + } + } + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/placesv2/jsonparserhelpers.h b/src/plugins/geoservices/nokia/placesv2/jsonparserhelpers.h new file mode 100644 index 0000000..4c7fffb --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/jsonparserhelpers.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef JSONPARSERHELPERS_H +#define JSONPARSERHELPERS_H + +#include + +QT_BEGIN_NAMESPACE + +class QJsonArray; +class QJsonObject; +class QGeoCoordinate; +class QPlaceContactDetail; +class QPlaceImage; +class QPlaceReview; +class QPlaceEditorial; +class QPlaceCategory; +class QPlaceContentRequest; +class QPlaceManagerEngineNokiaV2; + +QGeoCoordinate parseCoordinate(const QJsonArray &coordinateArray); +QPlaceSupplier parseSupplier(const QJsonObject &supplierObject, + const QPlaceManagerEngineNokiaV2 *engine); +QPlaceCategory parseCategory(const QJsonObject &categoryObject, + const QPlaceManagerEngineNokiaV2 *engine); +QList parseCategories(const QJsonArray &categoryArray, + const QPlaceManagerEngineNokiaV2 *engine); +QList parseContactDetails(const QJsonArray &contacts); + +QPlaceImage parseImage(const QJsonObject &imageObject, + const QPlaceManagerEngineNokiaV2 *engine); +QPlaceReview parseReview(const QJsonObject &reviewObject, + const QPlaceManagerEngineNokiaV2 *engine); +QPlaceEditorial parseEditorial(const QJsonObject &editorialObject, + const QPlaceManagerEngineNokiaV2 *engine); + +void parseCollection(QPlaceContent::Type type, const QJsonObject &object, + QPlaceContent::Collection *collection, int *totalCount, + QPlaceContentRequest *previous, QPlaceContentRequest *next, + const QPlaceManagerEngineNokiaV2 *engine); + +QT_END_NAMESPACE + +#endif // JSONPARSERHELPERS_H diff --git a/src/plugins/geoservices/nokia/placesv2/placesv2.pri b/src/plugins/geoservices/nokia/placesv2/placesv2.pri new file mode 100644 index 0000000..18c9fe3 --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/placesv2.pri @@ -0,0 +1,21 @@ +QT *= location network + +HEADERS += \ + qplacemanagerengine_nokiav2.h \ + placesv2/qplacecategoriesreplyhere.h \ + placesv2/qplacecontentreplyimpl.h \ + placesv2/qplacedetailsreplyimpl.h \ + placesv2/qplaceidreplyimpl.h \ + placesv2/qplacesearchreplyhere.h \ + placesv2/qplacesearchsuggestionreplyimpl.h \ + placesv2/jsonparserhelpers.h + +SOURCES += \ + qplacemanagerengine_nokiav2.cpp \ + placesv2/qplacecategoriesreplyhere.cpp \ + placesv2/qplacecontentreplyimpl.cpp \ + placesv2/qplacedetailsreplyimpl.cpp \ + placesv2/qplaceidreplyimpl.cpp \ + placesv2/qplacesearchreplyhere.cpp \ + placesv2/qplacesearchsuggestionreplyimpl.cpp \ + placesv2/jsonparserhelpers.cpp diff --git a/src/plugins/geoservices/nokia/placesv2/qplacecategoriesreplyhere.cpp b/src/plugins/geoservices/nokia/placesv2/qplacecategoriesreplyhere.cpp new file mode 100644 index 0000000..5ae0d92 --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacecategoriesreplyhere.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacecategoriesreplyhere.h" + +QT_BEGIN_NAMESPACE + +QPlaceCategoriesReplyHere::QPlaceCategoriesReplyHere(QObject *parent) +: QPlaceReply(parent) +{ +} + +QPlaceCategoriesReplyHere::~QPlaceCategoriesReplyHere() +{ +} + +void QPlaceCategoriesReplyHere::emitFinished() +{ + setFinished(true); + emit finished(); +} + +void QPlaceCategoriesReplyHere::setError(QPlaceReply::Error error_, const QString &errorString) +{ + QPlaceReply::setError(error_, errorString); + emit error(error_, errorString); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/placesv2/qplacecategoriesreplyhere.h b/src/plugins/geoservices/nokia/placesv2/qplacecategoriesreplyhere.h new file mode 100644 index 0000000..4258d39 --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacecategoriesreplyhere.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECATEGORIESREPLYHERE_H +#define QPLACECATEGORIESREPLYHERE_H + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceCategoriesReplyHere : public QPlaceReply +{ + Q_OBJECT + +public: + explicit QPlaceCategoriesReplyHere(QObject *parent = 0); + ~QPlaceCategoriesReplyHere(); + + void emitFinished(); + +private slots: + void setError(QPlaceReply::Error error_, const QString &errorString); +}; + +QT_END_NAMESPACE + +#endif // QPLACECATEGORIESREPLYHERE_H diff --git a/src/plugins/geoservices/nokia/placesv2/qplacecontentreplyimpl.cpp b/src/plugins/geoservices/nokia/placesv2/qplacecontentreplyimpl.cpp new file mode 100644 index 0000000..9a5cbf4 --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacecontentreplyimpl.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "jsonparserhelpers.h" +#include "qplacecontentreplyimpl.h" +#include "../qplacemanagerengine_nokiav2.h" +#include "../qgeoerror_messages.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QPlaceContentReplyImpl::QPlaceContentReplyImpl(const QPlaceContentRequest &request, + QNetworkReply *reply, + QPlaceManagerEngineNokiaV2 *engine) + : QPlaceContentReply(engine), m_reply(reply), m_engine(engine) +{ + Q_ASSERT(engine); + setRequest(request); + + if (!m_reply) + return; + + m_reply->setParent(this); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(replyError(QNetworkReply::NetworkError))); +} + +QPlaceContentReplyImpl::~QPlaceContentReplyImpl() +{ +} + +void QPlaceContentReplyImpl::abort() +{ + if (m_reply) + m_reply->abort(); +} + +void QPlaceContentReplyImpl::setError(QPlaceReply::Error error_, const QString &errorString) +{ + QPlaceContentReply::setError(error_, errorString); + emit error(error_, errorString); + setFinished(true); + emit finished(); +} + +void QPlaceContentReplyImpl::replyFinished() +{ + if (m_reply->isOpen()) { + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll()); + if (!document.isObject()) { + setError(ParseError, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, PARSE_ERROR)); + return; + } + + QJsonObject object = document.object(); + + QPlaceContent::Collection collection; + int totalCount; + QPlaceContentRequest previous; + QPlaceContentRequest next; + + parseCollection(request().contentType(), object, &collection, &totalCount, + &previous, &next, m_engine); + + setTotalCount(totalCount); + setContent(collection); + setPreviousPageRequest(previous); + setNextPageRequest(next); + } + + m_reply->deleteLater(); + m_reply = 0; + + setFinished(true); + emit finished(); +} + +void QPlaceContentReplyImpl::replyError(QNetworkReply::NetworkError error) +{ + switch (error) { + case QNetworkReply::OperationCanceledError: + setError(CancelError, "Request canceled."); + break; + default: + setError(CommunicationError, "Network error."); + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/placesv2/qplacecontentreplyimpl.h b/src/plugins/geoservices/nokia/placesv2/qplacecontentreplyimpl.h new file mode 100644 index 0000000..c7ef7ee --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacecontentreplyimpl.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECONTENTREPLYIMPL_H +#define QPLACECONTENTREPLYIMPL_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceManager; +class QPlaceManagerEngineNokiaV2; + +class QPlaceContentReplyImpl : public QPlaceContentReply +{ + Q_OBJECT + +public: + QPlaceContentReplyImpl(const QPlaceContentRequest &request, QNetworkReply *reply, + QPlaceManagerEngineNokiaV2 *engine); + ~QPlaceContentReplyImpl(); + + void abort(); + +private slots: + void setError(QPlaceReply::Error error_, const QString &errorString); + void replyFinished(); + void replyError(QNetworkReply::NetworkError error); + +private: + QNetworkReply *m_reply; + QPlaceManagerEngineNokiaV2 *m_engine; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/placesv2/qplacedetailsreplyimpl.cpp b/src/plugins/geoservices/nokia/placesv2/qplacedetailsreplyimpl.cpp new file mode 100644 index 0000000..e85b9cc --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacedetailsreplyimpl.cpp @@ -0,0 +1,347 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacedetailsreplyimpl.h" +#include "jsonparserhelpers.h" +#include "../qplacemanagerengine_nokiav2.h" +#include "../qgeoerror_messages.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// These countries format the street address as: {house number} {street name} +// All other countries format it as: {street name} {house number} +static const char COUNTRY_TABLE_string[] = + "CAN\0" + "NZL\0" + "GBR\0" + "AUS\0" + "LKA\0" + "USA\0" + "SGP\0" + "FRA\0" + "BHS\0" + "CHN\0" + "IND\0" + "IRL\0" + "ARE\0" + "\0"; + +static const int COUNTRY_TABLE_indices[] = { + 0, 4, 8, 12, 16, 20, 24, 28, + 32, 36, 40, 44, 48, -1 +}; + +static bool countryTableContains(const QString &countryCode) +{ + for (int i = 0; COUNTRY_TABLE_indices[i] != -1; ++i) { + if (countryCode == QLatin1String(COUNTRY_TABLE_string + COUNTRY_TABLE_indices[i])) + return true; + } + + return false; +} + +QPlaceDetailsReplyImpl::QPlaceDetailsReplyImpl(QNetworkReply *reply, + QPlaceManagerEngineNokiaV2 *parent) + : QPlaceDetailsReply(parent), m_reply(reply), m_engine(parent) +{ + Q_ASSERT(parent); + + if (!m_reply) + return; + + m_reply->setParent(this); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); +} + +QPlaceDetailsReplyImpl::~QPlaceDetailsReplyImpl() +{ +} + +void QPlaceDetailsReplyImpl::abort() +{ + if (m_reply) + m_reply->abort(); +} + +void QPlaceDetailsReplyImpl::setError(QPlaceReply::Error error_, const QString &errorString) +{ + QPlaceReply::setError(error_, errorString); + emit error(error_, errorString); + setFinished(true); + emit finished(); +} + +void QPlaceDetailsReplyImpl::replyFinished() +{ + if (m_reply->error() != QNetworkReply::NoError) { + switch (m_reply->error()) { + case QNetworkReply::OperationCanceledError: + setError(CancelError, "Request canceled."); + break; + case QNetworkReply::ContentNotFoundError: + setError(PlaceDoesNotExistError, + QString::fromLatin1("The id, %1, does not reference an existing place") + .arg(m_placeId)); + break; + default: + setError(CommunicationError, "Network error."); + } + return; + } + + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll()); + if (!document.isObject()) { + setError(ParseError, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, PARSE_ERROR)); + return; + } + + QJsonObject object = document.object(); + + QPlace place; + + place.setPlaceId(object.value(QLatin1String("placeId")).toString()); + + //const QUrl view = object.value(QLatin1String("view")).toString(); + + place.setName(object.value(QLatin1String("name")).toString()); + + //if (object.contains(QLatin1String("distance"))) + // double distance = object.value(QLatin1String("distance")).toDouble(); + + //if (object.contains(QLatin1String("alternativeNames"))) { + // QJsonArray alternativeNames = object.value(QLatin1String("alternativeNames")).toArray(); + //} + + QGeoLocation location; + + QJsonObject locationObject = object.value(QLatin1String("location")).toObject(); + + //if (locationObject.contains(QLatin1String("locationId"))) + // const QString locationId = locationObject.value(QLatin1String("locationId")).toString(); + + QJsonArray position = locationObject.value(QLatin1String("position")).toArray(); + location.setCoordinate(QGeoCoordinate(position.at(0).toDouble(), position.at(1).toDouble())); + + QGeoAddress address; + + QJsonObject addressObject = locationObject.value(QLatin1String("address")).toObject(); + + address.setText(addressObject.value(QLatin1String("text")).toString()); + + address.setCountry(addressObject.value(QLatin1String("country")).toString()); + address.setCountryCode(addressObject.value(QLatin1String("countryCode")).toString()); + + QString house; + QString street; + + if (addressObject.contains(QLatin1String("house"))) + house = addressObject.value(QLatin1String("house")).toString(); + if (addressObject.contains(QLatin1String("street"))) + street = addressObject.value(QLatin1String("street")).toString(); + + if (countryTableContains(address.countryCode())) { + if (!house.isEmpty() && !street.startsWith(house)) + street = house + QLatin1Char(' ') + street; + } else { + if (!house.isEmpty() && !street.endsWith(house)) + street += QLatin1Char(' ') + house; + } + + address.setStreet(street); + + if (addressObject.contains(QLatin1String("city"))) + address.setCity(addressObject.value(QLatin1String("city")).toString()); + if (addressObject.contains(QLatin1String("district"))) + address.setDistrict(addressObject.value(QLatin1String("district")).toString()); + if (addressObject.contains(QLatin1String("state"))) + address.setState(addressObject.value(QLatin1String("state")).toString()); + if (addressObject.contains(QLatin1String("county"))) + address.setCounty(addressObject.value(QLatin1String("county")).toString()); + if (addressObject.contains(QLatin1String("postalCode"))) + address.setPostalCode(addressObject.value(QLatin1String("postalCode")).toString()); + + location.setAddress(address); + + if (locationObject.contains(QLatin1String("bbox"))) { + QJsonArray bbox = locationObject.value(QLatin1String("bbox")).toArray(); + QGeoRectangle box(QGeoCoordinate(bbox.at(3).toDouble(), bbox.at(0).toDouble()), + QGeoCoordinate(bbox.at(1).toDouble(), bbox.at(2).toDouble())); + location.setBoundingBox(box); + } + + place.setLocation(location); + + place.setCategories(parseCategories(object.value(QLatin1String("categories")).toArray(), + m_engine)); + + place.setIcon(m_engine->icon(object.value(QLatin1String("icon")).toString(), + place.categories())); + + if (object.contains(QLatin1String("contacts"))) { + QJsonObject contactsObject = object.value(QLatin1String("contacts")).toObject(); + + if (contactsObject.contains(QLatin1String("phone"))) { + place.setContactDetails(QPlaceContactDetail::Phone, + parseContactDetails(contactsObject.value(QLatin1String("phone")).toArray())); + } + if (contactsObject.contains(QLatin1String("fax"))) { + place.setContactDetails(QPlaceContactDetail::Fax, + parseContactDetails(contactsObject.value(QLatin1String("fax")).toArray())); + } + if (contactsObject.contains(QLatin1String("website"))) { + place.setContactDetails(QPlaceContactDetail::Website, + parseContactDetails(contactsObject.value(QLatin1String("website")).toArray())); + } + if (contactsObject.contains(QLatin1String("email"))) { + place.setContactDetails(QPlaceContactDetail::Email, + parseContactDetails(contactsObject.value(QLatin1String("email")).toArray())); + } + } + + //if (object.contains(QLatin1String("verifiedByOwner"))) + // bool verifiedByOwner = object.value(QLatin1String("verifiedByOwner")).toBool(); + + if (object.contains(QLatin1String("attribution"))) + place.setAttribution(object.value(QLatin1String("attribution")).toString()); + + if (object.contains(QLatin1String("supplier"))) { + place.setSupplier(parseSupplier(object.value(QLatin1String("supplier")).toObject(), + m_engine)); + } + + if (object.contains(QLatin1String("ratings"))) { + QJsonObject ratingsObject = object.value(QLatin1String("ratings")).toObject(); + + QPlaceRatings ratings; + ratings.setAverage(ratingsObject.value(QLatin1String("average")).toDouble()); + ratings.setCount(ratingsObject.value(QLatin1String("count")).toDouble()); + ratings.setMaximum(5.0); + + place.setRatings(ratings); + } + + if (object.contains(QLatin1String("extended"))) { + QJsonObject extendedObject = object.value(QLatin1String("extended")).toObject(); + + for (auto it = extendedObject.constBegin(), end = extendedObject.constEnd(); it != end; ++it) { + QJsonObject attributeObject = it.value().toObject(); + + QPlaceAttribute attribute; + + attribute.setLabel(attributeObject.value(QLatin1String("label")).toString()); + attribute.setText(attributeObject.value(QLatin1String("text")).toString()); + + QString key = it.key(); + if (key == QLatin1String("payment")) + place.setExtendedAttribute(QPlaceAttribute::Payment, attribute); + else if (key == QLatin1String("openingHours")) + place.setExtendedAttribute(QPlaceAttribute::OpeningHours, attribute); + else + place.setExtendedAttribute(key, attribute); + } + } + + if (object.contains(QLatin1String("media"))) { + QJsonObject mediaObject = object.value(QLatin1String("media")).toObject(); + + if (mediaObject.contains(QLatin1String("images"))) { + QPlaceContent::Collection collection; + int totalCount = 0; + + parseCollection(QPlaceContent::ImageType, + mediaObject.value(QLatin1String("images")).toObject(), + &collection, &totalCount, 0, 0, m_engine); + + place.setTotalContentCount(QPlaceContent::ImageType, totalCount); + place.setContent(QPlaceContent::ImageType, collection); + } + if (mediaObject.contains(QLatin1String("editorials"))) { + QPlaceContent::Collection collection; + int totalCount = 0; + + parseCollection(QPlaceContent::EditorialType, + mediaObject.value(QLatin1String("editorials")).toObject(), + &collection, &totalCount, 0, 0, m_engine); + + place.setTotalContentCount(QPlaceContent::EditorialType, totalCount); + place.setContent(QPlaceContent::EditorialType, collection); + } + if (mediaObject.contains(QLatin1String("reviews"))) { + QPlaceContent::Collection collection; + int totalCount = 0; + + parseCollection(QPlaceContent::ReviewType, + mediaObject.value(QLatin1String("reviews")).toObject(), + &collection, &totalCount, 0, 0, m_engine); + + place.setTotalContentCount(QPlaceContent::ReviewType, totalCount); + place.setContent(QPlaceContent::ReviewType, collection); + } + } + + //if (object.contains(QLatin1String("related"))) { + // QJsonObject relatedObject = object.value(QLatin1String("related")).toObject(); + //} + + QPlaceAttribute provider; + provider.setText(QLatin1String("here")); + place.setExtendedAttribute(QPlaceAttribute::Provider, provider); + + place.setVisibility(QLocation::PublicVisibility); + place.setDetailsFetched(true); + setPlace(place); + + m_reply->deleteLater(); + m_reply = 0; + + setFinished(true); + emit finished(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/placesv2/qplacedetailsreplyimpl.h b/src/plugins/geoservices/nokia/placesv2/qplacedetailsreplyimpl.h new file mode 100644 index 0000000..2524d04 --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacedetailsreplyimpl.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEDETAILSREPLYIMPL_H +#define QPLACEDETAILSREPLYIMPL_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceManager; +class QPlaceManagerEngineNokiaV2; + +class QPlaceDetailsReplyImpl : public QPlaceDetailsReply +{ + Q_OBJECT + +public: + QPlaceDetailsReplyImpl(QNetworkReply *reply, QPlaceManagerEngineNokiaV2 *parent); + ~QPlaceDetailsReplyImpl(); + + void abort(); + void setPlaceId(const QString &placeId) { m_placeId = placeId; } + +private slots: + void setError(QPlaceReply::Error error_, const QString &errorString); + void replyFinished(); + +private: + QNetworkReply *m_reply; + QPlaceManagerEngineNokiaV2 *m_engine; + QString m_placeId; +}; + +QT_END_NAMESPACE + +#endif // QPLACEDETAILSREPLYIMPL_H diff --git a/src/plugins/geoservices/nokia/placesv2/qplaceidreplyimpl.cpp b/src/plugins/geoservices/nokia/placesv2/qplaceidreplyimpl.cpp new file mode 100644 index 0000000..2378d0b --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplaceidreplyimpl.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaceidreplyimpl.h" + +QT_BEGIN_NAMESPACE + +QPlaceIdReplyImpl::QPlaceIdReplyImpl(QPlaceIdReply::OperationType type, QObject *parent) +: QPlaceIdReply(type, parent) +{ +} + +QPlaceIdReplyImpl::~QPlaceIdReplyImpl() +{ +} + +void QPlaceIdReplyImpl::setId(const QString &id) +{ + QPlaceIdReply::setId(id); +} + +void QPlaceIdReplyImpl::setError(QPlaceReply::Error error_, const QString &errorString) +{ + if (error_ != QPlaceReply::NoError) { + QPlaceIdReply::setError(error_, errorString); + emit error(error_, errorString); + } + + setFinished(true); + emit finished(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/placesv2/qplaceidreplyimpl.h b/src/plugins/geoservices/nokia/placesv2/qplaceidreplyimpl.h new file mode 100644 index 0000000..091f3d6 --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplaceidreplyimpl.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef UNSUPPORTED_REPLIES_H +#define UNSUPPORTED_REPLIES_H + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceIdReplyImpl : public QPlaceIdReply +{ + Q_OBJECT + +public: + QPlaceIdReplyImpl(QPlaceIdReply::OperationType type, QObject *parent = 0); + ~QPlaceIdReplyImpl(); + + void setId(const QString &id); + +private slots: + void setError(QPlaceReply::Error error_, const QString &errorString); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/placesv2/qplacesearchreplyhere.cpp b/src/plugins/geoservices/nokia/placesv2/qplacesearchreplyhere.cpp new file mode 100644 index 0000000..3a56c92 --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacesearchreplyhere.cpp @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacesearchreplyhere.h" +#include "jsonparserhelpers.h" +#include "../qplacemanagerengine_nokiav2.h" +#include "../qgeoerror_messages.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +QPlaceSearchReplyHere::QPlaceSearchReplyHere(const QPlaceSearchRequest &request, + QNetworkReply *reply, + QPlaceManagerEngineNokiaV2 *parent) + : QPlaceSearchReply(parent), m_reply(reply), m_engine(parent) +{ + Q_ASSERT(parent); + + setRequest(request); + + if (!m_reply) + return; + + m_reply->setParent(this); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); +} + +QPlaceSearchReplyHere::~QPlaceSearchReplyHere() +{ +} + +void QPlaceSearchReplyHere::abort() +{ + if (m_reply) + m_reply->abort(); +} + +void QPlaceSearchReplyHere::setError(QPlaceReply::Error error_, const QString &errorString) +{ + QPlaceReply::setError(error_, errorString); + emit error(error_, errorString); + setFinished(true); + emit finished(); +} + +void QPlaceSearchReplyHere::replyFinished() +{ + if (m_reply->error() != QNetworkReply::NoError) { + switch (m_reply->error()) { + case QNetworkReply::OperationCanceledError: + setError(CancelError, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, CANCEL_ERROR)); + break; + case QNetworkReply::ContentNotFoundError: + setError(PlaceDoesNotExistError, + QString::fromLatin1("The id, %1, does not reference an existing place") + .arg(request().recommendationId())); + break; + default: + setError(CommunicationError, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, NETWORK_ERROR)); + } + return; + } + + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll()); + if (!document.isObject()) { + setError(ParseError, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, PARSE_ERROR)); + return; + } + + QJsonObject resultsObject = document.object(); + + if (resultsObject.contains(QStringLiteral("results"))) + resultsObject = resultsObject.value(QStringLiteral("results")).toObject(); + + QJsonArray items = resultsObject.value(QStringLiteral("items")).toArray(); + + QList results; + for (int i = 0; i < items.count(); ++i) { + QJsonObject item = items.at(i).toObject(); + + const QString type = item.value(QStringLiteral("type")).toString(); + if (type == QStringLiteral("urn:nlp-types:place")) + results.append(parsePlaceResult(item)); + else if (type == QStringLiteral("urn:nlp-types:search")) + results.append(parseSearchResult(item)); + } + + if (resultsObject.contains(QStringLiteral("next"))) { + QPlaceSearchRequest request; + request.setSearchContext(QUrl(resultsObject.value(QStringLiteral("next")).toString())); + setNextPageRequest(request); + } + + if (resultsObject.contains(QStringLiteral("previous"))) { + QPlaceSearchRequest request; + request.setSearchContext(QUrl(resultsObject.value(QStringLiteral("previous")).toString())); + setPreviousPageRequest(request); + } + + setResults(results); + + m_reply->deleteLater(); + m_reply = 0; + + setFinished(true); + emit finished(); +} + +QPlaceResult QPlaceSearchReplyHere::parsePlaceResult(const QJsonObject &item) const +{ + QPlaceResult result; + + if (item.contains(QStringLiteral("distance"))) + result.setDistance(item.value(QStringLiteral("distance")).toDouble()); + + QPlace place; + + QGeoLocation location; + + location.setCoordinate(parseCoordinate(item.value(QStringLiteral("position")).toArray())); + + const QString vicinity = item.value(QStringLiteral("vicinity")).toString(); + QGeoAddress address; + address.setText(vicinity); + location.setAddress(address); + + if (item.contains(QStringLiteral("bbox"))) { + QJsonArray bbox = item.value(QStringLiteral("bbox")).toArray(); + QGeoRectangle box(QGeoCoordinate(bbox.at(3).toDouble(), bbox.at(0).toDouble()), + QGeoCoordinate(bbox.at(1).toDouble(), bbox.at(2).toDouble())); + location.setBoundingBox(box); + } + + place.setLocation(location); + + QPlaceRatings ratings; + ratings.setAverage(item.value(QStringLiteral("averageRating")).toDouble()); + ratings.setMaximum(5.0); + place.setRatings(ratings); + + const QString title = item.value(QStringLiteral("title")).toString(); + place.setName(title); + result.setTitle(title); + + QPlaceIcon icon = m_engine->icon(item.value(QStringLiteral("icon")).toString()); + place.setIcon(icon); + result.setIcon(icon); + + place.setCategory(parseCategory(item.value(QStringLiteral("category")).toObject(), + m_engine)); + + //QJsonArray having = item.value(QStringLiteral("having")).toArray(); + + result.setSponsored(item.value(QStringLiteral("sponsored")).toBool()); + + QUrl href = item.value(QStringLiteral("href")).toString(); + //QUrl type = item.value(QStringLiteral("type")).toString(); + + place.setPlaceId(href.path().mid(18, 41)); + + QPlaceAttribute provider; + provider.setText(QStringLiteral("here")); + place.setExtendedAttribute(QPlaceAttribute::Provider, provider); + place.setVisibility(QLocation::PublicVisibility); + + result.setPlace(place); + + return result; +} + +QPlaceProposedSearchResult QPlaceSearchReplyHere::parseSearchResult(const QJsonObject &item) const +{ + QPlaceProposedSearchResult result; + + result.setTitle(item.value(QStringLiteral("title")).toString()); + + QPlaceIcon icon = m_engine->icon(item.value(QStringLiteral("icon")).toString()); + result.setIcon(icon); + + QPlaceSearchRequest request; + request.setSearchContext(QUrl(item.value("href").toString())); + + result.setSearchRequest(request); + + return result; +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/placesv2/qplacesearchreplyhere.h b/src/plugins/geoservices/nokia/placesv2/qplacesearchreplyhere.h new file mode 100644 index 0000000..a712ab8 --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacesearchreplyhere.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESEARCHREPLYHERE_H +#define QPLACESEARCHREPLYHERE_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceManagerEngineNokiaV2; +class QPlaceResult; +class QPlaceProposedSearchResult; + +class QPlaceSearchReplyHere : public QPlaceSearchReply +{ + Q_OBJECT + +public: + explicit QPlaceSearchReplyHere(const QPlaceSearchRequest &request, + QNetworkReply *reply, + QPlaceManagerEngineNokiaV2 *parent); + ~QPlaceSearchReplyHere(); + + void abort(); + +private slots: + void setError(QPlaceReply::Error error_, const QString &errorString); + void replyFinished(); + +private: + QPlaceResult parsePlaceResult(const QJsonObject &item) const; + QPlaceProposedSearchResult parseSearchResult(const QJsonObject &item) const; + + QNetworkReply *m_reply; + QPlaceManagerEngineNokiaV2 *m_engine; +}; + +QT_END_NAMESPACE + +#endif // QPLACESEARCHREPLYHERE_H diff --git a/src/plugins/geoservices/nokia/placesv2/qplacesearchsuggestionreplyimpl.cpp b/src/plugins/geoservices/nokia/placesv2/qplacesearchsuggestionreplyimpl.cpp new file mode 100644 index 0000000..6ed8b5a --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacesearchsuggestionreplyimpl.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacesearchsuggestionreplyimpl.h" +#include "../qgeoerror_messages.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QPlaceSearchSuggestionReplyImpl::QPlaceSearchSuggestionReplyImpl(QNetworkReply *reply, + QObject *parent) +: QPlaceSearchSuggestionReply(parent), m_reply(reply) +{ + if (!m_reply) + return; + + m_reply->setParent(this); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); +} + +QPlaceSearchSuggestionReplyImpl::~QPlaceSearchSuggestionReplyImpl() +{ +} + +void QPlaceSearchSuggestionReplyImpl::abort() +{ + if (m_reply) + m_reply->abort(); +} + +void QPlaceSearchSuggestionReplyImpl::setError(QPlaceReply::Error error_, + const QString &errorString) +{ + QPlaceReply::setError(error_, errorString); + emit error(error_, errorString); + setFinished(true); + emit finished(); +} + +void QPlaceSearchSuggestionReplyImpl::replyFinished() +{ + if (m_reply->error() != QNetworkReply::NoError) { + switch (m_reply->error()) { + case QNetworkReply::OperationCanceledError: + setError(CancelError, "Request canceled."); + break; + default: + setError(CommunicationError, "Network error."); + } + return; + } + + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll()); + if (!document.isObject()) { + setError(ParseError, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, PARSE_ERROR)); + emit error(error(), errorString()); + return; + } + + QJsonObject object = document.object(); + + QJsonArray suggestions = object.value(QStringLiteral("suggestions")).toArray(); + + QStringList s; + for (int i = 0; i < suggestions.count(); ++i) { + QJsonValue v = suggestions.at(i); + if (v.isString()) + s.append(v.toString()); + } + + setSuggestions(s); + + m_reply->deleteLater(); + m_reply = 0; + + setFinished(true); + emit finished(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/placesv2/qplacesearchsuggestionreplyimpl.h b/src/plugins/geoservices/nokia/placesv2/qplacesearchsuggestionreplyimpl.h new file mode 100644 index 0000000..dbcba3a --- /dev/null +++ b/src/plugins/geoservices/nokia/placesv2/qplacesearchsuggestionreplyimpl.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESEARCHSUGGESTIONREPLYIMPL_H +#define QPLACESEARCHSUGGESTIONREPLYIMPL_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceSearchSuggestionReplyImpl : public QPlaceSearchSuggestionReply +{ + Q_OBJECT + +public: + explicit QPlaceSearchSuggestionReplyImpl(QNetworkReply *reply, QObject *parent = 0); + ~QPlaceSearchSuggestionReplyImpl(); + + void abort(); + +private slots: + void setError(QPlaceReply::Error error_, const QString &errorString); + void replyFinished(); + +private: + QNetworkReply *m_reply; +}; + +QT_END_NAMESPACE + +#endif // QPLACESEARCHSUGGESTIONREPLYIMPL_H diff --git a/src/plugins/geoservices/nokia/qgeocodereply_nokia.cpp b/src/plugins/geoservices/nokia/qgeocodereply_nokia.cpp new file mode 100644 index 0000000..73d2d4c --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeocodereply_nokia.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocodereply_nokia.h" +#include "qgeocodexmlparser.h" +#include "qgeoerror_messages.h" + +#include +#include + +Q_DECLARE_METATYPE(QList) + +QT_BEGIN_NAMESPACE + +QGeoCodeReplyNokia::QGeoCodeReplyNokia(QNetworkReply *reply, int limit, int offset, + const QGeoShape &viewport, QObject *parent) +: QGeoCodeReply(parent), m_reply(reply), m_parsing(false) +{ + qRegisterMetaType >(); + + connect(m_reply, SIGNAL(finished()), this, SLOT(networkFinished())); + connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(networkError(QNetworkReply::NetworkError))); + + setLimit(limit); + setOffset(offset); + setViewport(viewport); +} + +QGeoCodeReplyNokia::~QGeoCodeReplyNokia() +{ + abort(); +} + +void QGeoCodeReplyNokia::abort() +{ + if (!m_reply) { + m_parsing = false; + return; + } + + m_reply->abort(); + + m_reply->deleteLater(); + m_reply = 0; + m_parsing = false; +} + +void QGeoCodeReplyNokia::networkFinished() +{ + if (!m_reply) + return; + + if (m_reply->error() != QNetworkReply::NoError) + return; + + QGeoCodeXmlParser *parser = new QGeoCodeXmlParser; + parser->setBounds(viewport()); + connect(parser, SIGNAL(results(QList)), + this, SLOT(appendResults(QList))); + connect(parser, SIGNAL(error(QString)), this, SLOT(parseError(QString))); + + m_parsing = true; + parser->parse(m_reply->readAll()); + + m_reply->deleteLater(); + m_reply = 0; +} + +void QGeoCodeReplyNokia::networkError(QNetworkReply::NetworkError error) +{ + Q_UNUSED(error) + + if (!m_reply) + return; + + setError(QGeoCodeReply::CommunicationError, m_reply->errorString()); + + m_reply->deleteLater(); + m_reply = 0; +} + +void QGeoCodeReplyNokia::appendResults(const QList &locations) +{ + if (!m_parsing) + return; + + m_parsing = false; + setLocations(locations); + setFinished(true); +} + +void QGeoCodeReplyNokia::parseError(const QString &errorString) +{ + Q_UNUSED(errorString) + + setError(QGeoCodeReply::ParseError, + QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, RESPONSE_NOT_RECOGNIZABLE)); + abort(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeocodereply_nokia.h b/src/plugins/geoservices/nokia/qgeocodereply_nokia.h new file mode 100644 index 0000000..85726fc --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeocodereply_nokia.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODEREPLY_NOKIA_H +#define QGEOCODEREPLY_NOKIA_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCodeReplyNokia : public QGeoCodeReply +{ + Q_OBJECT +public: + QGeoCodeReplyNokia(QNetworkReply *reply, int limit, int offset, const QGeoShape &viewport, QObject *parent = 0); + ~QGeoCodeReplyNokia(); + + void abort(); + +private Q_SLOTS: + void networkFinished(); + void networkError(QNetworkReply::NetworkError error); + void appendResults(const QList &locations); + void parseError(const QString &errorString); + +private: + QNetworkReply *m_reply; + bool m_parsing; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/qgeocodexmlparser.cpp b/src/plugins/geoservices/nokia/qgeocodexmlparser.cpp new file mode 100644 index 0000000..8973886 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeocodexmlparser.cpp @@ -0,0 +1,573 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocodexmlparser.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoCodeXmlParser::QGeoCodeXmlParser() +{ +} + +QGeoCodeXmlParser::~QGeoCodeXmlParser() +{ +} + +void QGeoCodeXmlParser::setBounds(const QGeoShape &bounds) +{ + m_bounds = bounds; +} + +void QGeoCodeXmlParser::parse(const QByteArray &data) +{ + m_data = data; + QThreadPool::globalInstance()->start(this); +} + +void QGeoCodeXmlParser::run() +{ + m_reader = new QXmlStreamReader(m_data); + + if (!parseRootElement()) + emit error(m_reader->errorString()); + else + emit results(m_results); + + delete m_reader; + m_reader = 0; +} + +bool QGeoCodeXmlParser::parseRootElement() +{ + /* + + + + + + + + + + + + + + + + + + */ + + if (m_reader->readNextStartElement()) { + if (m_reader->name() == "places") { + if (m_reader->attributes().hasAttribute("resultCode")) { + QStringRef result = m_reader->attributes().value("resultCode"); + if (result == "FAILED") { + QString resultDesc = m_reader->attributes().value("resultDescription").toString(); + if (resultDesc.isEmpty()) + resultDesc = "The attribute \"resultCode\" of the element \"places\" indicates that the request failed."; + + m_reader->raiseError(resultDesc); + + return false; + } else if (result != "OK") { + m_reader->raiseError(QString("The attribute \"resultCode\" of the element \"places\" has an unknown value (value was %1).").arg(result.toString())); + return false; + } + } + + while (m_reader->readNextStartElement()) { + if (m_reader->name() == "place") { + QGeoLocation location; + + if (!parsePlace(&location)) + return false; + + if (!m_bounds.isValid() || m_bounds.contains(location.coordinate())) + m_results.append(location); + } else { + m_reader->raiseError(QString("The element \"places\" did not expect a child element named \"%1\".").arg(m_reader->name().toString())); + return false; + } + } + } else { + m_reader->raiseError(QString("The root element is expected to have the name \"places\" (root element was named \"%1\").").arg(m_reader->name().toString())); + return false; + } + } else { + m_reader->raiseError("Expected a root element named \"places\" (no root element found)."); + return false; + } + + if (m_reader->readNextStartElement()) { + m_reader->raiseError(QString("A single root element named \"places\" was expected (second root element was named \"%1\")").arg(m_reader->name().toString())); + return false; + } + + return true; +} + + +//Note: the term Place here is semi-confusing since +// the xml 'place' is actually a location ie coord + address +bool QGeoCodeXmlParser::parsePlace(QGeoLocation *location) +{ + /* + + + + + + + + + + + + + + + + */ + + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "place"); + + if (!m_reader->attributes().hasAttribute("title")) { + m_reader->raiseError("The element \"place\" did not have the required attribute \"title\"."); + return false; + } + + if (!m_reader->attributes().hasAttribute("language")) { + //m_reader->raiseError("The element \"place\" did not have the required attribute \"language\"."); + //return false; + } else { + QString lang = m_reader->attributes().value("language").toString(); + + if (lang.length() != 3) { + m_reader->raiseError(QString("The attribute \"language\" of the element \"place\" was not of length 3 (length was %1).").arg(lang.length())); + return false; + } + } + + bool parsedLocation = false; + bool parsedAddress = false; + bool parsedAlternatives = false; + + while (m_reader->readNextStartElement()) { + QString name = m_reader->name().toString(); + if (name == "location") { + if (parsedLocation) { + m_reader->raiseError("The element \"place\" has multiple child elements named \"location\" (exactly one expected)"); + return false; + } + + if (!parseLocation(location)) + return false; + + parsedLocation = true; + } else if (name == "address") { + if (parsedAddress) { + m_reader->raiseError("The element \"place\" has multiple child elements named \"address\" (at most one expected)"); + return false; + } + + QGeoAddress address; + if (!parseAddress(&address)) + return false; + else + location->setAddress(address); + + location->setAddress(address); + + parsedAddress = true; + } else if (name == "alternatives") { + if (parsedAlternatives) { + m_reader->raiseError("The element \"place\" has multiple child elements named \"alternatives\" (at most one expected)"); + return false; + } + + // skip alternatives for now + // need to work out if we have a use for them at all + // and how to store them if we get them + m_reader->skipCurrentElement(); + + parsedAlternatives = true; + } else { + m_reader->raiseError(QString("The element \"place\" did not expect a child element named \"%1\".").arg(m_reader->name().toString())); + return false; + } + } + + if (!parsedLocation) { + m_reader->raiseError("The element \"place\" has no child elements named \"location\" (exactly one expected)"); + return false; + } + + return true; +} + +//Note: the term Place here is semi-confusing since +// the xml 'location' is actually a parital location i.e coord +// as opposed to coord + address +bool QGeoCodeXmlParser::parseLocation(QGeoLocation *location) +{ + /* + + + + + + + + + + + + + + */ + + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "location"); + + bool parsedPosition = false; + bool parsedBounds = false; + + while (m_reader->readNextStartElement()) { + QString name = m_reader->name().toString(); + if (name == "position") { + if (parsedPosition) { + m_reader->raiseError("The element \"location\" has multiple child elements named \"position\" (exactly one expected)"); + return false; + } + + QGeoCoordinate coord; + if (!parseCoordinate(&coord, "position")) + return false; + + location->setCoordinate(coord); + + parsedPosition = true; + } else if (name == "boundingBox") { + if (parsedBounds) { + m_reader->raiseError("The element \"location\" has multiple child elements named \"boundingBox\" (at most one expected)"); + return false; + } + + QGeoRectangle bounds; + + if (!parseBoundingBox(&bounds)) + return false; + + location->setBoundingBox(bounds); + + parsedBounds = true; + } else { + m_reader->raiseError(QString("The element \"location\" did not expect a child element named \"%1\".").arg(m_reader->name().toString())); + return false; + } + } + + if (!parsedPosition) { + m_reader->raiseError("The element \"location\" has no child elements named \"position\" (exactly one expected)"); + return false; + } + + return true; +} + +bool QGeoCodeXmlParser::parseAddress(QGeoAddress *address) +{ + /* + + + + + + + + + + + + + + + + + + + + + + + + + + + */ + + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "address"); + + // currently ignoring the type of the address + + if (!m_reader->readNextStartElement()) + return true; + + if (m_reader->name() == "country") { + address->setCountry(m_reader->readElementText()); + if (!m_reader->readNextStartElement()) + return true; + } + + if (m_reader->name() == "countryCode") { + address->setCountryCode(m_reader->readElementText()); + + if (address->countryCode().length() != 3) { + m_reader->raiseError(QString("The text of the element \"countryCode\" was not of length 3 (length was %1).").arg(address->countryCode().length())); + return false; + } + + if (!m_reader->readNextStartElement()) + return true; + } + + if (m_reader->name() == "state") { + address->setState(m_reader->readElementText()); + if (!m_reader->readNextStartElement()) + return true; + } + + if (m_reader->name() == "county") { + address->setCounty(m_reader->readElementText()); + if (!m_reader->readNextStartElement()) + return true; + } + + if (m_reader->name() == "city") { + address->setCity(m_reader->readElementText()); + if (!m_reader->readNextStartElement()) + return true; + } + + if (m_reader->name() == "district") { + address->setDistrict(m_reader->readElementText()); + if (!m_reader->readNextStartElement()) + return true; + } + + bool inThoroughfare = false; + + if (m_reader->name() == "thoroughfare") { + inThoroughfare = m_reader->readNextStartElement(); + + if (inThoroughfare && (m_reader->name() == "name")) { + address->setStreet(m_reader->readElementText()); + if (!m_reader->readNextStartElement()) + inThoroughfare = false; + } + + if (inThoroughfare && (m_reader->name() == "number")) { + address->setStreet(m_reader->readElementText() + ' ' + address->street()); + if (!m_reader->readNextStartElement()) + inThoroughfare = false; + } + + if (inThoroughfare) { + m_reader->raiseError(QString("The element \"thoroughFare\" did not expect the child element \"%1\" at this point (unknown child element or child element out of order).").arg(m_reader->name().toString())); + return false; + } + + if (!m_reader->readNextStartElement()) + return true; + } + + if (m_reader->name() == "postCode") { + address->setPostalCode(m_reader->readElementText()); + if (!m_reader->readNextStartElement()) + return true; + } + + m_reader->raiseError(QString("The element \"address\" did not expect the child element \"%1\" at this point (unknown child element or child element out of order).").arg(m_reader->name().toString())); + return false; +} + +bool QGeoCodeXmlParser::parseBoundingBox(QGeoRectangle *bounds) +{ + /* + + + + + + + */ + + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "boundingBox"); + + if (!m_reader->readNextStartElement()) { + m_reader->raiseError("The element \"boundingBox\" was expected to have 2 child elements (0 found)"); + return false; + } + + QGeoCoordinate nw; + + if (m_reader->name() == "topLeft") { + if (!parseCoordinate(&nw, "topLeft")) + return false; + } else { + m_reader->raiseError(QString("The element \"boundingBox\" expected this child element to be named \"topLeft\" (found an element named \"%1\")").arg(m_reader->name().toString())); + return false; + } + + if (!m_reader->readNextStartElement()) { + m_reader->raiseError("The element \"boundingBox\" was expected to have 2 child elements (1 found)"); + return false; + } + + QGeoCoordinate se; + + if (m_reader->name() == "bottomRight") { + if (!parseCoordinate(&se, "bottomRight")) + return false; + } else { + m_reader->raiseError(QString("The element \"boundingBox\" expected this child element to be named \"bottomRight\" (found an element named \"%1\")").arg(m_reader->name().toString())); + return false; + } + + if (m_reader->readNextStartElement()) { + m_reader->raiseError("The element \"boundingBox\" was expected to have 2 child elements (more than 2 found)"); + return false; + } + + *bounds = QGeoRectangle(nw, se); + + return true; +} + +bool QGeoCodeXmlParser::parseCoordinate(QGeoCoordinate *coordinate, const QString &elementName) +{ + /* + + + + + + + + + + + + + + + + + + + + + */ + + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == elementName); + + if (!m_reader->readNextStartElement()) { + m_reader->raiseError(QString("The element \"%1\" was expected to have 2 child elements (0 found)").arg(elementName)); + return false; + } + + if (m_reader->name() == "latitude") { + bool ok = false; + QString s = m_reader->readElementText(); + double lat = s.toDouble(&ok); + + if (!ok) { + m_reader->raiseError(QString("The element \"latitude\" expected a value convertable to type float (value was \"%1\")").arg(s)); + return false; + } + + if (lat < -90.0 || 90.0 < lat) { + m_reader->raiseError(QString("The element \"latitude\" expected a value between -90.0 and 90.0 inclusive (value was %1)").arg(lat)); + return false; + } + + coordinate->setLatitude(lat); + } else { + m_reader->raiseError(QString("The element \"%1\" expected this child element to be named \"latitude\" (found an element named \"%2\")").arg(elementName).arg(m_reader->name().toString())); + } + + if (!m_reader->readNextStartElement()) { + m_reader->raiseError(QString("The element \"%1\" was expected to have 2 child elements (1 found)").arg(elementName)); + return false; + } + + if (m_reader->name() == "longitude") { + bool ok = false; + QString s = m_reader->readElementText(); + double lng = s.toDouble(&ok); + + if (!ok) { + m_reader->raiseError(QString("The element \"longitude\" expected a value convertable to type float (value was \"%1\")").arg(s)); + return false; + } + + if (lng < -180.0 || 180.0 < lng) { + m_reader->raiseError(QString("The element \"longitude\" expected a value between -180.0 and 180.0 inclusive (value was %1)").arg(lng)); + return false; + } + + coordinate->setLongitude(lng); + } else { + m_reader->raiseError(QString("The element \"%1\" expected this child element to be named \"longitude\" (found an element named \"%2\")").arg(elementName).arg(m_reader->name().toString())); + } + + if (m_reader->readNextStartElement()) { + m_reader->raiseError(QString("The element \"%1\" was expected to have 2 child elements (more than 2 found)").arg(elementName)); + return false; + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeocodexmlparser.h b/src/plugins/geoservices/nokia/qgeocodexmlparser.h new file mode 100644 index 0000000..83a4b59 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeocodexmlparser.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODEXMLPARSER_H +#define QGEOCODEXMLPARSER_H + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoLocation; +class QGeoAddress; +class QGeoRectangle; +class QGeoCoordinate; +class QXmlStreamReader; + +class QGeoCodeXmlParser : public QObject, public QRunnable +{ + Q_OBJECT + +public: + QGeoCodeXmlParser(); + ~QGeoCodeXmlParser(); + + void setBounds(const QGeoShape &bounds); + void parse(const QByteArray &data); + void run(); + +signals: + void results(const QList &locations); + void error(const QString &errorString); + +private: + bool parseRootElement(); + bool parsePlace(QGeoLocation *location); + bool parseLocation(QGeoLocation *location); + bool parseAddress(QGeoAddress *address); + bool parseBoundingBox(QGeoRectangle *bounds); + bool parseCoordinate(QGeoCoordinate *coordinate, const QString &elementName); + + QGeoShape m_bounds; + QByteArray m_data; + QXmlStreamReader *m_reader; + + QList m_results; + QString m_errorString; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.cpp b/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.cpp new file mode 100644 index 0000000..2e0eae4 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.cpp @@ -0,0 +1,295 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocodingmanagerengine_nokia.h" +#include "qgeocodereply_nokia.h" +#include "marclanguagecodes.h" +#include "qgeonetworkaccessmanager.h" +#include "qgeouriprovider.h" +#include "uri_constants.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoCodingManagerEngineNokia::QGeoCodingManagerEngineNokia( + QGeoNetworkAccessManager *networkManager, + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) + : QGeoCodingManagerEngine(parameters) + , m_networkManager(networkManager) + , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.geocoding.host"), GEOCODING_HOST, GEOCODING_HOST_CN)) +{ + Q_ASSERT(networkManager); + m_networkManager->setParent(this); + + if (parameters.contains(QStringLiteral("here.token"))) + m_token = parameters.value(QStringLiteral("here.token")).toString(); + + if (parameters.contains(QStringLiteral("here.app_id"))) + m_applicationId = parameters.value(QStringLiteral("here.app_id")).toString(); + + if (error) + *error = QGeoServiceProvider::NoError; + + if (errorString) + *errorString = ""; +} + +QGeoCodingManagerEngineNokia::~QGeoCodingManagerEngineNokia() {} + +QString QGeoCodingManagerEngineNokia::getAuthenticationString() const +{ + QString authenticationString; + + if (!m_token.isEmpty() && !m_applicationId.isEmpty()) { + authenticationString += "?token="; + authenticationString += m_token; + + authenticationString += "&app_id="; + authenticationString += m_applicationId; + } + + return authenticationString; +} + + +QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QGeoAddress &address, + const QGeoShape &bounds) +{ + QString requestString = "http://"; + requestString += m_uriProvider->getCurrentHost(); + requestString += "/geocoder/gc/2.0"; + + requestString += getAuthenticationString(); + + requestString += "&lg="; + requestString += languageToMarc(locale().language()); + + if (address.country().isEmpty()) { + QStringList parts; + + if (!address.state().isEmpty()) + parts << address.state(); + + if (!address.city().isEmpty()) + parts << address.city(); + + if (!address.postalCode().isEmpty()) + parts << address.postalCode(); + + if (!address.street().isEmpty()) + parts << address.street(); + + requestString += "&obloc="; + requestString += parts.join(" "); + } else { + requestString += "&country="; + requestString += address.country(); + + if (!address.state().isEmpty()) { + requestString += "&state="; + requestString += address.state(); + } + + if (!address.city().isEmpty()) { + requestString += "&city="; + requestString += address.city(); + } + + if (!address.postalCode().isEmpty()) { + requestString += "&zip="; + requestString += address.postalCode(); + } + + if (!address.street().isEmpty()) { + requestString += "&street="; + requestString += address.street(); + } + } + + + // TODO? + // street number has been removed from QGeoAddress + // do we need to try to split it out from QGeoAddress::street + // in order to geocode properly + + // Old code: +// if (!address.streetNumber().isEmpty()) { +// requestString += "&number="; +// requestString += address.streetNumber(); +// } + + return geocode(requestString, bounds); +} + +QGeoCodeReply *QGeoCodingManagerEngineNokia::reverseGeocode(const QGeoCoordinate &coordinate, + const QGeoShape &bounds) +{ + QString requestString = "http://"; + requestString += m_uriProvider->getCurrentHost(); + requestString += "/geocoder/rgc/2.0"; + + requestString += getAuthenticationString(); + + requestString += "&long="; + requestString += trimDouble(coordinate.longitude()); + requestString += "&lat="; + requestString += trimDouble(coordinate.latitude()); + + requestString += "&lg="; + requestString += languageToMarc(locale().language()); + + return geocode(requestString, bounds); +} + +QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QString &address, + int limit, + int offset, + const QGeoShape &bounds) +{ + QString requestString = "http://"; + requestString += m_uriProvider->getCurrentHost(); + requestString += "/geocoder/gc/2.0"; + + requestString += getAuthenticationString(); + + requestString += "&lg="; + requestString += languageToMarc(locale().language()); + + requestString += "&obloc="; + requestString += address; + + if (limit > 0) { + requestString += "&total="; + requestString += QString::number(limit); + } + + if (offset > 0) { + requestString += "&offset="; + requestString += QString::number(offset); + } + + return geocode(requestString, bounds, limit, offset); +} + +QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(QString requestString, + const QGeoShape &bounds, + int limit, + int offset) +{ + QNetworkReply *networkReply = m_networkManager->get(QNetworkRequest(QUrl(requestString))); + QGeoCodeReplyNokia *reply = new QGeoCodeReplyNokia(networkReply, limit, offset, bounds, this); + + connect(reply, + SIGNAL(finished()), + this, + SLOT(placesFinished())); + + connect(reply, + SIGNAL(error(QGeoCodeReply::Error,QString)), + this, + SLOT(placesError(QGeoCodeReply::Error,QString))); + + return reply; +} + +QString QGeoCodingManagerEngineNokia::trimDouble(double degree, int decimalDigits) +{ + QString sDegree = QString::number(degree, 'g', decimalDigits); + + int index = sDegree.indexOf('.'); + + if (index == -1) + return sDegree; + else + return QString::number(degree, 'g', decimalDigits + index); +} + +void QGeoCodingManagerEngineNokia::placesFinished() +{ + QGeoCodeReply *reply = qobject_cast(sender()); + + if (!reply) + return; + + if (receivers(SIGNAL(finished(QGeoCodeReply*))) == 0) { + reply->deleteLater(); + return; + } + + emit finished(reply); +} + +void QGeoCodingManagerEngineNokia::placesError(QGeoCodeReply::Error error, const QString &errorString) +{ + QGeoCodeReply *reply = qobject_cast(sender()); + + if (!reply) + return; + + if (receivers(SIGNAL(error(QGeoCodeReply*,QGeoCodeReply::Error,QString))) == 0) { + reply->deleteLater(); + return; + } + + emit this->error(reply, error, errorString); +} + +QString QGeoCodingManagerEngineNokia::languageToMarc(QLocale::Language language) +{ + uint offset = 3 * (uint(language)); + if (language == QLocale::C || offset + 3 > sizeof(marc_language_code_list)) + return QLatin1String("eng"); + + const unsigned char *c = marc_language_code_list + offset; + if (c[0] == 0) + return QLatin1String("eng"); + + QString code(3, Qt::Uninitialized); + code[0] = ushort(c[0]); + code[1] = ushort(c[1]); + code[2] = ushort(c[2]); + + return code; +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.h b/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.h new file mode 100644 index 0000000..baa9190 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeocodingmanagerengine_nokia.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODINGMANAGER_NOKIA_H +#define QGEOCODINGMANAGER_NOKIA_H + +#include "qgeoserviceproviderplugin_nokia.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoNetworkAccessManager; +class QGeoUriProvider; + +class QGeoCodingManagerEngineNokia : public QGeoCodingManagerEngine +{ + Q_OBJECT +public: + QGeoCodingManagerEngineNokia(QGeoNetworkAccessManager *networkManager, + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString); + ~QGeoCodingManagerEngineNokia(); + + QGeoCodeReply *geocode(const QGeoAddress &address, + const QGeoShape &bounds); + QGeoCodeReply *reverseGeocode(const QGeoCoordinate &coordinate, + const QGeoShape &bounds); + + QGeoCodeReply *geocode(const QString &searchString, + int limit, + int offset, + const QGeoShape &bounds); + +private Q_SLOTS: + void placesFinished(); + void placesError(QGeoCodeReply::Error error, const QString &errorString); + +private: + static QString trimDouble(double degree, int decimalDigits = 10); + QGeoCodeReply *geocode(QString requestString, const QGeoShape &bounds, int limit = -1, int offset = 0); + QString languageToMarc(QLocale::Language language); + QString getAuthenticationString() const; + + QGeoNetworkAccessManager *m_networkManager; + QString m_token; + QString m_applicationId; + QGeoUriProvider *m_uriProvider; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/qgeoerror_messages.cpp b/src/plugins/geoservices/nokia/qgeoerror_messages.cpp new file mode 100644 index 0000000..576ecd4 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoerror_messages.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoerror_messages.h" + +QT_BEGIN_NAMESPACE + +const char NOKIA_PLUGIN_CONTEXT_NAME[] = "QtLocationQML"; +const char MISSED_CREDENTIALS[] = QT_TRANSLATE_NOOP("QtLocationQML", "Qt Location requires app_id and token parameters.\nPlease register at https://developer.here.com/ to get your personal application credentials."); +const char SAVING_PLACE_NOT_SUPPORTED[] = QT_TRANSLATE_NOOP("QtLocationQML", "Saving places is not supported."); +const char REMOVING_PLACE_NOT_SUPPORTED[] = QT_TRANSLATE_NOOP("QtLocationQML", "Removing places is not supported."); +const char SAVING_CATEGORY_NOT_SUPPORTED[] = QT_TRANSLATE_NOOP("QtLocationQML", "Saving categories is not supported."); +const char REMOVING_CATEGORY_NOT_SUPPORTED[] = QT_TRANSLATE_NOOP("QtLocationQML", "Removing categories is not supported."); +const char PARSE_ERROR[] = QT_TRANSLATE_NOOP("QtLocationQML", "Error parsing response."); +const char NETWORK_ERROR[] = QT_TRANSLATE_NOOP("QtLocationQML", "Network error."); +const char CANCEL_ERROR[] = QT_TRANSLATE_NOOP("QtLocationQML", "Request was canceled."); +const char RESPONSE_NOT_RECOGNIZABLE[] = QT_TRANSLATE_NOOP("QtLocationQML", "The response from the service was not in a recognizable format."); + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeoerror_messages.h b/src/plugins/geoservices/nokia/qgeoerror_messages.h new file mode 100644 index 0000000..8bae1f2 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoerror_messages.h @@ -0,0 +1,58 @@ + +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOERROR_MESSAGES_H +#define QGEOERROR_MESSAGES_H + +#include + +QT_BEGIN_NAMESPACE + +extern const char NOKIA_PLUGIN_CONTEXT_NAME[]; +extern const char MISSED_CREDENTIALS[]; +extern const char SAVING_PLACE_NOT_SUPPORTED[]; +extern const char REMOVING_PLACE_NOT_SUPPORTED[]; +extern const char SAVING_CATEGORY_NOT_SUPPORTED[]; +extern const char REMOVING_CATEGORY_NOT_SUPPORTED[]; +extern const char PARSE_ERROR[]; +extern const char NETWORK_ERROR[]; +extern const char CANCEL_ERROR[]; +extern const char RESPONSE_NOT_RECOGNIZABLE[]; + +QT_END_NAMESPACE + +#endif // QGEOERROR_MESSAGES_H diff --git a/src/plugins/geoservices/nokia/qgeointrinsicnetworkaccessmanager.cpp b/src/plugins/geoservices/nokia/qgeointrinsicnetworkaccessmanager.cpp new file mode 100644 index 0000000..267c4ca --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeointrinsicnetworkaccessmanager.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeointrinsicnetworkaccessmanager.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoIntrinsicNetworkAccessManager::QGeoIntrinsicNetworkAccessManager(QObject *parent) +: QGeoNetworkAccessManager(parent) +, m_networkManager(new QNetworkAccessManager(this)) +{ +} + +QGeoIntrinsicNetworkAccessManager::QGeoIntrinsicNetworkAccessManager(const QVariantMap ¶meters, const QString &token, QObject *parent) +: QGeoNetworkAccessManager(parent) +, m_customProxyToken(token) +, m_networkManager(new QNetworkAccessManager(this)) +{ + configure(parameters); +} + +void QGeoIntrinsicNetworkAccessManager::configure(const QVariantMap ¶meters) +{ + QString proxy = parameters.value(QStringLiteral("here.proxy")).toString(); + if (proxy.isEmpty() && !m_customProxyToken.isEmpty()) + proxy = parameters.value(m_customProxyToken).toString(); + + if (!proxy.isEmpty()) { +#ifndef QT_NO_NETWORKPROXY + if (proxy.toLower() != QStringLiteral("system")) { + QUrl proxyUrl(proxy); + if (proxyUrl.isValid()) { + qDebug() << "Setting proxy to " << proxyUrl.toString(); + m_networkManager->setProxy( + QNetworkProxy(QNetworkProxy::HttpProxy, + proxyUrl.host(), + proxyUrl.port(8080), + proxyUrl.userName(), + proxyUrl.password())); + } + } else if (QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy) { + QNetworkProxyFactory::setUseSystemConfiguration(true); + qDebug() << "Setting system proxy."; + } +#else + qDebug() << "No proxy support"; +#endif + } else { + qDebug() << "No proxy parameter specified."; + } +} + +QNetworkReply *QGeoIntrinsicNetworkAccessManager::get(const QNetworkRequest &request) +{ + return m_networkManager->get(request); +} + +QNetworkReply *QGeoIntrinsicNetworkAccessManager::post(const QNetworkRequest &request, const QByteArray &data) +{ + return m_networkManager->post(request, data); +} +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeointrinsicnetworkaccessmanager.h b/src/plugins/geoservices/nokia/qgeointrinsicnetworkaccessmanager.h new file mode 100644 index 0000000..fb5ab48 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeointrinsicnetworkaccessmanager.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOINTRINSICNETWORKACCESSMANAGER_H +#define QGEOINTRINSICNETWORKACCESSMANAGER_H + +#include "qgeonetworkaccessmanager.h" + +#include + +QT_BEGIN_NAMESPACE + +class QNetworkAccessManager; + +class QGeoIntrinsicNetworkAccessManager : public QGeoNetworkAccessManager +{ +public: + explicit QGeoIntrinsicNetworkAccessManager(QObject *parent = 0); + QGeoIntrinsicNetworkAccessManager(const QVariantMap ¶meters, const QString &token = QString(), QObject *parent = 0); + + virtual QNetworkReply *get(const QNetworkRequest &request); + virtual QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data); + +private: + void configure(const QVariantMap ¶meters); + + const QString m_customProxyToken; + QNetworkAccessManager *m_networkManager; +}; + +QT_END_NAMESPACE + +#endif // QGEOINTRINSICNETWORKACCESSMANAGER_H diff --git a/src/plugins/geoservices/nokia/qgeomapreply_nokia.cpp b/src/plugins/geoservices/nokia/qgeomapreply_nokia.cpp new file mode 100644 index 0000000..c95f8de --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeomapreply_nokia.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomapreply_nokia.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoMapReplyNokia::QGeoMapReplyNokia(QNetworkReply *reply, const QGeoTileSpec &spec, QObject *parent) + : QGeoTiledMapReply(spec, parent), + m_reply(reply) +{ + connect(m_reply, + SIGNAL(finished()), + this, + SLOT(networkFinished())); + + connect(m_reply, + SIGNAL(error(QNetworkReply::NetworkError)), + this, + SLOT(networkError(QNetworkReply::NetworkError))); +} + +QGeoMapReplyNokia::~QGeoMapReplyNokia() +{ +} + +QNetworkReply *QGeoMapReplyNokia::networkReply() const +{ + return m_reply; +} + +void QGeoMapReplyNokia::abort() +{ + if (!m_reply) + return; + + m_reply->abort(); +} + +void QGeoMapReplyNokia::networkFinished() +{ + if (!m_reply) + return; + + if (m_reply->error() != QNetworkReply::NoError) + return; + + setMapImageData(m_reply->readAll()); + setMapImageFormat("png"); + setFinished(true); + + m_reply->deleteLater(); + m_reply = 0; +} + +void QGeoMapReplyNokia::networkError(QNetworkReply::NetworkError error) +{ + if (!m_reply) + return; + + if (error != QNetworkReply::OperationCanceledError) + setError(QGeoTiledMapReply::CommunicationError, m_reply->errorString()); + setFinished(true); + m_reply->deleteLater(); + m_reply = 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeomapreply_nokia.h b/src/plugins/geoservices/nokia/qgeomapreply_nokia.h new file mode 100644 index 0000000..5575991 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeomapreply_nokia.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPREPLY_NOKIA_H +#define QGEOMAPREPLY_NOKIA_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoMapReplyNokia : public QGeoTiledMapReply +{ + Q_OBJECT + +public: + QGeoMapReplyNokia(QNetworkReply *reply, const QGeoTileSpec &spec, QObject *parent = 0); + ~QGeoMapReplyNokia(); + + void abort(); + + QNetworkReply *networkReply() const; + +private Q_SLOTS: + void networkFinished(); + void networkError(QNetworkReply::NetworkError error); + +private: + QPointer m_reply; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/qgeomapversion.cpp b/src/plugins/geoservices/nokia/qgeomapversion.cpp new file mode 100644 index 0000000..4432762 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeomapversion.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Appello Systems AB. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomapversion.h" + +#include + +QT_BEGIN_NAMESPACE + +QGeoMapVersion::QGeoMapVersion() + : m_version(-1) {} + +bool QGeoMapVersion::isNewVersion(const QJsonObject &newVersionData) +{ + return m_versionData != newVersionData; +} + +int QGeoMapVersion::version() const +{ + return m_version; +} + +void QGeoMapVersion::setVersion(int version) +{ + m_version = version; +} + +void QGeoMapVersion::setVersionData(const QJsonObject &versionData) +{ + m_versionData = versionData; +} + + +QByteArray QGeoMapVersion::toJson() const +{ + + QJsonObject object; + object[QLatin1String("version")] = m_version; + object[QLatin1String("data")] = m_versionData; + + QJsonDocument document(object); + + return document.toJson(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeomapversion.h b/src/plugins/geoservices/nokia/qgeomapversion.h new file mode 100644 index 0000000..0e602e6 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeomapversion.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Appello Systems AB. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPVERSION_H +#define QGEOMAPVERSION_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoMapVersion +{ + +public: + QGeoMapVersion(); + bool isNewVersion(const QJsonObject &newVersionData); + int version() const; + void setVersion(const int); + void setVersionData(const QJsonObject &versionData); + QByteArray toJson() const; + +private: + int m_version; + QJsonObject m_versionData; +}; + +QT_END_NAMESPACE + +#endif // QGEOMAPVERSION_H diff --git a/src/plugins/geoservices/nokia/qgeonetworkaccessmanager.h b/src/plugins/geoservices/nokia/qgeonetworkaccessmanager.h new file mode 100644 index 0000000..541f00c --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeonetworkaccessmanager.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEONETWORKACCESSMANAGER_H +#define QGEONETWORKACCESSMANAGER_H + +#include + +QT_BEGIN_NAMESPACE + +class QNetworkReply; +class QNetworkRequest; +class QByteArray; + +class QGeoNetworkAccessManager : public QObject +{ + Q_OBJECT +public: + virtual ~QGeoNetworkAccessManager() {} + virtual QNetworkReply *get(const QNetworkRequest &request) = 0; + virtual QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data) = 0; + +protected: + QGeoNetworkAccessManager(QObject *parent) : QObject(parent) {} +}; + +QT_END_NAMESPACE + +#endif // QGEONETWORKACCESSMANAGER_H diff --git a/src/plugins/geoservices/nokia/qgeoroutereply_nokia.cpp b/src/plugins/geoservices/nokia/qgeoroutereply_nokia.cpp new file mode 100644 index 0000000..45ea071 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoroutereply_nokia.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroutereply_nokia.h" +#include "qgeoroutexmlparser.h" +#include "qgeoerror_messages.h" + +#include + +#include + +Q_DECLARE_METATYPE(QList) + +QT_BEGIN_NAMESPACE + +QGeoRouteReplyNokia::QGeoRouteReplyNokia(const QGeoRouteRequest &request, + const QList &replies, + QObject *parent) +: QGeoRouteReply(request, parent), m_replies(replies), m_parsers(0) +{ + qRegisterMetaType >(); + + foreach (QNetworkReply *reply, m_replies) { + connect(reply, SIGNAL(finished()), this, SLOT(networkFinished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(networkError(QNetworkReply::NetworkError))); + } +} + +QGeoRouteReplyNokia::~QGeoRouteReplyNokia() +{ + abort(); +} + +void QGeoRouteReplyNokia::abort() +{ + if (m_replies.isEmpty() && !m_parsers) + return; + + foreach (QNetworkReply *reply, m_replies) { + reply->abort(); + reply->deleteLater(); + } + m_replies.clear(); + m_parsers = 0; +} + +void QGeoRouteReplyNokia::networkFinished() +{ + QNetworkReply *reply = qobject_cast(sender()); + if (!reply) + return; + + if (reply->error() != QNetworkReply::NoError) + return; + + QGeoRouteXmlParser *parser = new QGeoRouteXmlParser(request()); + connect(parser, SIGNAL(results(QList)), + this, SLOT(appendResults(QList))); + connect(parser, SIGNAL(error(QString)), this, SLOT(parserError(QString))); + + ++m_parsers; + parser->parse(reply->readAll()); + + m_replies.removeOne(reply); + reply->deleteLater(); +} + +void QGeoRouteReplyNokia::networkError(QNetworkReply::NetworkError error) +{ + QNetworkReply *reply = qobject_cast(sender()); + if (!reply) + return; + + if (error == QNetworkReply::UnknownContentError) { + QGeoRouteXmlParser *parser = new QGeoRouteXmlParser(request()); + connect(parser, SIGNAL(results(QList)), + this, SLOT(appendResults(QList))); + connect(parser, SIGNAL(error(QString)), this, SLOT(parserError(QString))); + + ++m_parsers; + parser->parse(reply->readAll()); + + m_replies.removeOne(reply); + reply->deleteLater(); + } else { + setError(QGeoRouteReply::CommunicationError, reply->errorString()); + abort(); + } +} + +void QGeoRouteReplyNokia::appendResults(const QList &routes) +{ + if (!m_parsers) + return; + + --m_parsers; + addRoutes(routes); + + if (!m_parsers && m_replies.isEmpty()) + setFinished(true); +} + +void QGeoRouteReplyNokia::parserError(const QString &errorString) +{ + Q_UNUSED(errorString) + + --m_parsers; + + setError(QGeoRouteReply::ParseError, + QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, RESPONSE_NOT_RECOGNIZABLE)); + abort(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeoroutereply_nokia.h b/src/plugins/geoservices/nokia/qgeoroutereply_nokia.h new file mode 100644 index 0000000..9d32bdb --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoroutereply_nokia.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEREPLY_NOKIA_H +#define QGEOROUTEREPLY_NOKIA_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRouteXmlParser; + +class QGeoRouteReplyNokia : public QGeoRouteReply +{ + Q_OBJECT +public: + QGeoRouteReplyNokia(const QGeoRouteRequest &request, const QList &replies, QObject *parent = 0); + ~QGeoRouteReplyNokia(); + + void abort(); + +private Q_SLOTS: + void networkFinished(); + void networkError(QNetworkReply::NetworkError error); + void appendResults(const QList &routes); + void parserError(const QString &errorString); + +private: + QList m_replies; + int m_parsers; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/qgeoroutexmlparser.cpp b/src/plugins/geoservices/nokia/qgeoroutexmlparser.cpp new file mode 100644 index 0000000..8e436a9 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoroutexmlparser.cpp @@ -0,0 +1,605 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroutexmlparser.h" + +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoDynamicSpeedInfoContainer::QGeoDynamicSpeedInfoContainer() +: trafficSpeed(0) +, baseSpeed(0) +, trafficTime(0) +, baseTime(0) +{} + +QGeoRouteXmlParser::QGeoRouteXmlParser(const QGeoRouteRequest &request) + : m_request(request) +{ +} + +QGeoRouteXmlParser::~QGeoRouteXmlParser() +{ +} + +void QGeoRouteXmlParser::parse(const QByteArray &data) +{ + m_data = data; + QThreadPool::globalInstance()->start(this); +} + +void QGeoRouteXmlParser::run() +{ + m_reader = new QXmlStreamReader(m_data); + + if (!parseRootElement()) + emit error(m_reader->errorString()); + else + emit results(m_results); + + delete m_reader; + m_reader = 0; +} + +bool QGeoRouteXmlParser::parseRootElement() +{ + if (!m_reader->readNextStartElement()) { + m_reader->raiseError("Expected a root element named \"CalculateRoute\" (no root element found)."); + return false; + } + + if (m_reader->name() == QLatin1String("Error")) { + QXmlStreamAttributes attributes = m_reader->attributes(); + if (attributes.value(QStringLiteral("type")) == QLatin1String("ApplicationError") + && attributes.value("subtype") == QLatin1String("NoRouteFound")) + return true; + } + + bool updateroute = false; + if (m_reader->name() != "CalculateRoute" && m_reader->name() != "GetRoute") { + m_reader->raiseError(QString("The root element is expected to have the name \"CalculateRoute\" or \"GetRoute\" (root element was named \"%1\").").arg(m_reader->name().toString())); + return false; + } else if (m_reader->name() == "GetRoute") { + updateroute = true; + } + + if (m_reader->readNextStartElement()) { + if (m_reader->name() != "Response") { + m_reader->raiseError(QString("Expected a element named \"Response\" (element was named \"%1\").").arg(m_reader->name().toString())); + return false; + } + } + + while (m_reader->readNextStartElement() && !m_reader->hasError()) { + if (m_reader->name() == "Route") { + QGeoRoute route; + route.setRequest(m_request); + if (updateroute) + route.setTravelMode(QGeoRouteRequest::TravelMode(int(m_request.travelModes()))); + if (!parseRoute(&route)) + continue; //route parsing failed move on to the next + m_results.append(route); + } else if (m_reader->name() == "Progress") { + //TODO: updated route progress + m_reader->skipCurrentElement(); + } else { + m_reader->skipCurrentElement(); + } + } + + return !m_reader->hasError(); +} + +bool QGeoRouteXmlParser::parseRoute(QGeoRoute *route) +{ + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "Route"); + m_maneuvers.clear(); + m_segments.clear(); + + m_reader->readNext(); + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == "Route") && + !m_reader->hasError()) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + if (m_reader->name() == "RouteId") { + route->setRouteId(m_reader->readElementText()); + } + //else if (m_reader->name() == "Waypoint") { + // succeeded = parseWaypoint(route); + //} + else if (m_reader->name() == "Mode") { + if (!parseMode(route)) + return false; + } else if (m_reader->name() == "Shape") { + QString elementName = m_reader->name().toString(); + QList path; + if (!parseGeoPoints(m_reader->readElementText(), &path, elementName)) + return false; + route->setPath(path); + } else if (m_reader->name() == "BoundingBox") { + QGeoRectangle bounds; + if (!parseBoundingBox(bounds)) + return false; + route->setBounds(bounds); + } else if (m_reader->name() == "Leg") { + if (!parseLeg()) + return false; + } else if (m_reader->name() == "Summary") { + if (!parseSummary(route)) + return false; + } else { + m_reader->skipCurrentElement(); + } + } + m_reader->readNext(); + } + + if (m_reader->hasError()) + return false; + + return postProcessRoute(route); +} + +bool QGeoRouteXmlParser::parseLeg() +{ + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "Leg"); + + m_reader->readNext(); + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == "Leg") && + !m_reader->hasError()) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + if (m_reader->name() == "Maneuver") { + if (!parseManeuver()) + return false; + } else if (m_reader->name() == "Link") { + if (!parseLink()) + return false; + } else { + m_reader->skipCurrentElement(); + } + } + m_reader->readNext(); + } + + return !m_reader->hasError(); +} + +bool QGeoRouteXmlParser::postProcessRoute(QGeoRoute *route) +{ + QList routeSegments; + + int maneuverIndex = 0; + for (int i = 0; i < m_segments.count(); ++i) { + // In case there is a maneuver in the middle of the list with no + // link ID attached, attach it to the next available segment + while ((maneuverIndex < m_maneuvers.size() - 1) && m_maneuvers.at(maneuverIndex).toId.isEmpty()) { + QGeoRouteSegment segment; + segment.setManeuver(m_maneuvers.at(maneuverIndex).maneuver); + QList path; // use instruction position as one point segment path + path.append(m_maneuvers.at(maneuverIndex).maneuver.position()); + segment.setPath(path); + routeSegments.append(segment); + ++maneuverIndex; + } + + QGeoRouteSegment segment = m_segments.at(i).segment; + if ((maneuverIndex < m_maneuvers.size()) && m_segments.at(i).id == m_maneuvers.at(maneuverIndex).toId) { + segment.setManeuver(m_maneuvers.at(maneuverIndex).maneuver); + ++maneuverIndex; + } + routeSegments.append(segment); + } + + // For the final maneuver in the list, make sure to attach it to the very + // last segment on the path, this is why we don't process the last + // maneuver in the loop above + while (maneuverIndex < m_maneuvers.size()) { + QGeoRouteSegment segment; + segment.setManeuver(m_maneuvers.at(maneuverIndex).maneuver); + QList path; // use instruction position as one point segment path + path.append(m_maneuvers.at(maneuverIndex).maneuver.position()); + segment.setPath(path); + + routeSegments.append(segment); + ++maneuverIndex; + } + + QList compactedRouteSegments; + compactedRouteSegments.append(routeSegments.first()); + routeSegments.removeFirst(); + + while (routeSegments.size() > 0) { + QGeoRouteSegment segment = routeSegments.first(); + routeSegments.removeFirst(); + + QGeoRouteSegment lastSegment = compactedRouteSegments.last(); + + if (lastSegment.maneuver().isValid()) { + compactedRouteSegments.append(segment); + } else { + compactedRouteSegments.removeLast(); + lastSegment.setDistance(lastSegment.distance() + segment.distance()); + lastSegment.setTravelTime(lastSegment.travelTime() + segment.travelTime()); + QList path = lastSegment.path(); + path.append(segment.path()); + lastSegment.setPath(path); + lastSegment.setManeuver(segment.maneuver()); + compactedRouteSegments.append(lastSegment); + } + } + + if (compactedRouteSegments.size() > 0) { + route->setFirstRouteSegment(compactedRouteSegments.at(0)); + for (int i = 0; i < compactedRouteSegments.size() - 1; ++i) + compactedRouteSegments[i].setNextRouteSegment(compactedRouteSegments.at(i + 1)); + } + + m_maneuvers.clear(); + m_segments.clear(); + return true; +} + +/* +bool QGeoRouteXmlParser::parseWaypoint(QGeoRoute *route) +{ + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "Waypoint"); + m_reader->readNext(); + QList path(route->pathSummary()); + + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == "Waypoint")) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + if (m_reader->name() == "MappedPosition") { + QGeoCoordinate coordinates; + if(!parseCoordinates(coordinates)) + return false; + path.append(coordinates); + } + else { + m_reader->skipCurrentElement(); + } + } + m_reader->readNext(); + } + route->setPathSummary(path); + return true; +} +*/ + +bool QGeoRouteXmlParser::parseMode(QGeoRoute *route) +{ + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "Mode"); + m_reader->readNext(); + + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == "Mode") && + !m_reader->hasError()) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + if (m_reader->name() == "TransportModes") { + QString value = m_reader->readElementText(); + if (value == "car") + route->setTravelMode(QGeoRouteRequest::CarTravel); + else if (value == "pedestrian") + route->setTravelMode(QGeoRouteRequest::PedestrianTravel); + else if (value == "publicTransport") + route->setTravelMode(QGeoRouteRequest::PublicTransitTravel); + else if (value == "bicycle") + route->setTravelMode(QGeoRouteRequest::BicycleTravel); + else if (value == "truck") + route->setTravelMode(QGeoRouteRequest::TruckTravel); + else { + // unsupported mode + m_reader->raiseError(QString("Unsupported travel mode '\"%1\"'").arg(value)); + return false; + } + } else { + m_reader->skipCurrentElement(); + } + } + m_reader->readNext(); + } + return !m_reader->hasError(); +} + +bool QGeoRouteXmlParser::parseSummary(QGeoRoute *route) +{ + Q_ASSERT(route); + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "Summary"); + m_reader->readNext(); + + double baseTime = -1, trafficTime = -1; + + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == "Summary") && + !m_reader->hasError()) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + if (m_reader->name() == "Distance") { + route->setDistance(m_reader->readElementText().toDouble()); + } else if (m_reader->name() == "TrafficTime") { + trafficTime = m_reader->readElementText().toDouble(); + } else if (m_reader->name() == "BaseTime") { + baseTime = m_reader->readElementText().toDouble(); + } else { + m_reader->skipCurrentElement(); + } + } + m_reader->readNext(); + } + + if (m_reader->hasError()) + return false; + + if (trafficTime >= 0) + route->setTravelTime(trafficTime); + else if (baseTime >= 0) + route->setTravelTime(baseTime); + + return true; +} + +bool QGeoRouteXmlParser::parseCoordinates(QGeoCoordinate &coord) +{ + QString currentElement = m_reader->name().toString(); + m_reader->readNext(); + + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == currentElement) && + !m_reader->hasError()) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + QString name = m_reader->name().toString(); + QString value = m_reader->readElementText(); + if (name == "Latitude") + coord.setLatitude(value.toDouble()); + else if (name == "Longitude") + coord.setLongitude(value.toDouble()); + } + m_reader->readNext(); + } + + return !m_reader->hasError(); +} + +bool QGeoRouteXmlParser::parseManeuver() +{ + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "Maneuver"); + + if (!m_reader->attributes().hasAttribute("id")) { + m_reader->raiseError("The element \"Maneuver\" did not have the required attribute \"id\"."); + return false; + } + QGeoManeuverContainer maneuverContainter; + maneuverContainter.id = m_reader->attributes().value("id").toString(); + + m_reader->readNext(); + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == "Maneuver") && + !m_reader->hasError()) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + if (m_reader->name() == "Position") { + QGeoCoordinate coordinates; + if (parseCoordinates(coordinates)) + maneuverContainter.maneuver.setPosition(coordinates); + } else if (m_reader->name() == "Instruction") { + maneuverContainter.maneuver.setInstructionText(m_reader->readElementText()); + } else if (m_reader->name() == "ToLink") { + maneuverContainter.toId = m_reader->readElementText(); + } else if (m_reader->name() == "TravelTime") { + maneuverContainter.maneuver.setTimeToNextInstruction(qRound(m_reader->readElementText().toDouble())); + } else if (m_reader->name() == "Length") { + maneuverContainter.maneuver.setDistanceToNextInstruction(m_reader->readElementText().toDouble()); + } else if (m_reader->name() == "Direction") { + QString value = m_reader->readElementText(); + if (value == "forward") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionForward); + else if (value == "bearRight") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionBearRight); + else if (value == "lightRight") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionLightRight); + else if (value == "right") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionRight); + else if (value == "hardRight") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionHardRight); + else if (value == "uTurnRight") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionUTurnRight); + else if (value == "uTurnLeft") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionUTurnLeft); + else if (value == "hardLeft") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionHardLeft); + else if (value == "left") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionLeft); + else if (value == "lightLeft") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionLightLeft); + else if (value == "bearLeft") + maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionBearLeft); + else + maneuverContainter.maneuver.setDirection(QGeoManeuver::NoDirection); + } else { + m_reader->skipCurrentElement(); + } + } + m_reader->readNext(); + } + + if (m_reader->hasError()) + return false; + + m_maneuvers.append(maneuverContainter); + return true; +} + +bool QGeoRouteXmlParser::parseLink() +{ + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QStringLiteral("Link")); + m_reader->readNext(); + + QGeoRouteSegmentContainer segmentContainer; + + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == QStringLiteral("Link")) && + !m_reader->hasError()) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + if (m_reader->name() == QStringLiteral("LinkId")) { + segmentContainer.id = m_reader->readElementText(); + } else if (m_reader->name() == QStringLiteral("Shape")) { + QString elementName = m_reader->name().toString(); + QList path; + parseGeoPoints(m_reader->readElementText(), &path, elementName); + segmentContainer.segment.setPath(path); + } else if (m_reader->name() == QStringLiteral("Length")) { + segmentContainer.segment.setDistance(m_reader->readElementText().toDouble()); + } else if (m_reader->name() == QStringLiteral("Maneuver")) { + segmentContainer.maneuverId = m_reader->readElementText(); + } else if (m_reader->name() == QStringLiteral("DynamicSpeedInfo")) { + QGeoDynamicSpeedInfoContainer speedInfo; + if (!parseDynamicSpeedInfo(speedInfo)) + return false; + const double time = speedInfo.trafficTime >= 0 ? speedInfo.trafficTime : speedInfo.baseTime; + if (time >= 0) + segmentContainer.segment.setTravelTime(time); + } else { + m_reader->skipCurrentElement(); + } + } + m_reader->readNext(); + } + + if (m_reader->hasError()) + return false; + + m_segments.append(segmentContainer); + return true; +} + +bool QGeoRouteXmlParser::parseGeoPoints(const QString &strPoints, QList *geoPoints, const QString &elementName) +{ + QStringList rawPoints = strPoints.split(' '); + + for (int i = 0; i < rawPoints.length(); ++i) { + QStringList coords = rawPoints[i].split(','); + + if (coords.length() != 2) { + m_reader->raiseError(QString("Each of the space separated values of \"%1\" is expected to be a comma separated pair of coordinates (value was \"%2\")").arg(elementName).arg(rawPoints[i])); + return false; + } + + bool ok = false; + QString latString = coords[0]; + double lat = latString.toDouble(&ok); + + if (!ok) { + m_reader->raiseError(QString("The latitude portions of \"%1\" are expected to have a value convertable to a double (value was \"%2\")").arg(elementName).arg(latString)); + return false; + } + + QString lngString = coords[1]; + double lng = lngString.toDouble(&ok); + + if (!ok) { + m_reader->raiseError(QString("The longitude portions of \"%1\" are expected to have a value convertable to a double (value was \"%2\")").arg(elementName).arg(lngString)); + return false; + } + + QGeoCoordinate geoPoint(lat, lng); + geoPoints->append(geoPoint); + } + + return true; +} + +bool QGeoRouteXmlParser::parseBoundingBox(QGeoRectangle &bounds) +{ + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "BoundingBox"); + + QGeoCoordinate tl; + QGeoCoordinate br; + + m_reader->readNext(); + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == "BoundingBox") && + !m_reader->hasError()) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + if (m_reader->name() == "TopLeft") { + QGeoCoordinate coordinates; + if (parseCoordinates(coordinates)) + tl = coordinates; + } else if (m_reader->name() == "BottomRight") { + QGeoCoordinate coordinates; + if (parseCoordinates(coordinates)) + br = coordinates; + } else { + m_reader->skipCurrentElement(); + } + } + m_reader->readNext(); + } + + if (m_reader->hasError()) + return false; + + if (tl.isValid() && br.isValid()) { + bounds = QGeoRectangle(tl, br); + return true; + } + + return false; +} + +bool QGeoRouteXmlParser::parseDynamicSpeedInfo(QGeoDynamicSpeedInfoContainer &speedInfo) +{ + Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QStringLiteral("DynamicSpeedInfo")); + + m_reader->readNext(); + while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == QStringLiteral("DynamicSpeedInfo")) && + !m_reader->hasError()) { + if (m_reader->tokenType() == QXmlStreamReader::StartElement) { + if (m_reader->name() == QStringLiteral("TrafficSpeed")) { + speedInfo.trafficSpeed = m_reader->readElementText().toDouble(); + } else if (m_reader->name() == QStringLiteral("TrafficTime")) { + speedInfo.trafficTime = qRound(m_reader->readElementText().toDouble()); + } else if (m_reader->name() == QStringLiteral("BaseSpeed")) { + speedInfo.baseSpeed = m_reader->readElementText().toDouble(); + } else if (m_reader->name() == QStringLiteral("BaseTime")) { + speedInfo.baseTime = qRound(m_reader->readElementText().toDouble()); + } else { + m_reader->skipCurrentElement(); + } + } + m_reader->readNext(); + } + + return !m_reader->hasError(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeoroutexmlparser.h b/src/plugins/geoservices/nokia/qgeoroutexmlparser.h new file mode 100644 index 0000000..e2feb72 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoroutexmlparser.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QROUTEXMLPARSER_H +#define QROUTEXMLPARSER_H + +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QXmlStreamReader; +class QGeoRoute; +class QGeoCoordinate; +class QGeoRectangle; + +class QGeoManeuverContainer +{ +public: + QGeoManeuver maneuver; + QString id; + QString toId; +}; + +class QGeoRouteSegmentContainer +{ +public: + QGeoRouteSegment segment; + QString id; + QString maneuverId; +}; + +class QGeoDynamicSpeedInfoContainer +{ +public: + QGeoDynamicSpeedInfoContainer(); + +public: + double trafficSpeed; + double baseSpeed; + int trafficTime; + int baseTime; +}; + +class QGeoRouteXmlParser : public QObject, public QRunnable +{ + Q_OBJECT + +public: + QGeoRouteXmlParser(const QGeoRouteRequest &request); + ~QGeoRouteXmlParser(); + + void parse(const QByteArray &data); + void run(); + +signals: + void results(const QList &routes); + void error(const QString &errorString); + +private: + bool parseRootElement(); + bool parseRoute(QGeoRoute *route); + //bool parseWaypoint(QGeoRoute *route); + bool parseCoordinates(QGeoCoordinate &coord); + bool parseMode(QGeoRoute *route); + bool parseSummary(QGeoRoute *route); + bool parseGeoPoints(const QString &strPoints, QList *geoPoints, const QString &elementName); + bool parseLeg(); + bool parseManeuver(); + bool parseLink(); + bool postProcessRoute(QGeoRoute *route); + + bool parseBoundingBox(QGeoRectangle &bounds); + bool parseDynamicSpeedInfo(QGeoDynamicSpeedInfoContainer &speedInfo); + + QGeoRouteRequest m_request; + QByteArray m_data; + QXmlStreamReader *m_reader; + + QList m_results; + QList m_maneuvers; + QList m_segments; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/qgeoroutingmanagerengine_nokia.cpp b/src/plugins/geoservices/nokia/qgeoroutingmanagerengine_nokia.cpp new file mode 100644 index 0000000..a33d1ba --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoroutingmanagerengine_nokia.cpp @@ -0,0 +1,491 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroutingmanagerengine_nokia.h" +#include "qgeoroutereply_nokia.h" +#include "qgeonetworkaccessmanager.h" +#include "qgeouriprovider.h" +#include "uri_constants.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoRoutingManagerEngineNokia::QGeoRoutingManagerEngineNokia( + QGeoNetworkAccessManager *networkManager, + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) + : QGeoRoutingManagerEngine(parameters) + , m_networkManager(networkManager) + , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.routing.host"), ROUTING_HOST)) + +{ + Q_ASSERT(networkManager); + m_networkManager->setParent(this); + + m_appId = parameters.value(QStringLiteral("here.app_id")).toString(); + m_token = parameters.value(QStringLiteral("here.token")).toString(); + + QGeoRouteRequest::FeatureTypes featureTypes; + featureTypes |= QGeoRouteRequest::TollFeature; + featureTypes |= QGeoRouteRequest::HighwayFeature; + featureTypes |= QGeoRouteRequest::FerryFeature; + featureTypes |= QGeoRouteRequest::TunnelFeature; + featureTypes |= QGeoRouteRequest::DirtRoadFeature; + featureTypes |= QGeoRouteRequest::ParksFeature; + setSupportedFeatureTypes(featureTypes); + + QGeoRouteRequest::FeatureWeights featureWeights; + featureWeights |= QGeoRouteRequest::DisallowFeatureWeight; + featureWeights |= QGeoRouteRequest::AvoidFeatureWeight; + featureWeights |= QGeoRouteRequest::PreferFeatureWeight; + setSupportedFeatureWeights(featureWeights); + + QGeoRouteRequest::ManeuverDetails maneuverDetails; + maneuverDetails |= QGeoRouteRequest::BasicManeuvers; + setSupportedManeuverDetails(maneuverDetails); + + QGeoRouteRequest::RouteOptimizations optimizations; + optimizations |= QGeoRouteRequest::ShortestRoute; + optimizations |= QGeoRouteRequest::FastestRoute; + setSupportedRouteOptimizations(optimizations); + + QGeoRouteRequest::TravelModes travelModes; + travelModes |= QGeoRouteRequest::CarTravel; + travelModes |= QGeoRouteRequest::PedestrianTravel; + travelModes |= QGeoRouteRequest::PublicTransitTravel; + travelModes |= QGeoRouteRequest::BicycleTravel; + setSupportedTravelModes(travelModes); + + QGeoRouteRequest::SegmentDetails segmentDetails; + segmentDetails |= QGeoRouteRequest::BasicSegmentData; + setSupportedSegmentDetails(segmentDetails); + + if (error) + *error = QGeoServiceProvider::NoError; + + if (errorString) + *errorString = QString(); +} + +QGeoRoutingManagerEngineNokia::~QGeoRoutingManagerEngineNokia() {} + +QGeoRouteReply *QGeoRoutingManagerEngineNokia::calculateRoute(const QGeoRouteRequest &request) +{ + const QStringList reqStrings = calculateRouteRequestString(request); + + if (reqStrings.isEmpty()) { + QGeoRouteReply *reply = new QGeoRouteReply(QGeoRouteReply::UnsupportedOptionError, "The given route request options are not supported by this service provider.", this); + emit error(reply, reply->error(), reply->errorString()); + return reply; + } + + QList replies; + foreach (const QString &reqString, reqStrings) + replies.append(m_networkManager->get(QNetworkRequest(QUrl(reqString)))); + + QGeoRouteReplyNokia *reply = new QGeoRouteReplyNokia(request, replies, this); + + connect(reply, + SIGNAL(finished()), + this, + SLOT(routeFinished())); + + connect(reply, + SIGNAL(error(QGeoRouteReply::Error,QString)), + this, + SLOT(routeError(QGeoRouteReply::Error,QString))); + + return reply; +} + +QGeoRouteReply *QGeoRoutingManagerEngineNokia::updateRoute(const QGeoRoute &route, const QGeoCoordinate &position) +{ + const QStringList reqStrings = updateRouteRequestString(route, position); + + if (reqStrings.isEmpty()) { + QGeoRouteReply *reply = new QGeoRouteReply(QGeoRouteReply::UnsupportedOptionError, "The given route request options are not supported by this service provider.", this); + emit error(reply, reply->error(), reply->errorString()); + return reply; + } + + QList replies; + foreach (const QString &reqString, reqStrings) + replies.append(m_networkManager->get(QNetworkRequest(QUrl(reqString)))); + + QGeoRouteRequest updateRequest(route.request()); + updateRequest.setTravelModes(route.travelMode()); + QGeoRouteReplyNokia *reply = new QGeoRouteReplyNokia(updateRequest, replies, this); + + connect(reply, + SIGNAL(finished()), + this, + SLOT(routeFinished())); + + connect(reply, + SIGNAL(error(QGeoRouteReply::Error,QString)), + this, + SLOT(routeError(QGeoRouteReply::Error,QString))); + + return reply; +} + +bool QGeoRoutingManagerEngineNokia::checkEngineSupport(const QGeoRouteRequest &request, + QGeoRouteRequest::TravelModes travelModes) const +{ + QList featureTypeList = request.featureTypes(); + QGeoRouteRequest::FeatureTypes featureTypeFlag = QGeoRouteRequest::NoFeature; + QGeoRouteRequest::FeatureWeights featureWeightFlag = QGeoRouteRequest::NeutralFeatureWeight; + + for (int i = 0; i < featureTypeList.size(); ++i) { + featureTypeFlag |= featureTypeList.at(i); + featureWeightFlag |= request.featureWeight(featureTypeList.at(i)); + } + + if ((featureTypeFlag & supportedFeatureTypes()) != featureTypeFlag) + return false; + + if ((featureWeightFlag & supportedFeatureWeights()) != featureWeightFlag) + return false; + + + if ((request.maneuverDetail() & supportedManeuverDetails()) != request.maneuverDetail()) + return false; + + if ((request.segmentDetail() & supportedSegmentDetails()) != request.segmentDetail()) + return false; + + if ((request.routeOptimization() & supportedRouteOptimizations()) != request.routeOptimization()) + return false; + + if ((travelModes & supportedTravelModes()) != travelModes) + return false; + + // Count the number of set bits (= number of travel modes) (popcount) + int count = 0; + + for (unsigned bits = travelModes; bits; bits >>= 1) + count += (bits & 1); + + // We only allow one travel mode at a time + if (count != 1) + return false; + + return true; +} + +QStringList QGeoRoutingManagerEngineNokia::calculateRouteRequestString(const QGeoRouteRequest &request) +{ + bool supported = checkEngineSupport(request, request.travelModes()); + + if (!supported) + return QStringList(); + QStringList requests; + + QString baseRequest = QStringLiteral("http://"); + baseRequest += m_uriProvider->getCurrentHost(); + baseRequest += QStringLiteral("/routing/7.2/calculateroute.xml"); + + baseRequest += QStringLiteral("?alternatives="); + baseRequest += QString::number(request.numberAlternativeRoutes()); + + if (!m_appId.isEmpty() && !m_token.isEmpty()) { + baseRequest += QStringLiteral("&app_id="); + baseRequest += m_appId; + baseRequest += QStringLiteral("&token="); + baseRequest += m_token; + } + + int numWaypoints = request.waypoints().size(); + if (numWaypoints < 2) + return QStringList(); + + for (int i = 0;i < numWaypoints;++i) { + baseRequest += QStringLiteral("&waypoint"); + baseRequest += QString::number(i); + baseRequest += QStringLiteral("=geo!"); + baseRequest += trimDouble(request.waypoints().at(i).latitude()); + baseRequest += ','; + baseRequest += trimDouble(request.waypoints().at(i).longitude()); + } + + QGeoRouteRequest::RouteOptimizations optimization = request.routeOptimization(); + + QStringList types; + if (optimization.testFlag(QGeoRouteRequest::ShortestRoute)) + types.append("shortest"); + if (optimization.testFlag(QGeoRouteRequest::FastestRoute)) + types.append("fastest"); + + foreach (const QString &optimization, types) { + QString requestString = baseRequest; + requestString += modesRequestString(request, request.travelModes(), optimization); + requestString += routeRequestString(request); + requests << requestString; + } + + return requests; +} + +QStringList QGeoRoutingManagerEngineNokia::updateRouteRequestString(const QGeoRoute &route, const QGeoCoordinate &position) +{ + if (!checkEngineSupport(route.request(), route.travelMode())) + return QStringList(); + QStringList requests; + + QString baseRequest = "http://"; + baseRequest += m_uriProvider->getCurrentHost(); + baseRequest += "/routing/7.2/getroute.xml"; + + baseRequest += "?routeid="; + baseRequest += route.routeId(); + + baseRequest += "&pos="; + baseRequest += QString::number(position.latitude()); + baseRequest += ','; + baseRequest += QString::number(position.longitude()); + + QGeoRouteRequest::RouteOptimizations optimization = route.request().routeOptimization(); + + QStringList types; + if (optimization.testFlag(QGeoRouteRequest::ShortestRoute)) + types.append("shortest"); + if (optimization.testFlag(QGeoRouteRequest::FastestRoute)) + types.append("fastest"); + + foreach (const QString &optimization, types) { + QString requestString = baseRequest; + requestString += modesRequestString(route.request(), route.travelMode(), optimization); + requestString += routeRequestString(route.request()); + requests << requestString; + } + + return requests; +} + +QString QGeoRoutingManagerEngineNokia::modesRequestString(const QGeoRouteRequest &request, + QGeoRouteRequest::TravelModes travelModes, const QString &optimization) const +{ + QString requestString; + + QStringList modes; + if (travelModes.testFlag(QGeoRouteRequest::CarTravel)) + modes.append("car"); + if (travelModes.testFlag(QGeoRouteRequest::PedestrianTravel)) + modes.append("pedestrian"); + if (travelModes.testFlag(QGeoRouteRequest::PublicTransitTravel)) + modes.append("publicTransport"); + + QStringList featureStrings; + QList featureTypes = request.featureTypes(); + for (int i = 0; i < featureTypes.size(); ++i) { + QGeoRouteRequest::FeatureWeight weight = request.featureWeight(featureTypes.at(i)); + + if (weight == QGeoRouteRequest::NeutralFeatureWeight) + continue; + + QString weightString = ""; + switch (weight) { + case QGeoRouteRequest::PreferFeatureWeight: + weightString = '1'; + break; + case QGeoRouteRequest::AvoidFeatureWeight: + weightString = "-1"; + break; + case QGeoRouteRequest::DisallowFeatureWeight: + weightString = "-3"; + break; + case QGeoRouteRequest::NeutralFeatureWeight: + case QGeoRouteRequest::RequireFeatureWeight: + break; + } + + if (weightString.isEmpty()) + continue; + + switch (featureTypes.at(i)) { + case QGeoRouteRequest::TollFeature: + featureStrings.append("tollroad:" + weightString); + break; + case QGeoRouteRequest::HighwayFeature: + featureStrings.append("motorway:" + weightString); + break; + case QGeoRouteRequest::FerryFeature: + featureStrings.append("boatFerry:" + weightString); + featureStrings.append("railFerry:" + weightString); + break; + case QGeoRouteRequest::TunnelFeature: + featureStrings.append("tunnel:" + weightString); + break; + case QGeoRouteRequest::DirtRoadFeature: + featureStrings.append("dirtRoad:" + weightString); + break; + case QGeoRouteRequest::PublicTransitFeature: + case QGeoRouteRequest::ParksFeature: + case QGeoRouteRequest::MotorPoolLaneFeature: + case QGeoRouteRequest::NoFeature: + break; + } + } + + requestString += "&mode="; + requestString += optimization + ';' + modes.join(','); + if (featureStrings.count()) + requestString += ';' + featureStrings.join(','); + return requestString; +} + +QString QGeoRoutingManagerEngineNokia::routeRequestString(const QGeoRouteRequest &request) const +{ + QString requestString; + + foreach (const QGeoRectangle &area, request.excludeAreas()) { + requestString += QLatin1String("&avoidareas="); + requestString += trimDouble(area.topLeft().latitude()); + requestString += QLatin1String(","); + requestString += trimDouble(area.topLeft().longitude()); + requestString += QLatin1String(";"); + requestString += trimDouble(area.bottomRight().latitude()); + requestString += QLatin1String(","); + requestString += trimDouble(area.bottomRight().longitude()); + } + +// TODO: work out what was going on here +// - segment and instruction/maneuever functions are mixed and matched +// - tried to implement sensible equivalents below +// QStringList legAttributes; +// if (request.instructionDetail() & QGeoRouteRequest::BasicSegmentData) { +// requestString += "&linkattributes=sh,le"; //shape,length +// legAttributes.append("links"); +// } +// +// if (request.instructionDetail() & QGeoRouteRequest::BasicInstructions) { +// legAttributes.append("maneuvers"); +// requestString += "&maneuverattributes=po,tt,le,di"; //position,traveltime,length,direction +// if (!(request.instructionDetail() & QGeoRouteRequest::NoSegmentData)) +// requestString += ",li"; //link +// } + + QStringList legAttributes; + if (request.segmentDetail() & QGeoRouteRequest::BasicSegmentData) { + requestString += "&linkattributes=sh,le"; //shape,length + legAttributes.append("links"); + } + + if (request.maneuverDetail() & QGeoRouteRequest::BasicManeuvers) { + legAttributes.append("maneuvers"); + requestString += "&maneuverattributes=po,tt,le,di"; //position,traveltime,length,direction + if (!(request.segmentDetail() & QGeoRouteRequest::NoSegmentData)) + requestString += ",li"; //link + } + + requestString += "&routeattributes=sm,sh,bb,lg"; //summary,shape,boundingBox,legs + if (legAttributes.count() > 0) { + requestString += "&legattributes="; + requestString += legAttributes.join(","); + } + + requestString += "&departure="; + requestString += QDateTime::currentDateTime().toUTC().toString("yyyy-MM-ddThh:mm:ssZ"); + + requestString += "&instructionformat=text"; + + requestString += "&metricSystem="; + if (QLocale::MetricSystem == measurementSystem()) + requestString += "metric"; + else + requestString += "imperial"; + + const QLocale loc(locale()); + + if (QLocale::C != loc.language() && QLocale::AnyLanguage != loc.language()) { + requestString += "&language="; + requestString += loc.name(); + //If the first language isn't supported, english will be selected automatically + if (QLocale::English != loc.language()) + requestString += ",en_US"; + } + + return requestString; +} + +QString QGeoRoutingManagerEngineNokia::trimDouble(double degree, int decimalDigits) +{ + QString sDegree = QString::number(degree, 'g', decimalDigits); + + int index = sDegree.indexOf('.'); + + if (index == -1) + return sDegree; + else + return QString::number(degree, 'g', decimalDigits + index); +} + +void QGeoRoutingManagerEngineNokia::routeFinished() +{ + QGeoRouteReply *reply = qobject_cast(sender()); + + if (!reply) + return; + + if (receivers(SIGNAL(finished(QGeoRouteReply*))) == 0) { + reply->deleteLater(); + return; + } + + emit finished(reply); +} + +void QGeoRoutingManagerEngineNokia::routeError(QGeoRouteReply::Error error, const QString &errorString) +{ + QGeoRouteReply *reply = qobject_cast(sender()); + + if (!reply) + return; + + if (receivers(SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString))) == 0) { + reply->deleteLater(); + return; + } + + emit this->error(reply, error, errorString); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeoroutingmanagerengine_nokia.h b/src/plugins/geoservices/nokia/qgeoroutingmanagerengine_nokia.h new file mode 100644 index 0000000..9335bca --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoroutingmanagerengine_nokia.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTINGMANAGER_NOKIA_H +#define QGEOROUTINGMANAGER_NOKIA_H + +#include "qgeoserviceproviderplugin_nokia.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoNetworkAccessManager; +class QGeoUriProvider; + +class QGeoRoutingManagerEngineNokia : public QGeoRoutingManagerEngine +{ + Q_OBJECT +public: + QGeoRoutingManagerEngineNokia(QGeoNetworkAccessManager *networkInterface, + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString); + ~QGeoRoutingManagerEngineNokia(); + + QGeoRouteReply *calculateRoute(const QGeoRouteRequest &request); + QGeoRouteReply *updateRoute(const QGeoRoute &route, const QGeoCoordinate &position); + +private Q_SLOTS: + void routeFinished(); + void routeError(QGeoRouteReply::Error error, const QString &errorString); + +private: + QStringList calculateRouteRequestString(const QGeoRouteRequest &request); + QStringList updateRouteRequestString(const QGeoRoute &route, const QGeoCoordinate &position); + QString routeRequestString(const QGeoRouteRequest &request) const; + bool checkEngineSupport(const QGeoRouteRequest &request, + QGeoRouteRequest::TravelModes travelModes) const; + QString modesRequestString(const QGeoRouteRequest &request, + QGeoRouteRequest::TravelModes travelModes, + const QString &optimization) const; + static QString trimDouble(double degree, int decimalDigits = 10); + + QGeoNetworkAccessManager *m_networkManager; + QGeoUriProvider *m_uriProvider; + QString m_appId; + QString m_token; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/qgeoserviceproviderplugin_nokia.cpp b/src/plugins/geoservices/nokia/qgeoserviceproviderplugin_nokia.cpp new file mode 100644 index 0000000..f6d05c3 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoserviceproviderplugin_nokia.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoserviceproviderplugin_nokia.h" + +#include "qgeocodingmanagerengine_nokia.h" +#include "qgeoroutingmanagerengine_nokia.h" +#include "qgeotiledmappingmanagerengine_nokia.h" +#include "qplacemanagerengine_nokiav2.h" +#include "qgeointrinsicnetworkaccessmanager.h" +#include "qgeoerror_messages.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace +{ + bool isValidParameter(const QString ¶m) + { + if (param.isEmpty()) + return false; + + if (param.length() > 512) + return false; + + foreach (QChar c, param) { + if (!c.isLetterOrNumber() && c.toLatin1() != '%' && c.toLatin1() != '-' && + c.toLatin1() != '+' && c.toLatin1() != '_') { + return false; + } + } + return true; + } + + QGeoNetworkAccessManager *tryGetNetworkAccessManager(const QVariantMap ¶meters) + { + return static_cast(qvariant_cast(parameters.value(QStringLiteral("nam")))); + } + + void checkUsageTerms(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) + { + QString appId, token; + + appId = parameters.value(QStringLiteral("here.app_id")).toString(); + token = parameters.value(QStringLiteral("here.token")).toString(); + + if (isValidParameter(appId) && isValidParameter(token)) + return; + + if (parameters.contains(QStringLiteral("app_id")) || parameters.contains(QStringLiteral("token"))) + qWarning() << QStringLiteral("Please prefix 'app_id' and 'token' with prefix 'here' (e.g.: 'here.app_id')"); + + *error = QGeoServiceProvider::MissingRequiredParameterError; + *errorString = QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, MISSED_CREDENTIALS); + } + + template + TInstance * CreateInstanceOf(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) + { + checkUsageTerms(parameters, error, errorString); + + if (*error != QGeoServiceProvider::NoError) + return 0; + + QGeoNetworkAccessManager *networkManager = tryGetNetworkAccessManager(parameters); + if (!networkManager) + networkManager = new QGeoIntrinsicNetworkAccessManager(parameters); + + return new TInstance(networkManager, parameters, error, errorString); + } +} + +QGeoCodingManagerEngine *QGeoServiceProviderFactoryNokia::createGeocodingManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const +{ + return CreateInstanceOf(parameters, error, errorString); +} + +QGeoMappingManagerEngine *QGeoServiceProviderFactoryNokia::createMappingManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const +{ + return CreateInstanceOf(parameters, error, errorString); +} + +QGeoRoutingManagerEngine *QGeoServiceProviderFactoryNokia::createRoutingManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const +{ + return CreateInstanceOf(parameters, error, errorString); +} + +QPlaceManagerEngine *QGeoServiceProviderFactoryNokia::createPlaceManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const +{ + return CreateInstanceOf(parameters, error, errorString); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeoserviceproviderplugin_nokia.h b/src/plugins/geoservices/nokia/qgeoserviceproviderplugin_nokia.h new file mode 100644 index 0000000..bce65cb --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeoserviceproviderplugin_nokia.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDER_NOKIA_H +#define QGEOSERVICEPROVIDER_NOKIA_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoNetworkAccessManager; + +class QGeoServiceProviderFactoryNokia : public QObject, public QGeoServiceProviderFactory +{ + Q_OBJECT + Q_INTERFACES(QGeoServiceProviderFactory) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0" + FILE "nokia_plugin.json") + +public: + QGeoCodingManagerEngine *createGeocodingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + QGeoMappingManagerEngine *createMappingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + QGeoRoutingManagerEngine *createRoutingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + QPlaceManagerEngine *createPlaceManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/qgeotiledmap_nokia.cpp b/src/plugins/geoservices/nokia/qgeotiledmap_nokia.cpp new file mode 100644 index 0000000..d83ad0f --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeotiledmap_nokia.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 The Qt Company Ltd. + ** Contact: http://www.qt.io/licensing/ + ** + ** This file is part of the QtLocation module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL3$ + ** Commercial License Usage + ** Licensees holding valid commercial Qt licenses may use this file in + ** accordance with the commercial license agreement provided with the + ** Software or, alternatively, in accordance with the terms contained in + ** a written agreement between you and The Qt Company. For licensing terms + ** and conditions see http://www.qt.io/terms-conditions. For further + ** information use the contact form at http://www.qt.io/contact-us. + ** + ** GNU Lesser General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU Lesser + ** General Public License version 3 as published by the Free Software + ** Foundation and appearing in the file LICENSE.LGPLv3 included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU Lesser General Public License version 3 requirements + ** will be met: https://www.gnu.org/licenses/lgpl.html. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU + ** General Public License version 2.0 or later as published by the Free + ** Software Foundation and appearing in the file LICENSE.GPL included in + ** the packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 2.0 requirements will be + ** met: http://www.gnu.org/licenses/gpl-2.0.html. + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include "qgeotiledmap_nokia.h" +#include "qgeotiledmappingmanagerengine_nokia.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + Constructs a new tiled map data object, which stores the map data required by + \a geoMap and makes use of the functionality provided by \a engine. + */ +QGeoTiledMapNokia::QGeoTiledMapNokia(QGeoTiledMappingManagerEngineNokia *engine, QObject *parent /*= 0*/) : + QGeoTiledMap(engine, parent), + m_logo(":/images/logo.png"), // HERE logo image + m_engine(engine) +{} + +QGeoTiledMapNokia::~QGeoTiledMapNokia() {} + +void QGeoTiledMapNokia::evaluateCopyrights(const QSet &visibleTiles) +{ + const int spaceToLogo = 4; + const int blurRate = 1; + const int fontSize = 10; + + if (m_engine.isNull()) + return; + + const QString copyrightsString = m_engine->evaluateCopyrightsText(activeMapType(), cameraData().zoomLevel(), visibleTiles); + + if (width() > 0 && height() > 0 && ((copyrightsString.isNull() && m_copyrightsSlab.isNull()) || copyrightsString != m_lastCopyrightsString)) { + QFont font("Sans Serif"); + font.setPixelSize(fontSize); + font.setStyleHint(QFont::SansSerif); + font.setWeight(QFont::Bold); + + QRect textBounds = QFontMetrics(font).boundingRect(0, 0, width(), height(), Qt::AlignBottom | Qt::AlignLeft | Qt::TextWordWrap, copyrightsString); + + m_copyrightsSlab = QImage(m_logo.width() + textBounds.width() + spaceToLogo + blurRate * 2, + qMax(m_logo.height(), textBounds.height() + blurRate * 2), + QImage::Format_ARGB32_Premultiplied); + m_copyrightsSlab.fill(Qt::transparent); + + QPainter painter(&m_copyrightsSlab); + painter.drawImage(QPoint(0, m_copyrightsSlab.height() - m_logo.height()), m_logo); + painter.setFont(font); + painter.setPen(QColor(0, 0, 0, 64)); + painter.translate(spaceToLogo + m_logo.width(), -blurRate); + for (int x=-blurRate; x<=blurRate; ++x) { + for (int y=-blurRate; y<=blurRate; ++y) { + painter.drawText(x, y, textBounds.width(), m_copyrightsSlab.height(), + Qt::AlignBottom | Qt::AlignLeft | Qt::TextWordWrap, + copyrightsString); + } + } + painter.setPen(Qt::white); + painter.drawText(0, 0, textBounds.width(), m_copyrightsSlab.height(), + Qt::AlignBottom | Qt::AlignLeft | Qt::TextWordWrap, + copyrightsString); + painter.end(); + + m_lastCopyrightsString = copyrightsString; + } + + emit copyrightsChanged(m_copyrightsSlab); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeotiledmap_nokia.h b/src/plugins/geoservices/nokia/qgeotiledmap_nokia.h new file mode 100644 index 0000000..9651cc8 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeotiledmap_nokia.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAP_NOKIA_H +#define QGEOMAP_NOKIA_H + +#include "qgeotiledmap_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoTiledMappingManagerEngineNokia; + +class QGeoTiledMapNokia: public QGeoTiledMap +{ +Q_OBJECT +public: + QGeoTiledMapNokia(QGeoTiledMappingManagerEngineNokia *engine, QObject *parent = 0); + ~QGeoTiledMapNokia(); + + QString getViewCopyright(); + void evaluateCopyrights(const QSet &visibleTiles); + +private: + QImage m_logo; + QImage m_copyrightsSlab; + QString m_lastCopyrightsString; + QPointer m_engine; + + Q_DISABLE_COPY(QGeoTiledMapNokia) +}; + +QT_END_NAMESPACE + +#endif // QGEOMAP_NOKIA_H diff --git a/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.cpp b/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.cpp new file mode 100644 index 0000000..4f44e5f --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.cpp @@ -0,0 +1,384 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocameracapabilities_p.h" +#include "qgeotiledmappingmanagerengine_nokia.h" +#include "qgeotiledmap_nokia.h" +#include "qgeotilefetcher_nokia.h" +#include "qgeotilespec_p.h" +#include "qgeofiletilecache_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoTiledMappingManagerEngineNokia::QGeoTiledMappingManagerEngineNokia( + QGeoNetworkAccessManager *networkManager, + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) + : QGeoTiledMappingManagerEngine() +{ + Q_UNUSED(error); + Q_UNUSED(errorString); + + QGeoCameraCapabilities capabilities; + + capabilities.setMinimumZoomLevel(0.0); + capabilities.setMaximumZoomLevel(20.0); + + setCameraCapabilities(capabilities); + + setTileSize(QSize(256, 256)); + + QList types; + types << QGeoMapType(QGeoMapType::StreetMap, tr("Street Map"), tr("Normal map view in daylight mode"), false, false, 1); + types << QGeoMapType(QGeoMapType::SatelliteMapDay, tr("Satellite Map"), tr("Satellite map view in daylight mode"), false, false, 2); + types << QGeoMapType(QGeoMapType::TerrainMap, tr("Terrain Map"), tr("Terrain map view in daylight mode"), false, false, 3); + types << QGeoMapType(QGeoMapType::HybridMap, tr("Hybrid Map"), tr("Satellite map view with streets in daylight mode"), false, false, 4); + types << QGeoMapType(QGeoMapType::TransitMap, tr("Transit Map"), tr("Color-reduced map view with public transport scheme in daylight mode"), false, false, 5); + types << QGeoMapType(QGeoMapType::GrayStreetMap, tr("Gray Street Map"), tr("Color-reduced map view in daylight mode"), false, false, 6); + types << QGeoMapType(QGeoMapType::StreetMap, tr("Mobile Street Map"), tr("Mobile normal map view in daylight mode"), true, false, 7); + types << QGeoMapType(QGeoMapType::TerrainMap, tr("Mobile Terrain Map"), tr("Mobile terrain map view in daylight mode"), true, false, 8); + types << QGeoMapType(QGeoMapType::HybridMap, tr("Mobile Hybrid Map"), tr("Mobile satellite map view with streets in daylight mode"), true, false, 9); + types << QGeoMapType(QGeoMapType::TransitMap, tr("Mobile Transit Map"), tr("Mobile color-reduced map view with public transport scheme in daylight mode"), true, false, 10); + types << QGeoMapType(QGeoMapType::GrayStreetMap, tr("Mobile Gray Street Map"), tr("Mobile color-reduced map view in daylight mode"), true, false, 11); + types << QGeoMapType(QGeoMapType::StreetMap, tr("Custom Street Map"), tr("Normal map view in daylight mode"), false, false, 12); + types << QGeoMapType(QGeoMapType::StreetMap, tr("Night Street Map"), tr("Normal map view in night mode"), false, true, 13); + types << QGeoMapType(QGeoMapType::StreetMap, tr("Mobile Night Street Map"), tr("Mobile normal map view in night mode"), true, true, 14); + types << QGeoMapType(QGeoMapType::GrayStreetMap, tr("Gray Night Street Map"), tr("Color-reduced map view in night mode (especially used for background maps)"), false, true, 15); + types << QGeoMapType(QGeoMapType::GrayStreetMap, tr("Mobile Gray Night Street Map"), tr("Mobile color-reduced map view in night mode (especially used for background maps)"), true, true, 16); + types << QGeoMapType(QGeoMapType::PedestrianMap, tr("Pedestrian Street Map"), tr("Pedestrian map view in daylight mode"), false, false, 17); + types << QGeoMapType(QGeoMapType::PedestrianMap, tr("Mobile Pedestrian Street Map"), tr("Mobile pedestrian map view in daylight mode for mobile usage"), true, false, 18); + types << QGeoMapType(QGeoMapType::PedestrianMap, tr("Pedestrian Night Street Map"), tr("Pedestrian map view in night mode"), false, true, 19); + types << QGeoMapType(QGeoMapType::PedestrianMap, tr("Mobile Pedestrian Night Street Map"), tr("Mobile pedestrian map view in night mode for mobile usage"), true, true, 20); + types << QGeoMapType(QGeoMapType::CarNavigationMap, tr("Car Navigation Map"), tr("Normal map view in daylight mode for car navigation"), false, false, 21); + setSupportedMapTypes(types); + + QGeoTileFetcherNokia *fetcher = new QGeoTileFetcherNokia(parameters, networkManager, this, tileSize()); + setTileFetcher(fetcher); + + // TODO: do this in a plugin-neutral way so that other tiled map plugins + // don't need this boilerplate or hardcode plugin name + + if (parameters.contains(QStringLiteral("here.mapping.cache.directory"))) { + m_cacheDirectory = parameters.value(QStringLiteral("here.mapping.cache.directory")).toString(); + } else { + // managerName() is not yet set, we have to hardcode the plugin name below + m_cacheDirectory = QAbstractGeoTileCache::baseCacheDirectory() + QLatin1String("here"); + } + + QAbstractGeoTileCache *tileCache = new QGeoFileTileCache(m_cacheDirectory); + setTileCache(tileCache); + + if (parameters.contains(QStringLiteral("here.mapping.cache.disk.size"))) { + bool ok = false; + int cacheSize = parameters.value(QStringLiteral("here.mapping.cache.disk.size")).toString().toInt(&ok); + if (ok) + tileCache->setMaxDiskUsage(cacheSize); + } + + if (parameters.contains(QStringLiteral("here.mapping.cache.memory.size"))) { + bool ok = false; + int cacheSize = parameters.value(QStringLiteral("here.mapping.cache.memory.size")).toString().toInt(&ok); + if (ok) + tileCache->setMaxMemoryUsage(cacheSize); + } + + if (parameters.contains(QStringLiteral("here.mapping.cache.texture.size"))) { + bool ok = false; + int cacheSize = parameters.value(QStringLiteral("here.mapping.cache.texture.size")).toString().toInt(&ok); + if (ok) + tileCache->setExtraTextureUsage(cacheSize); + } + + populateMapSchemes(); + loadMapVersion(); + QMetaObject::invokeMethod(fetcher, "fetchCopyrightsData", Qt::QueuedConnection); + QMetaObject::invokeMethod(fetcher, "fetchVersionData", Qt::QueuedConnection); +} + +QGeoTiledMappingManagerEngineNokia::~QGeoTiledMappingManagerEngineNokia() +{ +} + +void QGeoTiledMappingManagerEngineNokia::populateMapSchemes() +{ + m_mapSchemes[0] = QStringLiteral("normal.day"); + m_mapSchemes[1] = QStringLiteral("normal.day"); + m_mapSchemes[2] = QStringLiteral("satellite.day"); + m_mapSchemes[3] = QStringLiteral("terrain.day"); + m_mapSchemes[4] = QStringLiteral("hybrid.day"); + m_mapSchemes[5] = QStringLiteral("normal.day.transit"); + m_mapSchemes[6] = QStringLiteral("normal.day.grey"); + m_mapSchemes[7] = QStringLiteral("normal.day.mobile"); + m_mapSchemes[8] = QStringLiteral("terrain.day.mobile"); + m_mapSchemes[9] = QStringLiteral("hybrid.day.mobile"); + m_mapSchemes[10] = QStringLiteral("normal.day.transit.mobile"); + m_mapSchemes[11] = QStringLiteral("normal.day.grey.mobile"); + m_mapSchemes[12] = QStringLiteral("normal.day.custom"); + m_mapSchemes[13] = QStringLiteral("normal.night"); + m_mapSchemes[14] = QStringLiteral("normal.night.mobile"); + m_mapSchemes[15] = QStringLiteral("normal.night.grey"); + m_mapSchemes[16] = QStringLiteral("normal.night.grey.mobile"); + m_mapSchemes[17] = QStringLiteral("pedestrian.day"); + m_mapSchemes[18] = QStringLiteral("pedestrian.day.mobile"); + m_mapSchemes[19] = QStringLiteral("pedestrian.night"); + m_mapSchemes[20] = QStringLiteral("pedestrian.night.mobile"); + m_mapSchemes[21] = QStringLiteral("carnav.day.grey"); +} + +QString QGeoTiledMappingManagerEngineNokia::getScheme(int mapId) +{ + return m_mapSchemes[mapId]; +} + +QString QGeoTiledMappingManagerEngineNokia::getBaseScheme(int mapId) +{ + QString fullScheme(m_mapSchemes[mapId]); + + return fullScheme.section(QLatin1Char('.'), 0, 0); +} + +int QGeoTiledMappingManagerEngineNokia::mapVersion() +{ + return m_mapVersion.version(); +} + +void QGeoTiledMappingManagerEngineNokia::loadCopyrightsDescriptorsFromJson(const QByteArray &jsonData) +{ + QJsonDocument doc = QJsonDocument::fromJson(QByteArray(jsonData)); + if (doc.isNull()) { + qDebug() << "QGeoTiledMappingManagerEngineNokia::loadCopyrightsDescriptorsFromJson() Invalid JSon document"; + return; + } + + QJsonObject jsonObj = doc.object(); + + m_copyrights.clear(); + for (auto it = jsonObj.constBegin(), end = jsonObj.constEnd(); it != end; ++it) { + QList copyrightDescList; + + QJsonArray descs = it.value().toArray(); + for (int descIndex = 0; descIndex < descs.count(); descIndex++) { + CopyrightDesc copyrightDesc; + QJsonObject desc = descs.at(descIndex).toObject(); + + copyrightDesc.minLevel = desc["minLevel"].toDouble(); + copyrightDesc.maxLevel = desc["maxLevel"].toDouble(); + copyrightDesc.label = desc["label"].toString(); + copyrightDesc.alt = desc["alt"].toString(); + + QJsonArray coordBoxes = desc["boxes"].toArray(); + for (int boxIndex = 0; boxIndex < coordBoxes.count(); boxIndex++) { + QJsonArray box = coordBoxes[boxIndex].toArray(); + qreal top = box[0].toDouble(); + qreal left = box[1].toDouble(); + qreal bottom = box[2].toDouble(); + qreal right = box[3].toDouble(); + QGeoRectangle boundingBox(QGeoCoordinate(top > bottom? top : bottom, + left), + QGeoCoordinate(top > bottom? bottom : top, + right)); + copyrightDesc.boxes << boundingBox; + } + copyrightDescList << copyrightDesc; + } + m_copyrights[it.key()] = copyrightDescList; + } +} + +void QGeoTiledMappingManagerEngineNokia::parseNewVersionInfo(const QByteArray &versionData) +{ + const QString versionString = QString::fromUtf8(versionData); + + const QStringList versionLines = versionString.split(QLatin1Char('\n')); + QJsonObject newVersionData; + foreach (const QString &line, versionLines) { + const QStringList versionInfo = line.split(':'); + if (versionInfo.size() > 1) { + const QString versionKey = versionInfo[0].trimmed(); + const QString versionValue = versionInfo[1].trimmed(); + if (!versionKey.isEmpty() && !versionValue.isEmpty()) { + newVersionData[versionKey] = versionValue; + } + } + } + + updateVersion(newVersionData); +} + +void QGeoTiledMappingManagerEngineNokia::updateVersion(const QJsonObject &newVersionData) { + + if (m_mapVersion.isNewVersion(newVersionData)) { + + m_mapVersion.setVersionData(newVersionData); + m_mapVersion.setVersion(m_mapVersion.version() + 1); + + saveMapVersion(); + setTileVersion(m_mapVersion.version()); + } +} + +void QGeoTiledMappingManagerEngineNokia::saveMapVersion() +{ + QDir saveDir(m_cacheDirectory); + QFile saveFile(saveDir.filePath(QStringLiteral("here_version"))); + + if (!saveFile.open(QIODevice::WriteOnly)) { + qWarning("Failed to write here/nokia map version."); + return; + } + + saveFile.write(m_mapVersion.toJson()); + saveFile.close(); +} + +void QGeoTiledMappingManagerEngineNokia::loadMapVersion() +{ + QDir saveDir(m_cacheDirectory); + QFile loadFile(saveDir.filePath(QStringLiteral("here_version"))); + + if (!loadFile.open(QIODevice::ReadOnly)) { + qWarning("Failed to read here/nokia map version."); + return; + } + + QByteArray saveData = loadFile.readAll(); + loadFile.close(); + + QJsonDocument doc(QJsonDocument::fromJson(saveData)); + + QJsonObject object = doc.object(); + + m_mapVersion.setVersion(object[QStringLiteral("version")].toInt()); + m_mapVersion.setVersionData(object[QStringLiteral("data")].toObject()); + setTileVersion(m_mapVersion.version()); +} + +QString QGeoTiledMappingManagerEngineNokia::evaluateCopyrightsText(const QGeoMapType mapType, + const qreal zoomLevel, + const QSet &tiles) +{ + static const QChar copyrightSymbol(0x00a9); + typedef QSet::const_iterator tile_iter; + QGeoRectangle viewport; + double viewX0, viewY0, viewX1, viewY1; + + tile_iter tile = tiles.constBegin(); + tile_iter lastTile = tiles.constEnd(); + + if (tiles.count()) { + double divFactor = qPow(2.0, tile->zoom()); + viewX0 = viewX1 = tile->x(); + viewY0 = viewY1 = tile->y(); + + // this approach establishes a geo-bounding box from passed tiles to test for intersecition + // with copyrights boxes. + int numTiles = 0; + for (; tile != lastTile; ++tile) { + if (tile->x() < viewX0) + viewX0 = tile->x(); + if (tile->x() > viewX1) + viewX1 = tile->x(); + if (tile->y() < viewY0) + viewY0 = tile->y(); + if (tile->y() > viewY1) + viewY1 = tile->y(); + numTiles++; + } + + viewX1++; + viewY1++; + + QDoubleVector2D pt; + + pt.setX(viewX0 / divFactor); + pt.setY(viewY0 / divFactor); + viewport.setTopLeft(QGeoProjection::mercatorToCoord(pt)); + pt.setX(viewX1 / divFactor); + pt.setY(viewY1 / divFactor); + viewport.setBottomRight(QGeoProjection::mercatorToCoord(pt)); + } + + // TODO: the following invalidation detection algorithm may be improved later. + QList descriptorList = m_copyrights[ getBaseScheme(mapType.mapId()) ]; + CopyrightDesc *descriptor; + int descIndex, boxIndex; + QString copyrightsText; + QSet copyrightStrings; + + for (descIndex = 0; descIndex < descriptorList.count(); descIndex++) { + if (descriptorList[descIndex].minLevel <= zoomLevel && zoomLevel <= descriptorList[descIndex].maxLevel) { + descriptor = &descriptorList[descIndex]; + + for (boxIndex = 0; boxIndex < descriptor->boxes.count(); boxIndex++) { + QGeoRectangle box = descriptor->boxes[boxIndex]; + + if (box.intersects(viewport)) { + copyrightStrings.insert(descriptor->label); + break; + } + } + if (!descriptor->boxes.count()) + copyrightStrings.insert(descriptor->label); + } + } + + foreach (const QString ©rightString, copyrightStrings) { + if (copyrightsText.length()) + copyrightsText += QLatin1Char('\n'); + copyrightsText += copyrightSymbol; + copyrightsText += copyrightString; + } + + return copyrightsText; +} + +QGeoMap *QGeoTiledMappingManagerEngineNokia::createMap() +{ + return new QGeoTiledMapNokia(this); +} + +QT_END_NAMESPACE + diff --git a/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.h b/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.h new file mode 100644 index 0000000..1648312 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeotiledmappingmanagerengine_nokia.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEDMAPPINGMANAGERENGINE_NOKIA_H +#define QGEOTILEDMAPPINGMANAGERENGINE_NOKIA_H + +#include "qgeotiledmappingmanagerengine_p.h" +#include +#include "qgeomaptype_p.h" +#include "qgeomapversion.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QByteArray; +class QGeoTileSpec; +class QGeoNetworkAccessManager; + +class QGeoTiledMappingManagerEngineNokia : public QGeoTiledMappingManagerEngine +{ + Q_OBJECT + +public: + QGeoTiledMappingManagerEngineNokia(QGeoNetworkAccessManager *networkManager, + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString); + ~QGeoTiledMappingManagerEngineNokia(); + + virtual QGeoMap *createMap(); + QString evaluateCopyrightsText(const QGeoMapType mapType, + const qreal zoomLevel, + const QSet &tiles); + QString getScheme(int mapId); + QString getBaseScheme(int mapId); + int mapVersion(); + +public Q_SLOTS: + void loadCopyrightsDescriptorsFromJson(const QByteArray &jsonData); + void parseNewVersionInfo(const QByteArray &versionData); + +private: + class CopyrightDesc + { + public: + CopyrightDesc() + : maxLevel(-1), + minLevel(-1) {} + + qreal maxLevel; + qreal minLevel; + QList boxes; + QString alt; + QString label; + }; + + void initialize(); + void populateMapSchemes(); + void updateVersion(const QJsonObject &newVersionData); + void saveMapVersion(); + void loadMapVersion(); + + QHash > m_copyrights; + QHash m_mapSchemes; + QGeoMapVersion m_mapVersion; + + QString m_cacheDirectory; +}; + +QT_END_NAMESPACE + +#endif // QGEOTILEDMAPPINGMANAGERENGINE_NOKIA_H diff --git a/src/plugins/geoservices/nokia/qgeotilefetcher_nokia.cpp b/src/plugins/geoservices/nokia/qgeotilefetcher_nokia.cpp new file mode 100644 index 0000000..50acc2a --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeotilefetcher_nokia.cpp @@ -0,0 +1,323 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotilefetcher_nokia.h" +#include "qgeomapreply_nokia.h" +#include "qgeotiledmap_nokia.h" +#include "qgeotiledmappingmanagerengine_nokia.h" +#include "qgeonetworkaccessmanager.h" +#include "qgeouriprovider.h" +#include "uri_constants.h" + +#include + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace +{ + QString sizeToStr(const QSize &size) + { + if (size.height() >= 512 || size.width() >= 512) + return QStringLiteral("512"); + else if (size.height() >= 256 || size.width() >= 256) + return QStringLiteral("256"); + else + return QStringLiteral("128"); // 128 pixel tiles are deprecated. + } + + bool isAerialType(const QString mapScheme) + { + return mapScheme.startsWith("satellite") || mapScheme.startsWith("hybrid") || mapScheme.startsWith("terrain"); + } +} +QGeoTileFetcherNokia::QGeoTileFetcherNokia(const QVariantMap ¶meters, + QGeoNetworkAccessManager *networkManager, + QGeoTiledMappingManagerEngineNokia *engine, + const QSize &tileSize) +: QGeoTileFetcher(engine), m_engineNokia(engine), m_networkManager(networkManager), + m_tileSize(tileSize), m_copyrightsReply(0), + m_baseUriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.mapping.host"), MAP_TILES_HOST)), + m_aerialUriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.mapping.host.aerial"), MAP_TILES_HOST_AERIAL)) +{ + Q_ASSERT(networkManager); + m_networkManager->setParent(this); + + m_applicationId = parameters.value(QStringLiteral("here.app_id")).toString(); + m_token = parameters.value(QStringLiteral("here.token")).toString(); +} + +QGeoTileFetcherNokia::~QGeoTileFetcherNokia() +{ +} + +QGeoTiledMapReply *QGeoTileFetcherNokia::getTileImage(const QGeoTileSpec &spec) +{ + // TODO add error detection for if request.connectivityMode() != QGraphicsGeoMap::OnlineMode + QString rawRequest = getRequestString(spec); + if (rawRequest.isEmpty()) { + return new QGeoTiledMapReply(QGeoTiledMapReply::UnknownError, + tr("Mapping manager no longer exists"), this); + } + + QNetworkRequest netRequest((QUrl(rawRequest))); // The extra pair of parens disambiguates this from a function declaration + netRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); + + QNetworkReply *netReply = m_networkManager->get(netRequest); + + QGeoTiledMapReply *mapReply = new QGeoMapReplyNokia(netReply, spec); + + return mapReply; +} + +QString QGeoTileFetcherNokia::getRequestString(const QGeoTileSpec &spec) +{ + if (!m_engineNokia) + return QString(); + + static const QString http("http://"); + static const QString path("/maptile/2.1/maptile/newest/"); + static const QChar slash('/'); + + QString requestString = http; + + const QString mapScheme = m_engineNokia->getScheme(spec.mapId()); + if (isAerialType(mapScheme)) + requestString += m_aerialUriProvider->getCurrentHost(); + else + requestString += m_baseUriProvider->getCurrentHost(); + + requestString += path; + requestString += mapScheme; + requestString += slash; + requestString += QString::number(spec.zoom()); + requestString += slash; + requestString += QString::number(spec.x()); + requestString += slash; + requestString += QString::number(spec.y()); + requestString += slash; + requestString += sizeToStr(m_tileSize); + static const QString slashpng("/png8"); + requestString += slashpng; + + if (!m_token.isEmpty() && !m_applicationId.isEmpty()) { + requestString += "?token="; + requestString += m_token; + + requestString += "&app_id="; + requestString += m_applicationId; + } + + requestString += "&lg="; + requestString += getLanguageString(); + + return requestString; +} + +QString QGeoTileFetcherNokia::getLanguageString() const +{ + if (!m_engineNokia) + return QStringLiteral("ENG"); + + QLocale locale = m_engineNokia.data()->locale(); + + // English is the default, where no ln is specified. We hardcode the languages + // here even though the entire list is updated automagically from the server. + // The current languages are Arabic, Chinese, Simplified Chinese, English + // French, German, Italian, Polish, Russian and Spanish. The default is English. + // These are actually available from the same host under the URL: /maptiler/v2/info + + switch (locale.language()) { + case QLocale::Arabic: + return QStringLiteral("ARA"); + case QLocale::Chinese: + if (locale.script() == QLocale::TraditionalChineseScript) + return QStringLiteral("CHI"); + else + return QStringLiteral("CHT"); + case QLocale::Dutch: + return QStringLiteral("DUT"); + case QLocale::French: + return QStringLiteral("FRE"); + case QLocale::German: + return QStringLiteral("GER"); + case QLocale::Gaelic: + return QStringLiteral("GLE"); + case QLocale::Greek: + return QStringLiteral("GRE"); + case QLocale::Hebrew: + return QStringLiteral("HEB"); + case QLocale::Hindi: + return QStringLiteral("HIN"); + case QLocale::Indonesian: + return QStringLiteral("IND"); + case QLocale::Italian: + return QStringLiteral("ITA"); + case QLocale::Persian: + return QStringLiteral("PER"); + case QLocale::Polish: + return QStringLiteral("POL"); + case QLocale::Portuguese: + return QStringLiteral("POR"); + case QLocale::Russian: + return QStringLiteral("RUS"); + case QLocale::Sinhala: + return QStringLiteral("SIN"); + case QLocale::Spanish: + return QStringLiteral("SPA"); + case QLocale::Thai: + return QStringLiteral("THA"); + case QLocale::Turkish: + return QStringLiteral("TUR"); + case QLocale::Ukrainian: + return QStringLiteral("UKR"); + case QLocale::Urdu: + return QStringLiteral("URD"); + case QLocale::Vietnamese: + return QStringLiteral("VIE"); + + default: + return QStringLiteral("ENG"); + } + // No "lg" param means that we want English. +} + +QString QGeoTileFetcherNokia::token() const +{ + return m_token; +} + +QString QGeoTileFetcherNokia::applicationId() const +{ + return m_applicationId; +} + +void QGeoTileFetcherNokia::copyrightsFetched() +{ + if (m_engineNokia && m_copyrightsReply->error() == QNetworkReply::NoError) { + QMetaObject::invokeMethod(m_engineNokia.data(), + "loadCopyrightsDescriptorsFromJson", + Qt::QueuedConnection, + Q_ARG(QByteArray, m_copyrightsReply->readAll())); + } + + m_copyrightsReply->deleteLater(); +} + +void QGeoTileFetcherNokia::versionFetched() +{ + if (m_engineNokia && m_versionReply->error() == QNetworkReply::NoError) { + QMetaObject::invokeMethod(m_engineNokia.data(), + "parseNewVersionInfo", + Qt::QueuedConnection, + Q_ARG(QByteArray, m_versionReply->readAll())); + } + + m_versionReply->deleteLater(); +} + +void QGeoTileFetcherNokia::fetchCopyrightsData() +{ + QString copyrightUrl = QStringLiteral("http://"); + + copyrightUrl += m_baseUriProvider->getCurrentHost(); + copyrightUrl += QStringLiteral("/maptile/2.1/copyright/newest?output=json"); + + if (!token().isEmpty()) { + copyrightUrl += QStringLiteral("&token="); + copyrightUrl += token(); + } + + if (!applicationId().isEmpty()) { + copyrightUrl += QStringLiteral("&app_id="); + copyrightUrl += applicationId(); + } + + QNetworkRequest netRequest((QUrl(copyrightUrl))); + m_copyrightsReply = m_networkManager->get(netRequest); + if (m_copyrightsReply->error() != QNetworkReply::NoError) { + qWarning() << __FUNCTION__ << m_copyrightsReply->errorString(); + m_copyrightsReply->deleteLater(); + return; + } + + if (m_copyrightsReply->isFinished()) { + copyrightsFetched(); + } else { + connect(m_copyrightsReply, SIGNAL(finished()), this, SLOT(copyrightsFetched())); + } +} + +void QGeoTileFetcherNokia::fetchVersionData() +{ + QString versionUrl = QStringLiteral("http://"); + + versionUrl += m_baseUriProvider->getCurrentHost(); + versionUrl += QStringLiteral("/maptile/2.1/version"); + + if (!token().isEmpty()) { + versionUrl += QStringLiteral("?token="); + versionUrl += token(); + } + + if (!applicationId().isEmpty()) { + versionUrl += QStringLiteral("&app_id="); + versionUrl += applicationId(); + } + + QNetworkRequest netRequest((QUrl(versionUrl))); + m_versionReply = m_networkManager->get(netRequest); + + if (m_versionReply->error() != QNetworkReply::NoError) { + qWarning() << __FUNCTION__ << m_versionReply->errorString(); + m_versionReply->deleteLater(); + return; + } + + if (m_versionReply->isFinished()) + versionFetched(); + else + connect(m_versionReply, SIGNAL(finished()), this, SLOT(versionFetched())); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeotilefetcher_nokia.h b/src/plugins/geoservices/nokia/qgeotilefetcher_nokia.h new file mode 100644 index 0000000..44f2ad0 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeotilefetcher_nokia.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEFETCHER_NOKIA_H +#define QGEOTILEFETCHER_NOKIA_H + +#include "qgeoserviceproviderplugin_nokia.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoTiledMapReply; +class QGeoTileSpec; +class QGeoTiledMappingManagerEngine; +class QGeoTiledMappingManagerEngineNokia; +class QNetworkReply; +class QGeoNetworkAccessManager; +class QGeoUriProvider; + +class QGeoTileFetcherNokia : public QGeoTileFetcher +{ + Q_OBJECT + +public: + QGeoTileFetcherNokia(const QVariantMap ¶meters, QGeoNetworkAccessManager *networkManager, + QGeoTiledMappingManagerEngineNokia *engine, const QSize &tileSize); + ~QGeoTileFetcherNokia(); + + QGeoTiledMapReply *getTileImage(const QGeoTileSpec &spec); + + QString token() const; + QString applicationId() const; + +public Q_SLOTS: + void copyrightsFetched(); + void fetchCopyrightsData(); + void versionFetched(); + void fetchVersionData(); + +private: + Q_DISABLE_COPY(QGeoTileFetcherNokia) + + QString getRequestString(const QGeoTileSpec &spec); + + QString getLanguageString() const; + + QPointer m_engineNokia; + QGeoNetworkAccessManager *m_networkManager; + QSize m_tileSize; + QString m_token; + QNetworkReply *m_copyrightsReply; + QNetworkReply *m_versionReply; + + QString m_applicationId; + QGeoUriProvider *m_baseUriProvider; + QGeoUriProvider *m_aerialUriProvider; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/nokia/qgeouriprovider.cpp b/src/plugins/geoservices/nokia/qgeouriprovider.cpp new file mode 100644 index 0000000..05ace12 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeouriprovider.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeouriprovider.h" + +#ifdef USE_CHINA_NETWORK_REGISTRATION +#include +#endif + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace +{ + const QString CHINA_MCC = QLatin1String("460"); // China mobile country code + const QString CHINA2_MCC = QLatin1String("461"); // China mobile country code + const QString HONG_KONG_MCC = QLatin1String("454"); // Hong Kong mobile country code + const QString MACAU_MCC = QLatin1String("455"); // Macau mobile country code +} + +QGeoUriProvider::QGeoUriProvider( + QObject *parent, + const QVariantMap ¶meters, + const QString &hostParameterName, + const QString &internationalHost, + const QString &localizedHost) + : QObject(parent) +#ifdef USE_CHINA_NETWORK_REGISTRATION + , m_networkInfo(new QNetworkInfo(this)) +#endif + , m_internationalHost(parameters.value(hostParameterName, internationalHost).toString()) + , m_localizedHost(localizedHost) + , m_firstSubdomain(QChar::Null) + , m_maxSubdomains(0) +{ +#ifdef USE_CHINA_NETWORK_REGISTRATION + QObject::connect(m_networkInfo, SIGNAL(currentMobileCountryCodeChanged(int,QString)), this, SLOT(mobileCountryCodeChanged(int,QString))); +#endif + setCurrentHost(isInternationalNetwork() || m_localizedHost.isEmpty() ? m_internationalHost : m_localizedHost); +} + +QString QGeoUriProvider::getCurrentHost() const +{ + if (m_maxSubdomains) { + QString result(m_firstSubdomain.toLatin1() + qrand() % m_maxSubdomains); + result += '.' + m_currentHost; + return result; + } + return m_currentHost; +} + +void QGeoUriProvider::setCurrentHost(const QString &host) +{ + if (host.length() > 4 && host.at(1) == QChar('-') && host.at(3) == QChar('.')) { + QString realHost = host.right(host.length() - 4); + m_firstSubdomain = host.at(0); + m_maxSubdomains = host.at(2).toLatin1() - host.at(0).toLatin1() + 1; + m_currentHost = realHost; + } else { + m_currentHost = host; + m_firstSubdomain = QChar::Null; + m_maxSubdomains = 0; + } +} + +void QGeoUriProvider::mobileCountryCodeChanged(int interface, const QString& mcc) +{ + Q_UNUSED(interface) + Q_UNUSED(mcc) + + setCurrentHost(isInternationalNetwork() || m_localizedHost.isEmpty() ? m_internationalHost : m_localizedHost); +} + +bool QGeoUriProvider::isInternationalNetwork() const +{ +#ifndef USE_CHINA_NETWORK_REGISTRATION + return true; +#else + static QSet codes; + if (codes.empty()) { + codes.insert(CHINA_MCC); + codes.insert(CHINA2_MCC); + } + + QNetworkInfo::NetworkMode mode = m_networkInfo->currentNetworkMode(); + + int interfaces = m_networkInfo->networkInterfaceCount(mode); + for (int i = 0; i < interfaces; ++i) { + QString mcc = m_networkInfo->currentMobileCountryCode(interfaces); + if (codes.contains(mcc)) + return false; + } + + return true; +#endif // USE_CHINA_NETWORK_REGISTRATION +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qgeouriprovider.h b/src/plugins/geoservices/nokia/qgeouriprovider.h new file mode 100644 index 0000000..1bd30b5 --- /dev/null +++ b/src/plugins/geoservices/nokia/qgeouriprovider.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEO_MOBILE_COUNTRY_TRACKER_H +#define QGEO_MOBILE_COUNTRY_TRACKER_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QNetworkInfo; + +class QGeoUriProvider : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(QGeoUriProvider) + +public: + QGeoUriProvider(QObject *parent, + const QVariantMap ¶meters, + const QString &hostParameterName, + const QString &internationalHost, + const QString &localizedHost = QString()); + + QString getCurrentHost() const; + +private Q_SLOTS: + void mobileCountryCodeChanged(int interface, const QString& mcc); + +private: + bool isInternationalNetwork() const; + void setCurrentHost(const QString &host); + +#ifdef USE_CHINA_NETWORK_REGISTRATION + QNetworkInfo *m_networkInfo; +#endif + const QString m_internationalHost; + const QString m_localizedHost; + QString m_currentHost; + QChar m_firstSubdomain; + unsigned char m_maxSubdomains; +}; + +QT_END_NAMESPACE + +#endif // QGEO_MOBILE_COUNTRY_TRACKER_H diff --git a/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.cpp b/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.cpp new file mode 100644 index 0000000..6dcb28d --- /dev/null +++ b/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.cpp @@ -0,0 +1,863 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacemanagerengine_nokiav2.h" + +#include "placesv2/qplacecategoriesreplyhere.h" +#include "placesv2/qplacecontentreplyimpl.h" +#include "placesv2/qplacesearchsuggestionreplyimpl.h" +#include "placesv2/qplacesearchreplyhere.h" +#include "placesv2/qplacedetailsreplyimpl.h" +#include "placesv2/qplaceidreplyimpl.h" +#include "qgeonetworkaccessmanager.h" +#include "qgeouriprovider.h" +#include "uri_constants.h" +#include "qgeoerror_messages.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +static const char FIXED_CATEGORIES_string[] = + "eat-drink\0" + "going-out\0" + "sights-museums\0" + "transport\0" + "accommodation\0" + "shopping\0" + "leisure-outdoor\0" + "administrative-areas-buildings\0" + "natural-geographical\0" + "petrol-station\0" + "atm-bank-exchange\0" + "toilet-rest-area\0" + "hospital-health-care-facility\0" + "eat-drink|restaurant\0" // subcategories always start after relative parent category + "eat-drink|coffee-tea\0" + "eat-drink|snacks-fast-food\0" + "transport|airport" + "\0"; + +static const int FIXED_CATEGORIES_indices[] = { + 0, 10, 20, 35, 45, 59, 68, 84, + 115, 136, 151, 169, 186, 216, 237, 258, + 285, -1 +}; + +static const char * const NokiaIcon = "nokiaIcon"; +static const char * const IconPrefix = "iconPrefix"; +static const char * const NokiaIconGenerated = "nokiaIconGenerated"; + +static const char * const IconThemeKey = "places.icons.theme"; +static const char * const LocalDataPathKey = "places.local_data_path"; + +class CategoryParser +{ +public: + CategoryParser(); + bool parse(const QString &fileName); + + QPlaceCategoryTree tree() const { return m_tree; } + QHash restIdToIconHash() const { return m_restIdToIconHash; } + + QString errorString() const; + +private: + void processCategory(int level, const QString &id, + const QString &parentId = QString()); + + QJsonObject m_exploreObject; + QPlaceCategoryTree m_tree; + QString m_errorString; + + QHash m_restIdToIconHash; +}; + +CategoryParser::CategoryParser() +{ +} + +bool CategoryParser::parse(const QString &fileName) +{ + m_exploreObject = QJsonObject(); + m_tree.clear(); + m_errorString.clear(); + + QFile mappingFile(fileName); + + if (mappingFile.open(QIODevice::ReadOnly)) { + QJsonDocument document = QJsonDocument::fromJson(mappingFile.readAll()); + if (document.isObject()) { + QJsonObject docObject = document.object(); + if (docObject.contains(QStringLiteral("offline_explore"))) { + m_exploreObject = docObject.value(QStringLiteral("offline_explore")) + .toObject(); + if (m_exploreObject.contains(QStringLiteral("ROOT"))) { + processCategory(0, QString()); + return true; + } + } else { + m_errorString = fileName + + QStringLiteral("does not contain the offline_explore property"); + return false; + } + } else { + m_errorString = fileName + QStringLiteral("is not an json object"); + return false; + } + } + m_errorString = QString::fromLatin1("Unable to open ") + fileName; + return false; +} + +void CategoryParser::processCategory(int level, const QString &id, const QString &parentId) +{ + //We are basing the tree on a DAG from the input file, however we are simplyfing + //this into a 2 level tree, and a given category only has one parent + // + // A->B->Z + // A->C->Z + // Z in this case is not in the tree because it is 3 levels deep. + // + // X->Z + // Y->Z + // Only one of these is shown in the tree since Z can only have one parent + // the choice made between X and Y is arbitrary. + const int maxLevel = 2; + PlaceCategoryNode node; + node.category.setCategoryId(id); + node.parentId = parentId; + + m_tree.insert(node.category.categoryId(), node); + //this is simply to mark the node as being visited. + //a proper assignment to the tree happens at the end of function + + QJsonObject categoryJson = m_exploreObject.value(id.isEmpty() + ? QStringLiteral("ROOT") : id).toObject(); + QJsonArray children = categoryJson.value(QStringLiteral("children")).toArray(); + + if (level + 1 <= maxLevel && !categoryJson.contains(QStringLiteral("final"))) { + for (int i = 0; i < children.count(); ++i) { + QString childId = children.at(i).toString(); + if (!m_tree.contains(childId)) { + node.childIds.append(childId); + processCategory(level + 1, childId, id); + } + } + } + + m_tree.insert(node.category.categoryId(), node); +} + +QPlaceManagerEngineNokiaV2::QPlaceManagerEngineNokiaV2( + QGeoNetworkAccessManager *networkManager, + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) + : QPlaceManagerEngine(parameters) + , m_manager(networkManager) + , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.places.host"), PLACES_HOST, PLACES_HOST_CN)) +{ + Q_ASSERT(networkManager); + m_manager->setParent(this); + + m_locales.append(QLocale()); + + m_appId = parameters.value(QStringLiteral("here.app_id")).toString(); + m_appCode = parameters.value(QStringLiteral("here.token")).toString(); + + m_theme = parameters.value(IconThemeKey, QString()).toString(); + + if (m_theme == QStringLiteral("default")) + m_theme.clear(); + + m_localDataPath = parameters.value(LocalDataPathKey, QString()).toString(); + if (m_localDataPath.isEmpty()) { + QStringList dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + + if (!dataLocations.isEmpty() && !dataLocations.first().isEmpty()) { + m_localDataPath = dataLocations.first() + + QStringLiteral("/here/qtlocation/data"); + } + } + + if (error) + *error = QGeoServiceProvider::NoError; + + if (errorString) + errorString->clear(); +} + +QPlaceManagerEngineNokiaV2::~QPlaceManagerEngineNokiaV2() {} + +QPlaceDetailsReply *QPlaceManagerEngineNokiaV2::getPlaceDetails(const QString &placeId) +{ + QUrl requestUrl(QString::fromLatin1("http://") + m_uriProvider->getCurrentHost() + + QStringLiteral("/places/v1/places/") + placeId); + + QUrlQuery queryItems; + + queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html")); + //queryItems.append(qMakePair(QStringLiteral("size"), QString::number(5))); + //queryItems.append(qMakePair(QStringLiteral("image_dimensions"), QStringLiteral("w64-h64,w100"))); + + requestUrl.setQuery(queryItems); + + QNetworkReply *networkReply = sendRequest(requestUrl); + + QPlaceDetailsReplyImpl *reply = new QPlaceDetailsReplyImpl(networkReply, this); + reply->setPlaceId(placeId); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + return reply; +} + +QPlaceContentReply *QPlaceManagerEngineNokiaV2::getPlaceContent(const QPlaceContentRequest &request) +{ + QNetworkReply *networkReply = 0; + + if (request.contentContext().userType() == qMetaTypeId()) { + QUrl u = request.contentContext().value(); + + networkReply = sendRequest(u); + } else { + QUrl requestUrl(QString::fromLatin1("http://") + m_uriProvider->getCurrentHost() + + QStringLiteral("/places/v1/places/") + request.placeId() + + QStringLiteral("/media/")); + + QUrlQuery queryItems; + + switch (request.contentType()) { + case QPlaceContent::ImageType: + requestUrl.setPath(requestUrl.path() + QStringLiteral("images")); + + queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html")); + + if (request.limit() > 0) + queryItems.addQueryItem(QStringLiteral("size"), QString::number(request.limit())); + + //queryItems.append(qMakePair(QStringLiteral("image_dimensions"), QStringLiteral("w64-h64,w100"))); + + requestUrl.setQuery(queryItems); + + networkReply = sendRequest(requestUrl); + break; + case QPlaceContent::ReviewType: + requestUrl.setPath(requestUrl.path() + QStringLiteral("reviews")); + + queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html")); + + if (request.limit() > 0) + queryItems.addQueryItem(QStringLiteral("size"), QString::number(request.limit())); + + requestUrl.setQuery(queryItems); + + networkReply = sendRequest(requestUrl); + break; + case QPlaceContent::EditorialType: + requestUrl.setPath(requestUrl.path() + QStringLiteral("editorials")); + + queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html")); + + if (request.limit() > 0) + queryItems.addQueryItem(QStringLiteral("size"), QString::number(request.limit())); + + requestUrl.setQuery(queryItems); + + networkReply = sendRequest(requestUrl); + break; + case QPlaceContent::NoType: + ; + } + } + + QPlaceContentReply *reply = new QPlaceContentReplyImpl(request, networkReply, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + if (!networkReply) { + QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError), + Q_ARG(QString, QString("Retrieval of given content type not supported."))); + } + + return reply; +} + +static bool addAtForBoundingArea(const QGeoShape &area, + QUrlQuery *queryItems) +{ + QGeoCoordinate center = area.center(); + if (!center.isValid()) + return false; + + queryItems->addQueryItem(QStringLiteral("at"), + QString::number(center.latitude()) + + QLatin1Char(',') + + QString::number(center.longitude())); + return true; +} + +QPlaceSearchReply *QPlaceManagerEngineNokiaV2::search(const QPlaceSearchRequest &query) +{ + bool unsupported = false; + + unsupported |= query.visibilityScope() != QLocation::UnspecifiedVisibility && + query.visibilityScope() != QLocation::PublicVisibility; + + // Both a search term and search categories are not supported. + unsupported |= !query.searchTerm().isEmpty() && !query.categories().isEmpty(); + + //only a recommendation id by itself is supported. + unsupported |= !query.recommendationId().isEmpty() + && (!query.searchTerm().isEmpty() || !query.categories().isEmpty() + || query.searchArea().type() != QGeoShape::UnknownType); + + if (unsupported) { + QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, 0, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError), + Q_ARG(QString, "Unsupported search request options specified.")); + return reply; + } + + QUrlQuery queryItems; + + // Check that the search area is valid for all searches except recommendation and proposed + // searches, which do not need search centers. + if (query.recommendationId().isEmpty() && !query.searchContext().isValid()) { + if (!addAtForBoundingArea(query.searchArea(), &queryItems)) { + QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, 0, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError), + Q_ARG(QString, "Invalid search area provided")); + return reply; + } + } + + QNetworkReply *networkReply = 0; + + if (query.searchContext().userType() == qMetaTypeId()) { + // provided search context + QUrl u = query.searchContext().value(); + + typedef QPair QueryItem; + QList queryItemList = queryItems.queryItems(QUrl::FullyEncoded); + queryItems = QUrlQuery(u); + foreach (const QueryItem &item, queryItemList) + queryItems.addQueryItem(item.first, item.second); + + if (query.limit() > 0) + queryItems.addQueryItem(QStringLiteral("size"), QString::number(query.limit())); + + u.setQuery(queryItems); + + networkReply = sendRequest(u); + } else if (!query.searchTerm().isEmpty()) { + // search term query + QUrl requestUrl(QString::fromLatin1("http://") + m_uriProvider->getCurrentHost() + + QStringLiteral("/places/v1/discover/search")); + + queryItems.addQueryItem(QStringLiteral("q"), query.searchTerm()); + queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html")); + + if (query.limit() > 0) { + queryItems.addQueryItem(QStringLiteral("size"), + QString::number(query.limit())); + } + + requestUrl.setQuery(queryItems); + + QNetworkReply *networkReply = sendRequest(requestUrl); + + QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, networkReply, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + return reply; + } else if (!query.recommendationId().isEmpty()) { + QUrl requestUrl(QString::fromLatin1("http://") + m_uriProvider->getCurrentHost() + + QStringLiteral("/places/v1/places/") + query.recommendationId() + + QStringLiteral("/related/recommended")); + + queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html")); + + requestUrl.setQuery(queryItems); + + networkReply = sendRequest(requestUrl); + } else { + // category search + QUrl requestUrl(QStringLiteral("http://") + m_uriProvider->getCurrentHost() + + QStringLiteral("/places/v1/discover/explore")); + + QStringList ids; + foreach (const QPlaceCategory &category, query.categories()) + ids.append(category.categoryId()); + + QUrlQuery queryItems; + + if (!ids.isEmpty()) + queryItems.addQueryItem(QStringLiteral("cat"), ids.join(QStringLiteral(","))); + + addAtForBoundingArea(query.searchArea(), &queryItems); + + queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html")); + + if (query.limit() > 0) { + queryItems.addQueryItem(QStringLiteral("size"), + QString::number(query.limit())); + } + + requestUrl.setQuery(queryItems); + + networkReply = sendRequest(requestUrl); + } + + QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, networkReply, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + return reply; +} + +QPlaceSearchSuggestionReply *QPlaceManagerEngineNokiaV2::searchSuggestions(const QPlaceSearchRequest &query) +{ + bool unsupported = false; + + unsupported |= query.visibilityScope() != QLocation::UnspecifiedVisibility && + query.visibilityScope() != QLocation::PublicVisibility; + + unsupported |= !query.categories().isEmpty(); + unsupported |= !query.recommendationId().isEmpty(); + + if (unsupported) { + QPlaceSearchSuggestionReplyImpl *reply = new QPlaceSearchSuggestionReplyImpl(0, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError), + Q_ARG(QString, "Unsupported search request options specified.")); + return reply; + } + + QUrl requestUrl(QString::fromLatin1("http://") + m_uriProvider->getCurrentHost() + + QStringLiteral("/places/v1/suggest")); + + QUrlQuery queryItems; + + queryItems.addQueryItem(QStringLiteral("q"), query.searchTerm()); + + if (!addAtForBoundingArea(query.searchArea(), &queryItems)) { + QPlaceSearchSuggestionReplyImpl *reply = new QPlaceSearchSuggestionReplyImpl(0, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError), + Q_ARG(QString, "Invalid search area provided")); + return reply; + } + + requestUrl.setQuery(queryItems); + + QNetworkReply *networkReply = sendRequest(requestUrl); + + QPlaceSearchSuggestionReplyImpl *reply = new QPlaceSearchSuggestionReplyImpl(networkReply, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + return reply; +} + +QPlaceIdReply *QPlaceManagerEngineNokiaV2::savePlace(const QPlace &place) +{ + QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::SavePlace, this); + reply->setId(place.placeId()); + QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError), + Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, SAVING_PLACE_NOT_SUPPORTED))); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + return reply; +} + +QPlaceIdReply *QPlaceManagerEngineNokiaV2::removePlace(const QString &placeId) +{ + QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::RemovePlace, this); + reply->setId(placeId); + QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError), + Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, REMOVING_PLACE_NOT_SUPPORTED))); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + return reply; +} + +QPlaceIdReply *QPlaceManagerEngineNokiaV2::saveCategory(const QPlaceCategory &category, const QString &parentId) +{ + Q_UNUSED(parentId) + + QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::SaveCategory, this); + reply->setId(category.categoryId()); + QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError), + Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, SAVING_CATEGORY_NOT_SUPPORTED))); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + return reply; +} + +QPlaceIdReply *QPlaceManagerEngineNokiaV2::removeCategory(const QString &categoryId) +{ + QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::RemoveCategory, this); + reply->setId(categoryId); + QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError), + Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, REMOVING_CATEGORY_NOT_SUPPORTED))); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + return reply; +} + +QPlaceReply *QPlaceManagerEngineNokiaV2::initializeCategories() +{ + if (m_categoryReply) + return m_categoryReply.data(); + + m_tempTree.clear(); + CategoryParser parser; + + if (parser.parse(m_localDataPath + QStringLiteral("/offline/offline-mapping.json"))) { + m_tempTree = parser.tree(); + } else { + PlaceCategoryNode rootNode; + + for (int i = 0; FIXED_CATEGORIES_indices[i] != -1; ++i) { + const QString id = QString::fromLatin1(FIXED_CATEGORIES_string + + FIXED_CATEGORIES_indices[i]); + + int subCatDivider = id.indexOf(QChar('|')); + if (subCatDivider >= 0) { + // found a sub category + const QString subCategoryId = id.mid(subCatDivider+1); + const QString parentCategoryId = id.left(subCatDivider); + + if (m_tempTree.contains(parentCategoryId)) { + PlaceCategoryNode node; + node.category.setCategoryId(subCategoryId); + node.parentId = parentCategoryId; + + // find parent + PlaceCategoryNode &parent = m_tempTree[parentCategoryId]; + parent.childIds.append(subCategoryId); + m_tempTree.insert(subCategoryId, node); + } + + } else { + PlaceCategoryNode node; + node.category.setCategoryId(id); + + m_tempTree.insert(id, node); + rootNode.childIds.append(id); + } + } + + m_tempTree.insert(QString(), rootNode); + } + + //request all categories in the tree from the server + //because we don't want the root node, we skip it + for (auto it = m_tempTree.keyBegin(), end = m_tempTree.keyEnd(); it != end; ++it) { + if (*it == QString()) + continue; + QUrl requestUrl(QString::fromLatin1("http://") + m_uriProvider->getCurrentHost() + + QStringLiteral("/places/v1/categories/places/") + *it); + QNetworkReply *networkReply = sendRequest(requestUrl); + connect(networkReply, SIGNAL(finished()), this, SLOT(categoryReplyFinished())); + connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(categoryReplyError())); + + m_categoryRequests.insert(*it, networkReply); + } + + QPlaceCategoriesReplyHere *reply = new QPlaceCategoriesReplyHere(this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + m_categoryReply = reply; + return reply; +} + +QString QPlaceManagerEngineNokiaV2::parentCategoryId(const QString &categoryId) const +{ + return m_categoryTree.value(categoryId).parentId; +} + +QStringList QPlaceManagerEngineNokiaV2::childCategoryIds(const QString &categoryId) const +{ + return m_categoryTree.value(categoryId).childIds; +} + +QPlaceCategory QPlaceManagerEngineNokiaV2::category(const QString &categoryId) const +{ + return m_categoryTree.value(categoryId).category; +} + +QList QPlaceManagerEngineNokiaV2::childCategories(const QString &parentId) const +{ + QList results; + foreach (const QString &childId, m_categoryTree.value(parentId).childIds) + results.append(m_categoryTree.value(childId).category); + return results; +} + +QList QPlaceManagerEngineNokiaV2::locales() const +{ + return m_locales; +} + +void QPlaceManagerEngineNokiaV2::setLocales(const QList &locales) +{ + m_locales = locales; +} + +QPlaceIcon QPlaceManagerEngineNokiaV2::icon(const QString &remotePath, + const QList &categories) const +{ + QPlaceIcon icon; + QVariantMap params; + + QRegExp rx("(.*)(/icons/categories/.*)"); + + QString iconPrefix; + QString nokiaIcon; + if (rx.indexIn(remotePath) != -1 && !rx.cap(1).isEmpty() && !rx.cap(2).isEmpty()) { + iconPrefix = rx.cap(1); + nokiaIcon = rx.cap(2); + + if (QFile::exists(m_localDataPath + nokiaIcon)) + iconPrefix = QString::fromLatin1("file://") + m_localDataPath; + + params.insert(NokiaIcon, nokiaIcon); + params.insert(IconPrefix, iconPrefix); + + foreach (const QPlaceCategory &category, categories) { + if (category.icon().parameters().value(NokiaIcon) == nokiaIcon) { + params.insert(NokiaIconGenerated, true); + break; + } + } + } else { + QString path = remotePath + (!m_theme.isEmpty() + ? QLatin1Char('.') + m_theme : QString()); + params.insert(QPlaceIcon::SingleUrl, QUrl(path)); + + if (!nokiaIcon.isEmpty()) { + params.insert(NokiaIcon, nokiaIcon); + params.insert(IconPrefix, iconPrefix); + params.insert(NokiaIconGenerated, true); + } + } + + icon.setParameters(params); + + if (!icon.isEmpty()) + icon.setManager(manager()); + + return icon; +} + +QUrl QPlaceManagerEngineNokiaV2::constructIconUrl(const QPlaceIcon &icon, + const QSize &size) const +{ + Q_UNUSED(size) + QVariantMap params = icon.parameters(); + QString nokiaIcon = params.value(NokiaIcon).toString(); + + if (!nokiaIcon.isEmpty()) { + nokiaIcon.append(!m_theme.isEmpty() ? + QLatin1Char('.') + m_theme : QString()); + + if (params.contains(IconPrefix)) { + return QUrl(params.value(IconPrefix).toString() + + nokiaIcon); + } else { + return QUrl(QString::fromLatin1("file://") + m_localDataPath + + nokiaIcon); + } + } + + return QUrl(); +} + +void QPlaceManagerEngineNokiaV2::replyFinished() +{ + QPlaceReply *reply = qobject_cast(sender()); + if (reply) + emit finished(reply); +} + +void QPlaceManagerEngineNokiaV2::replyError(QPlaceReply::Error error_, const QString &errorString) +{ + QPlaceReply *reply = qobject_cast(sender()); + if (reply) + emit error(reply, error_, errorString); +} + +void QPlaceManagerEngineNokiaV2::categoryReplyFinished() +{ + QNetworkReply *reply = qobject_cast(sender()); + if (!reply) + return; + + QString categoryId; + + if (reply->error() == QNetworkReply::NoError) { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + if (!document.isObject()) { + if (m_categoryReply) { + QMetaObject::invokeMethod(m_categoryReply.data(), "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::ParseError), + Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, PARSE_ERROR))); + } + return; + } + + QJsonObject category = document.object(); + + categoryId = category.value(QStringLiteral("categoryId")).toString(); + if (m_tempTree.contains(categoryId)) { + PlaceCategoryNode node = m_tempTree.value(categoryId); + node.category.setName(category.value(QStringLiteral("name")).toString()); + node.category.setCategoryId(categoryId); + node.category.setIcon(icon(category.value(QStringLiteral("icon")).toString())); + + m_tempTree.insert(categoryId, node); + } + } else { + categoryId = m_categoryRequests.key(reply); + PlaceCategoryNode rootNode = m_tempTree.value(QString()); + rootNode.childIds.removeAll(categoryId); + m_tempTree.insert(QString(), rootNode); + m_tempTree.remove(categoryId); + } + + m_categoryRequests.remove(categoryId); + reply->deleteLater(); + + if (m_categoryRequests.isEmpty()) { + m_categoryTree = m_tempTree; + m_tempTree.clear(); + + if (m_categoryReply) + m_categoryReply.data()->emitFinished(); + } +} + +void QPlaceManagerEngineNokiaV2::categoryReplyError() +{ + if (m_categoryReply) { + QMetaObject::invokeMethod(m_categoryReply.data(), "setError", Qt::QueuedConnection, + Q_ARG(QPlaceReply::Error, QPlaceReply::CommunicationError), + Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, NETWORK_ERROR))); + } +} + +QNetworkReply *QPlaceManagerEngineNokiaV2::sendRequest(const QUrl &url) +{ + QUrlQuery queryItems(url); + queryItems.addQueryItem(QStringLiteral("app_id"), m_appId); + queryItems.addQueryItem(QStringLiteral("app_code"), m_appCode); + + QUrl requestUrl = url; + requestUrl.setQuery(queryItems); + + QNetworkRequest request; + request.setUrl(requestUrl); + + request.setRawHeader("Accept", "application/json"); + request.setRawHeader("Accept-Language", createLanguageString()); + + return m_manager->get(request); +} + +QByteArray QPlaceManagerEngineNokiaV2::createLanguageString() const +{ + QByteArray language; + + QList locales = m_locales; + if (locales.isEmpty()) + locales << QLocale(); + + foreach (const QLocale &loc, locales) { + language.append(loc.name().replace(2, 1, QLatin1Char('-')).toLatin1()); + language.append(", "); + } + language.chop(2); + + return language; +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.h b/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.h new file mode 100644 index 0000000..fe2e537 --- /dev/null +++ b/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEMANAGERENGINE_NOKIAV2_H +#define QPLACEMANAGERENGINE_NOKIAV2_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlaceContentReply; +class QNetworkReply; +class QNetworkAccessManager; +class QPlaceCategoriesReplyHere; +class QGeoNetworkAccessManager; +class QGeoUriProvider; + +struct PlaceCategoryNode +{ + QString parentId; + QStringList childIds; + QPlaceCategory category; +}; + +typedef QMap QPlaceCategoryTree; + +class QPlaceManagerEngineNokiaV2 : public QPlaceManagerEngine +{ + Q_OBJECT + +public: + QPlaceManagerEngineNokiaV2(QGeoNetworkAccessManager *networkManager, + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString); + ~QPlaceManagerEngineNokiaV2(); + + QPlaceDetailsReply *getPlaceDetails(const QString &placeId) Q_DECL_OVERRIDE; + + QPlaceContentReply *getPlaceContent(const QPlaceContentRequest &request) Q_DECL_OVERRIDE; + + QPlaceSearchReply *search(const QPlaceSearchRequest &query) Q_DECL_OVERRIDE; + + QPlaceSearchSuggestionReply *searchSuggestions(const QPlaceSearchRequest &query) Q_DECL_OVERRIDE; + + QPlaceIdReply *savePlace(const QPlace &place) Q_DECL_OVERRIDE; + QPlaceIdReply *removePlace(const QString &placeId) Q_DECL_OVERRIDE; + + QPlaceIdReply *saveCategory(const QPlaceCategory &category, const QString &parentId) Q_DECL_OVERRIDE; + QPlaceIdReply *removeCategory(const QString &categoryId) Q_DECL_OVERRIDE; + + QPlaceReply *initializeCategories() Q_DECL_OVERRIDE; + QString parentCategoryId(const QString &categoryId) const Q_DECL_OVERRIDE; + QStringList childCategoryIds(const QString &categoryId) const Q_DECL_OVERRIDE; + QPlaceCategory category(const QString &categoryId) const Q_DECL_OVERRIDE; + QList childCategories(const QString &parentId) const Q_DECL_OVERRIDE; + + QList locales() const Q_DECL_OVERRIDE; + void setLocales(const QList &locales) Q_DECL_OVERRIDE; + + QPlaceIcon icon(const QString &remotePath, + const QList &categories = QList()) const; + + QUrl constructIconUrl(const QPlaceIcon &icon, const QSize &size) const Q_DECL_OVERRIDE; + +private: + QNetworkReply *sendRequest(const QUrl &url); + QByteArray createLanguageString() const; + +private Q_SLOTS: + void replyFinished(); + void replyError(QPlaceReply::Error error_, const QString &errorString); + void categoryReplyFinished(); + void categoryReplyError(); + +private: + QGeoNetworkAccessManager *m_manager; + QGeoUriProvider *m_uriProvider; + + QList m_locales; + + QPlaceCategoryTree m_categoryTree; + QPlaceCategoryTree m_tempTree; + QHash m_restIdToIconHash; + + QPointer m_categoryReply; + QHash m_categoryRequests; + + QString m_appId; + QString m_appCode; + + QString m_localDataPath; + QString m_theme; +}; + +QT_END_NAMESPACE + +#endif // QPLACEMANAGERENGINE_NOKIAV2_H diff --git a/src/plugins/geoservices/nokia/resource.qrc b/src/plugins/geoservices/nokia/resource.qrc new file mode 100644 index 0000000..d48a6ca --- /dev/null +++ b/src/plugins/geoservices/nokia/resource.qrc @@ -0,0 +1,5 @@ + + + logo.png + + diff --git a/src/plugins/geoservices/nokia/uri_constants.cpp b/src/plugins/geoservices/nokia/uri_constants.cpp new file mode 100644 index 0000000..8a07532 --- /dev/null +++ b/src/plugins/geoservices/nokia/uri_constants.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "uri_constants.h" + +QT_BEGIN_NAMESPACE + +const QString ROUTING_HOST = QLatin1String("route.api.here.com"); +const QString GEOCODING_HOST = QLatin1String("loc.desktop.maps.svc.ovi.com"); +const QString GEOCODING_HOST_CN = QLatin1String("pr.geo.maps.svc.nokia.com.cn"); +const QString PLACES_HOST = QLatin1String("places.api.here.com"); +const QString PLACES_HOST_CN = QLatin1String("places.nlp.nokia.com.cn"); +const QString MAP_TILES_HOST = QLatin1String("1-4.base.maps.api.here.com"); +const QString MAP_TILES_HOST_AERIAL = QLatin1String("1-4.aerial.maps.api.here.com"); + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/nokia/uri_constants.h b/src/plugins/geoservices/nokia/uri_constants.h new file mode 100644 index 0000000..151a4aa --- /dev/null +++ b/src/plugins/geoservices/nokia/uri_constants.h @@ -0,0 +1,54 @@ + +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef URI_CONSTANTS_H +#define URI_CONSTANTS_H + +#include + +QT_BEGIN_NAMESPACE + +extern const QString ROUTING_HOST; +extern const QString GEOCODING_HOST; +extern const QString GEOCODING_HOST_CN; +extern const QString PLACES_HOST; +extern const QString PLACES_HOST_CN; +extern const QString MAP_TILES_HOST; +extern const QString MAP_TILES_HOST_AERIAL; + +QT_END_NAMESPACE + +#endif // URI_CONSTANTS_H diff --git a/src/plugins/geoservices/osm/osm.pro b/src/plugins/geoservices/osm/osm.pro new file mode 100644 index 0000000..56f4cb3 --- /dev/null +++ b/src/plugins/geoservices/osm/osm.pro @@ -0,0 +1,41 @@ +TARGET = qtgeoservices_osm + +QT += location-private positioning-private network + +HEADERS += \ + qgeoserviceproviderpluginosm.h \ + qgeotiledmappingmanagerengineosm.h \ + qgeotilefetcherosm.h \ + qgeomapreplyosm.h \ + qgeocodingmanagerengineosm.h \ + qgeocodereplyosm.h \ + qgeoroutingmanagerengineosm.h \ + qgeoroutereplyosm.h \ + qplacemanagerengineosm.h \ + qplacesearchreplyosm.h \ + qplacecategoriesreplyosm.h \ + qgeotiledmaposm.h \ + qgeotileproviderosm.h + +SOURCES += \ + qgeoserviceproviderpluginosm.cpp \ + qgeotiledmappingmanagerengineosm.cpp \ + qgeotilefetcherosm.cpp \ + qgeomapreplyosm.cpp \ + qgeocodingmanagerengineosm.cpp \ + qgeocodereplyosm.cpp \ + qgeoroutingmanagerengineosm.cpp \ + qgeoroutereplyosm.cpp \ + qplacemanagerengineosm.cpp \ + qplacesearchreplyosm.cpp \ + qplacecategoriesreplyosm.cpp \ + qgeotiledmaposm.cpp \ + qgeotileproviderosm.cpp + + +OTHER_FILES += \ + osm_plugin.json + +PLUGIN_TYPE = geoservices +PLUGIN_CLASS_NAME = QGeoServiceProviderFactoryOsm +load(qt_plugin) diff --git a/src/plugins/geoservices/osm/osm_plugin.json b/src/plugins/geoservices/osm/osm_plugin.json new file mode 100644 index 0000000..1aaf6f7 --- /dev/null +++ b/src/plugins/geoservices/osm/osm_plugin.json @@ -0,0 +1,13 @@ +{ + "Keys": ["osm"], + "Provider": "osm", + "Version": 100, + "Experimental": false, + "Features": [ + "OnlineMappingFeature", + "OnlineGeocodingFeature", + "ReverseGeocodingFeature", + "OnlineRoutingFeature", + "OnlinePlacesFeature" + ] +} diff --git a/src/plugins/geoservices/osm/qgeocodereplyosm.cpp b/src/plugins/geoservices/osm/qgeocodereplyosm.cpp new file mode 100644 index 0000000..15b1724 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeocodereplyosm.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocodereplyosm.h" + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QGeoCodeReplyOsm::QGeoCodeReplyOsm(QNetworkReply *reply, QObject *parent) +: QGeoCodeReply(parent), m_reply(reply) +{ + connect(m_reply, SIGNAL(finished()), this, SLOT(networkReplyFinished())); + connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(networkReplyError(QNetworkReply::NetworkError))); + + setLimit(1); + setOffset(0); +} + +QGeoCodeReplyOsm::~QGeoCodeReplyOsm() +{ + if (m_reply) + m_reply->deleteLater(); +} + +void QGeoCodeReplyOsm::abort() +{ + if (!m_reply) + return; + + m_reply->abort(); + + m_reply->deleteLater(); + m_reply = 0; +} + +static QGeoAddress parseAddressObject(const QJsonObject &object) +{ + QGeoAddress address; + address.setText(object.value(QStringLiteral("display_name")).toString()); + QJsonObject ao = object.value(QStringLiteral("address")).toObject(); + // setCountry + address.setCountry(ao.value(QStringLiteral("country")).toString()); + // setCountryCode + address.setCountryCode(ao.value(QStringLiteral("country_code")).toString()); + // setState + address.setState(ao.value(QStringLiteral("state")).toString()); + // setCity + if (ao.contains(QLatin1String("city"))) + address.setCity(ao.value(QStringLiteral("city")).toString()); + else if (ao.contains(QLatin1String("town"))) + address.setCity(ao.value(QLatin1String("town")).toString()); + else if (ao.contains(QLatin1String("village"))) + address.setCity(ao.value(QLatin1String("village")).toString()); + else + address.setCity(ao.value(QLatin1String("hamlet")).toString()); + // setDistrict + address.setDistrict(ao.value(QStringLiteral("suburb")).toString()); + // setPostalCode + address.setPostalCode(ao.value(QStringLiteral("postcode")).toString()); + // setStreet + address.setStreet(ao.value(QStringLiteral("road")).toString()); + return address; +} + +void QGeoCodeReplyOsm::networkReplyFinished() +{ + if (!m_reply) + return; + + if (m_reply->error() != QNetworkReply::NoError) + return; + + QList locations; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll()); + + if (document.isObject()) { + QJsonObject object = document.object(); + + QGeoCoordinate coordinate; + + coordinate.setLatitude(object.value(QStringLiteral("lat")).toString().toDouble()); + coordinate.setLongitude(object.value(QStringLiteral("lon")).toString().toDouble()); + + QGeoLocation location; + location.setCoordinate(coordinate); + location.setAddress(parseAddressObject(object)); + + locations.append(location); + + setLocations(locations); + } else if (document.isArray()) { + QJsonArray results = document.array(); + + for (int i = 0; i < results.count(); ++i) { + if (!results.at(i).isObject()) + continue; + + QJsonObject object = results.at(i).toObject(); + + QGeoCoordinate coordinate; + + coordinate.setLatitude(object.value(QStringLiteral("lat")).toString().toDouble()); + coordinate.setLongitude(object.value(QStringLiteral("lon")).toString().toDouble()); + + QGeoRectangle rectangle; + + if (object.contains(QStringLiteral("boundingbox"))) { + QJsonArray a = object.value(QStringLiteral("boundingbox")).toArray(); + if (a.count() == 4) { + rectangle.setTopLeft(QGeoCoordinate(a.at(1).toString().toDouble(), + a.at(2).toString().toDouble())); + rectangle.setBottomRight(QGeoCoordinate(a.at(0).toString().toDouble(), + a.at(3).toString().toDouble())); + } + } + + QGeoLocation location; + location.setCoordinate(coordinate); + location.setBoundingBox(rectangle); + location.setAddress(parseAddressObject(object)); + locations.append(location); + } + + } + + setLocations(locations); + setFinished(true); + + m_reply->deleteLater(); + m_reply = 0; +} + +void QGeoCodeReplyOsm::networkReplyError(QNetworkReply::NetworkError error) +{ + Q_UNUSED(error) + + if (!m_reply) + return; + + setError(QGeoCodeReply::CommunicationError, m_reply->errorString()); + + m_reply->deleteLater(); + m_reply = 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeocodereplyosm.h b/src/plugins/geoservices/osm/qgeocodereplyosm.h new file mode 100644 index 0000000..2772910 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeocodereplyosm.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODEREPLYOSM_H +#define QGEOCODEREPLYOSM_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoCodeReplyOsm : public QGeoCodeReply +{ + Q_OBJECT + +public: + explicit QGeoCodeReplyOsm(QNetworkReply *reply, QObject *parent = 0); + ~QGeoCodeReplyOsm(); + + void abort(); + +private Q_SLOTS: + void networkReplyFinished(); + void networkReplyError(QNetworkReply::NetworkError error); + +private: + QNetworkReply *m_reply; +}; + +QT_END_NAMESPACE + +#endif // QGEOCODEREPLYOSM_H diff --git a/src/plugins/geoservices/osm/qgeocodingmanagerengineosm.cpp b/src/plugins/geoservices/osm/qgeocodingmanagerengineosm.cpp new file mode 100644 index 0000000..693a80a --- /dev/null +++ b/src/plugins/geoservices/osm/qgeocodingmanagerengineosm.cpp @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocodingmanagerengineosm.h" +#include "qgeocodereplyosm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static QString addressToQuery(const QGeoAddress &address) +{ + return address.street() + QStringLiteral(", ") + + address.district() + QStringLiteral(", ") + + address.city() + QStringLiteral(", ") + + address.state() + QStringLiteral(", ") + + address.country(); +} + +static QString boundingBoxToLtrb(const QGeoRectangle &rect) +{ + return QString::number(rect.topLeft().longitude()) + QLatin1Char(',') + + QString::number(rect.topLeft().latitude()) + QLatin1Char(',') + + QString::number(rect.bottomRight().longitude()) + QLatin1Char(',') + + QString::number(rect.bottomRight().latitude()); +} + +QGeoCodingManagerEngineOsm::QGeoCodingManagerEngineOsm(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) +: QGeoCodingManagerEngine(parameters), m_networkManager(new QNetworkAccessManager(this)) +{ + if (parameters.contains(QStringLiteral("osm.useragent"))) + m_userAgent = parameters.value(QStringLiteral("osm.useragent")).toString().toLatin1(); + else + m_userAgent = "Qt Location based application"; + + if (parameters.contains(QStringLiteral("osm.geocoding.host"))) + m_urlPrefix = parameters.value(QStringLiteral("osm.geocoding.host")).toString().toLatin1(); + else + m_urlPrefix = QStringLiteral("http://nominatim.openstreetmap.org"); + + *error = QGeoServiceProvider::NoError; + errorString->clear(); +} + +QGeoCodingManagerEngineOsm::~QGeoCodingManagerEngineOsm() +{ +} + +QGeoCodeReply *QGeoCodingManagerEngineOsm::geocode(const QGeoAddress &address, const QGeoShape &bounds) +{ + return geocode(addressToQuery(address), -1, -1, bounds); +} + +QGeoCodeReply *QGeoCodingManagerEngineOsm::geocode(const QString &address, int limit, int offset, const QGeoShape &bounds) +{ + Q_UNUSED(offset) + + QNetworkRequest request; + request.setRawHeader("User-Agent", m_userAgent); + + QUrl url(QString("%1/search").arg(m_urlPrefix)); + QUrlQuery query; + query.addQueryItem(QStringLiteral("q"), address); + query.addQueryItem(QStringLiteral("format"), QStringLiteral("json")); + query.addQueryItem(QStringLiteral("accept-language"), locale().name().left(2)); + //query.addQueryItem(QStringLiteral("countrycodes"), QStringLiteral("au,jp")); + if (bounds.type() == QGeoShape::RectangleType) { + query.addQueryItem(QStringLiteral("viewbox"), boundingBoxToLtrb(bounds)); + query.addQueryItem(QStringLiteral("bounded"), QStringLiteral("1")); + } + query.addQueryItem(QStringLiteral("polygon_geojson"), QStringLiteral("1")); + query.addQueryItem(QStringLiteral("addressdetails"), QStringLiteral("1")); + if (limit != -1) + query.addQueryItem(QStringLiteral("limit"), QString::number(limit)); + + url.setQuery(query); + request.setUrl(url); + + QNetworkReply *reply = m_networkManager->get(request); + + QGeoCodeReplyOsm *geocodeReply = new QGeoCodeReplyOsm(reply, this); + + connect(geocodeReply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(geocodeReply, SIGNAL(error(QGeoCodeReply::Error,QString)), + this, SLOT(replyError(QGeoCodeReply::Error,QString))); + + return geocodeReply; +} + +QGeoCodeReply *QGeoCodingManagerEngineOsm::reverseGeocode(const QGeoCoordinate &coordinate, + const QGeoShape &bounds) +{ + Q_UNUSED(bounds) + + QNetworkRequest request; + request.setRawHeader("User-Agent", m_userAgent); + + QUrl url(QString("%1/reverse").arg(m_urlPrefix)); + QUrlQuery query; + query.addQueryItem(QStringLiteral("format"), QStringLiteral("json")); + query.addQueryItem(QStringLiteral("accept-language"), locale().name().left(2)); + query.addQueryItem(QStringLiteral("lat"), QString::number(coordinate.latitude())); + query.addQueryItem(QStringLiteral("lon"), QString::number(coordinate.longitude())); + query.addQueryItem(QStringLiteral("zoom"), QStringLiteral("18")); + query.addQueryItem(QStringLiteral("addressdetails"), QStringLiteral("1")); + + url.setQuery(query); + request.setUrl(url); + + QNetworkReply *reply = m_networkManager->get(request); + + QGeoCodeReplyOsm *geocodeReply = new QGeoCodeReplyOsm(reply, this); + + connect(geocodeReply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(geocodeReply, SIGNAL(error(QGeoCodeReply::Error,QString)), + this, SLOT(replyError(QGeoCodeReply::Error,QString))); + + return geocodeReply; +} + +void QGeoCodingManagerEngineOsm::replyFinished() +{ + QGeoCodeReply *reply = qobject_cast(sender()); + if (reply) + emit finished(reply); +} + +void QGeoCodingManagerEngineOsm::replyError(QGeoCodeReply::Error errorCode, const QString &errorString) +{ + QGeoCodeReply *reply = qobject_cast(sender()); + if (reply) + emit error(reply, errorCode, errorString); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeocodingmanagerengineosm.h b/src/plugins/geoservices/osm/qgeocodingmanagerengineosm.h new file mode 100644 index 0000000..4eea2cd --- /dev/null +++ b/src/plugins/geoservices/osm/qgeocodingmanagerengineosm.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODINGMANAGERENGINEOSM_H +#define QGEOCODINGMANAGERENGINEOSM_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QNetworkAccessManager; + +class QGeoCodingManagerEngineOsm : public QGeoCodingManagerEngine +{ + Q_OBJECT + +public: + QGeoCodingManagerEngineOsm(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, + QString *errorString); + ~QGeoCodingManagerEngineOsm(); + + QGeoCodeReply *geocode(const QGeoAddress &address, const QGeoShape &bounds) Q_DECL_OVERRIDE; + QGeoCodeReply *geocode(const QString &address, int limit, int offset, + const QGeoShape &bounds) Q_DECL_OVERRIDE; + QGeoCodeReply *reverseGeocode(const QGeoCoordinate &coordinate, + const QGeoShape &bounds) Q_DECL_OVERRIDE; + +private Q_SLOTS: + void replyFinished(); + void replyError(QGeoCodeReply::Error errorCode, const QString &errorString); + +private: + QNetworkAccessManager *m_networkManager; + QByteArray m_userAgent; + QString m_urlPrefix; +}; + +QT_END_NAMESPACE + +#endif // QGEOCODINGMANAGERENGINEOSM_H diff --git a/src/plugins/geoservices/osm/qgeomapreplyosm.cpp b/src/plugins/geoservices/osm/qgeomapreplyosm.cpp new file mode 100644 index 0000000..052ed35 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeomapreplyosm.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomapreplyosm.h" + +#include + +QGeoMapReplyOsm::QGeoMapReplyOsm(QNetworkReply *reply, + const QGeoTileSpec &spec, + const QString &imageFormat, + QObject *parent) +: QGeoTiledMapReply(spec, parent), m_reply(reply) +{ + connect(m_reply, SIGNAL(finished()), this, SLOT(networkReplyFinished())); + connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(networkReplyError(QNetworkReply::NetworkError))); + setMapImageFormat(imageFormat); +} + +QGeoMapReplyOsm::~QGeoMapReplyOsm() +{ + if (m_reply) { + m_reply->deleteLater(); + m_reply = 0; + } +} + +void QGeoMapReplyOsm::abort() +{ + if (!m_reply) + return; + + m_reply->abort(); +} + +QNetworkReply *QGeoMapReplyOsm::networkReply() const +{ + return m_reply; +} + +void QGeoMapReplyOsm::networkReplyFinished() +{ + if (!m_reply) + return; + + if (m_reply->error() != QNetworkReply::NoError) { + m_reply->deleteLater(); + m_reply = 0; + return; + } + + QByteArray a = m_reply->readAll(); + + setMapImageData(a); + setFinished(true); + + m_reply->deleteLater(); + m_reply = 0; +} + +void QGeoMapReplyOsm::networkReplyError(QNetworkReply::NetworkError error) +{ + if (!m_reply) + return; + + if (error != QNetworkReply::OperationCanceledError) + setError(QGeoTiledMapReply::CommunicationError, m_reply->errorString()); + + setFinished(true); + m_reply->deleteLater(); + m_reply = 0; +} diff --git a/src/plugins/geoservices/osm/qgeomapreplyosm.h b/src/plugins/geoservices/osm/qgeomapreplyosm.h new file mode 100644 index 0000000..804a0a2 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeomapreplyosm.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPREPLYOSM_H +#define QGEOMAPREPLYOSM_H + +#include "qgeotilefetcherosm.h" +#include "qgeotileproviderosm.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoMapReplyOsm : public QGeoTiledMapReply +{ + Q_OBJECT + +public: + QGeoMapReplyOsm(QNetworkReply *reply, const QGeoTileSpec &spec, const QString &imageFormat, QObject *parent = 0); + ~QGeoMapReplyOsm(); + + void abort(); + + QNetworkReply *networkReply() const; + +private Q_SLOTS: + void networkReplyFinished(); + void networkReplyError(QNetworkReply::NetworkError error); + +private: + QPointer m_reply; +}; + +QT_END_NAMESPACE + +#endif // QGEOMAPREPLYOSM_H diff --git a/src/plugins/geoservices/osm/qgeoroutereplyosm.cpp b/src/plugins/geoservices/osm/qgeoroutereplyosm.cpp new file mode 100644 index 0000000..da28317 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeoroutereplyosm.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroutereplyosm.h" +#include "qgeoroutingmanagerengineosm.h" + +QT_BEGIN_NAMESPACE + +QGeoRouteReplyOsm::QGeoRouteReplyOsm(QNetworkReply *reply, const QGeoRouteRequest &request, + QObject *parent) +: QGeoRouteReply(request, parent), m_reply(reply) +{ + connect(m_reply, SIGNAL(finished()), this, SLOT(networkReplyFinished())); + connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(networkReplyError(QNetworkReply::NetworkError))); +} + +QGeoRouteReplyOsm::~QGeoRouteReplyOsm() +{ + if (m_reply) + m_reply->deleteLater(); +} + +void QGeoRouteReplyOsm::abort() +{ + if (!m_reply) + return; + + m_reply->abort(); + + m_reply->deleteLater(); + m_reply = 0; +} + +void QGeoRouteReplyOsm::networkReplyFinished() +{ + if (!m_reply) + return; + + if (m_reply->error() != QNetworkReply::NoError) { + setError(QGeoRouteReply::CommunicationError, m_reply->errorString()); + m_reply->deleteLater(); + m_reply = 0; + return; + } + + if (m_reply->error() != QNetworkReply::NoError) { + setError(QGeoRouteReply::CommunicationError, m_reply->errorString()); + m_reply->deleteLater(); + m_reply = 0; + return; + } + + QGeoRoutingManagerEngineOsm *engine = qobject_cast(parent()); + const QGeoRouteParser *parser = engine->routeParser(); + + QList routes; + QString errorString; + QGeoRouteReply::Error error = parser->parseReply(routes, errorString, m_reply->readAll()); + + if (error == QGeoRouteReply::NoError) { + setRoutes(routes.mid(0,1)); // TODO QTBUG-56426 + // setError(QGeoRouteReply::NoError, status); // can't do this, or NoError is emitted and does damages + setFinished(true); + } else { + setError(error, errorString); + } + + m_reply->deleteLater(); + m_reply = 0; +} + +void QGeoRouteReplyOsm::networkReplyError(QNetworkReply::NetworkError error) +{ + Q_UNUSED(error) + + if (!m_reply) + return; + + setError(QGeoRouteReply::CommunicationError, m_reply->errorString()); + + m_reply->deleteLater(); + m_reply = 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeoroutereplyosm.h b/src/plugins/geoservices/osm/qgeoroutereplyosm.h new file mode 100644 index 0000000..8733c15 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeoroutereplyosm.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEREPLYOSM_H +#define QGEOROUTEREPLYOSM_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoRouteReplyOsm : public QGeoRouteReply +{ + Q_OBJECT + +public: + explicit QGeoRouteReplyOsm(QObject *parent = 0); + QGeoRouteReplyOsm(QNetworkReply *reply, const QGeoRouteRequest &request, QObject *parent = 0); + ~QGeoRouteReplyOsm(); + + void abort() Q_DECL_OVERRIDE; + +private Q_SLOTS: + void networkReplyFinished(); + void networkReplyError(QNetworkReply::NetworkError error); + +private: + QNetworkReply *m_reply; +}; + +QT_END_NAMESPACE + +#endif // QGEOROUTEREPLYOSM_H + diff --git a/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.cpp b/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.cpp new file mode 100644 index 0000000..12db22a --- /dev/null +++ b/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoroutingmanagerengineosm.h" +#include "qgeoroutereplyosm.h" +#include "QtLocation/private/qgeorouteparserosrmv4_p.h" +#include "QtLocation/private/qgeorouteparserosrmv5_p.h" + +#include + +#include + +QGeoRoutingManagerEngineOsm::QGeoRoutingManagerEngineOsm(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) +: QGeoRoutingManagerEngine(parameters), m_networkManager(new QNetworkAccessManager(this)) +{ + if (parameters.contains(QStringLiteral("osm.useragent"))) + m_userAgent = parameters.value(QStringLiteral("osm.useragent")).toString().toLatin1(); + else + m_userAgent = "Qt Location based application"; + + if (parameters.contains(QStringLiteral("osm.routing.host"))) + m_urlPrefix = parameters.value(QStringLiteral("osm.routing.host")).toString().toLatin1(); + else + m_urlPrefix = QStringLiteral("http://router.project-osrm.org/route/v1/driving/"); + // for v4 it was "http://router.project-osrm.org/viaroute" + + if (parameters.contains(QStringLiteral("osm.routing.apiversion")) + && (parameters.value(QStringLiteral("osm.routing.apiversion")).toString().toLatin1() == QByteArray("v4"))) + m_routeParser = new QGeoRouteParserOsrmV4(this); + else + m_routeParser = new QGeoRouteParserOsrmV5(this); + + *error = QGeoServiceProvider::NoError; + errorString->clear(); +} + +QGeoRoutingManagerEngineOsm::~QGeoRoutingManagerEngineOsm() +{ +} + +QGeoRouteReply* QGeoRoutingManagerEngineOsm::calculateRoute(const QGeoRouteRequest &request) +{ + QNetworkRequest networkRequest; + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent); + + networkRequest.setUrl(routeParser()->requestUrl(request, m_urlPrefix)); + + QNetworkReply *reply = m_networkManager->get(networkRequest); + + QGeoRouteReplyOsm *routeReply = new QGeoRouteReplyOsm(reply, request, this); + + connect(routeReply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(routeReply, SIGNAL(error(QGeoRouteReply::Error,QString)), + this, SLOT(replyError(QGeoRouteReply::Error,QString))); + + return routeReply; +} + +const QGeoRouteParser *QGeoRoutingManagerEngineOsm::routeParser() const +{ + return m_routeParser; +} + +void QGeoRoutingManagerEngineOsm::replyFinished() +{ + QGeoRouteReply *reply = qobject_cast(sender()); + if (reply) + emit finished(reply); +} + +void QGeoRoutingManagerEngineOsm::replyError(QGeoRouteReply::Error errorCode, + const QString &errorString) +{ + QGeoRouteReply *reply = qobject_cast(sender()); + if (reply) + emit error(reply, errorCode, errorString); +} diff --git a/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.h b/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.h new file mode 100644 index 0000000..8e2d7f5 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTINGMANAGERENGINEOSM_H +#define QGEOROUTINGMANAGERENGINEOSM_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QNetworkAccessManager; + +class QGeoRoutingManagerEngineOsm : public QGeoRoutingManagerEngine +{ + Q_OBJECT + +public: + QGeoRoutingManagerEngineOsm(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString); + ~QGeoRoutingManagerEngineOsm(); + + QGeoRouteReply *calculateRoute(const QGeoRouteRequest &request); + const QGeoRouteParser *routeParser() const; + +private Q_SLOTS: + void replyFinished(); + void replyError(QGeoRouteReply::Error errorCode, const QString &errorString); + +private: + QNetworkAccessManager *m_networkManager; + QGeoRouteParser *m_routeParser; + QByteArray m_userAgent; + QString m_urlPrefix; +}; + +QT_END_NAMESPACE + +#endif // QGEOROUTINGMANAGERENGINEOSM_H + diff --git a/src/plugins/geoservices/osm/qgeoserviceproviderpluginosm.cpp b/src/plugins/geoservices/osm/qgeoserviceproviderpluginosm.cpp new file mode 100644 index 0000000..b028a94 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeoserviceproviderpluginosm.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoserviceproviderpluginosm.h" +#include "qgeotiledmappingmanagerengineosm.h" +#include "qgeocodingmanagerengineosm.h" +#include "qgeoroutingmanagerengineosm.h" +#include "qplacemanagerengineosm.h" + +QT_BEGIN_NAMESPACE + +QGeoCodingManagerEngine *QGeoServiceProviderFactoryOsm::createGeocodingManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const +{ + return new QGeoCodingManagerEngineOsm(parameters, error, errorString); +} + +QGeoMappingManagerEngine *QGeoServiceProviderFactoryOsm::createMappingManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const +{ + return new QGeoTiledMappingManagerEngineOsm(parameters, error, errorString); +} + +QGeoRoutingManagerEngine *QGeoServiceProviderFactoryOsm::createRoutingManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const +{ + return new QGeoRoutingManagerEngineOsm(parameters, error, errorString); +} + +QPlaceManagerEngine *QGeoServiceProviderFactoryOsm::createPlaceManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const +{ + return new QPlaceManagerEngineOsm(parameters, error, errorString); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeoserviceproviderpluginosm.h b/src/plugins/geoservices/osm/qgeoserviceproviderpluginosm.h new file mode 100644 index 0000000..5e4ab3e --- /dev/null +++ b/src/plugins/geoservices/osm/qgeoserviceproviderpluginosm.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDER_OSM_H +#define QGEOSERVICEPROVIDER_OSM_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoServiceProviderFactoryOsm: public QObject, public QGeoServiceProviderFactory +{ + Q_OBJECT + Q_INTERFACES(QGeoServiceProviderFactory) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0" + FILE "osm_plugin.json") + +public: + QGeoCodingManagerEngine *createGeocodingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + QGeoMappingManagerEngine *createMappingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + QGeoRoutingManagerEngine *createRoutingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; + QPlaceManagerEngine *createPlaceManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/osm/qgeotiledmaposm.cpp b/src/plugins/geoservices/osm/qgeotiledmaposm.cpp new file mode 100644 index 0000000..f16e602 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeotiledmaposm.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotiledmaposm.h" +#include "qgeotiledmappingmanagerengineosm.h" +#include "qgeotilefetcherosm.h" + +#include + +QT_BEGIN_NAMESPACE + +QGeoTiledMapOsm::QGeoTiledMapOsm(QGeoTiledMappingManagerEngineOsm *engine, QObject *parent) +: QGeoTiledMap(engine, parent), m_mapId(-1), m_engine(engine) +{ + // Needed because evaluateCopyrights() is only triggered if visible tiles change in the map. + // It fails the first time it gets called if providers aren't resolved, and subsequent calls + // to it will be skipped until visible tiles change. + // This connection makes sure the copyrights are evaluated when copyright data are ready regardless + // of what tiles are visible. + connect(qobject_cast(engine->tileFetcher()), &QGeoTileFetcherOsm::providerDataUpdated, + this, &QGeoTiledMapOsm::onProviderDataUpdated); +} + +QGeoTiledMapOsm::~QGeoTiledMapOsm() +{ +} + +void QGeoTiledMapOsm::evaluateCopyrights(const QSet &visibleTiles) +{ + if (visibleTiles.isEmpty()) + return; + + QGeoTileSpec tile = *visibleTiles.constBegin(); + if (tile.mapId() == m_mapId) + return; + + int providerId = tile.mapId() - 1; + if (providerId < 0 || providerId >= m_engine->providers().size() || !m_engine->providers().at(providerId)->isValid()) + return; + + m_mapId = tile.mapId(); + onProviderDataUpdated(m_engine->providers().at(providerId)); +} + +void QGeoTiledMapOsm::onProviderDataUpdated(const QGeoTileProviderOsm *provider) +{ + if (!provider->isResolved() || provider->mapType().mapId() != m_mapId) + return; + QString copyRights; + const QString mapCopy = provider->mapCopyRight(); + const QString dataCopy = provider->dataCopyRight(); + const QString styleCopy = provider->styleCopyRight(); + if (!mapCopy.isEmpty()) { + copyRights += QStringLiteral("Map © "); + copyRights += mapCopy; + } + if (!dataCopy.isEmpty()) { + if (!copyRights.isEmpty()) + copyRights += QStringLiteral("
    "); + copyRights += QStringLiteral("Data © "); + copyRights += dataCopy; + } + if (!styleCopy.isEmpty()) { + if (!copyRights.isEmpty()) + copyRights += QStringLiteral("
    "); + copyRights += QStringLiteral("Style © "); + copyRights += styleCopy; + } + + if (copyRights.isEmpty() && provider->mapType().style() == QGeoMapType::CustomMap) + copyRights = m_engine->customCopyright(); + + emit copyrightsChanged(copyRights); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeotiledmaposm.h b/src/plugins/geoservices/osm/qgeotiledmaposm.h new file mode 100644 index 0000000..cfc7294 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeotiledmaposm.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEDMAPOSM_H +#define QGEOTILEDMAPOSM_H + +#include "qgeotileproviderosm.h" + +#include + +QT_BEGIN_NAMESPACE + +class QGeoTiledMappingManagerEngineOsm; +class QGeoTiledMapOsm: public QGeoTiledMap +{ + Q_OBJECT + +public: + QGeoTiledMapOsm(QGeoTiledMappingManagerEngineOsm *engine, QObject *parent = 0); + ~QGeoTiledMapOsm(); + +protected: + void evaluateCopyrights(const QSet &visibleTiles) Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void onProviderDataUpdated(const QGeoTileProviderOsm *provider); + +private: + int m_mapId; + QGeoTiledMappingManagerEngineOsm *m_engine; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp new file mode 100644 index 0000000..fba177f --- /dev/null +++ b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.cpp @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotiledmappingmanagerengineosm.h" +#include "qgeotilefetcherosm.h" +#include "qgeotiledmaposm.h" +#include "qgeotileproviderosm.h" + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +QGeoTiledMappingManagerEngineOsm::QGeoTiledMappingManagerEngineOsm(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) +: QGeoTiledMappingManagerEngine() +{ + QGeoCameraCapabilities cameraCaps; + cameraCaps.setMinimumZoomLevel(0.0); + cameraCaps.setMaximumZoomLevel(19.0); + setCameraCapabilities(cameraCaps); + + setTileSize(QSize(256, 256)); + + QNetworkAccessManager *nm = new QNetworkAccessManager(); + QString domain = QStringLiteral("http://maps-redirect.qt.io/osm/5.6/"); + if (parameters.contains(QStringLiteral("osm.mapping.providersrepository.address"))) { + QString customAddress = parameters.value(QStringLiteral("osm.mapping.providersrepository.address")).toString(); + // Allowing some malformed addresses ( containing the suffix "/osm/5.6/" + if (customAddress.indexOf(QStringLiteral(":")) < 0) // defaulting to http:// if no prefix is found + customAddress = QStringLiteral("http://") + customAddress; + if (customAddress[customAddress.length()-1] != QLatin1Char('/')) + customAddress += QLatin1Char('/'); + domain = customAddress; + } + + m_providers.push_back( + new QGeoTileProviderOsm(domain + "street", + nm, + QGeoMapType(QGeoMapType::StreetMap, tr("Street Map"), tr("Street map view in daylight mode"), false, false, 1), + QGeoTileProviderOsm::TileProvider(QStringLiteral("http://c.tile.openstreetmap.org/%z/%x/%y.png"), + QStringLiteral("png"), + QStringLiteral("
    OpenStreetMap.org"), + QStringLiteral("OpenStreetMap contributors") + ))); + m_providers.push_back( + new QGeoTileProviderOsm(domain + "satellite", + nm, + QGeoMapType(QGeoMapType::SatelliteMapDay, tr("Satellite Map"), tr("Satellite map view in daylight mode"), false, false, 2), + QGeoTileProviderOsm::TileProvider() + )); + m_providers.push_back( + new QGeoTileProviderOsm(domain + "cycle", + nm, + QGeoMapType(QGeoMapType::CycleMap, tr("Cycle Map"), tr("Cycle map view in daylight mode"), false, false, 3), + QGeoTileProviderOsm::TileProvider(QStringLiteral("http://c.tile.opencyclemap.org/cycle/%z/%x/%y.png"), + QStringLiteral("png"), + QStringLiteral("Thunderforest"), + QStringLiteral("OpenStreetMap contributors") + ))); + m_providers.push_back( + new QGeoTileProviderOsm(domain + "transit", + nm, + QGeoMapType(QGeoMapType::TransitMap, tr("Transit Map"), tr("Public transit map view in daylight mode"), false, false, 4), + QGeoTileProviderOsm::TileProvider(QStringLiteral("http://c.tile2.opencyclemap.org/transport/%z/%x/%y.png"), + QStringLiteral("png"), + QStringLiteral("Thunderforest"), + QStringLiteral("OpenStreetMap contributors") + ))); + m_providers.push_back( + new QGeoTileProviderOsm(domain + "night-transit", + nm, + QGeoMapType(QGeoMapType::TransitMap, tr("Night Transit Map"), tr("Public transit map view in night mode"), false, true, 5), + QGeoTileProviderOsm::TileProvider(QStringLiteral("http://a.tile.thunderforest.com/transport-dark/%z/%x/%y.png"), + QStringLiteral("png"), + QStringLiteral("Thunderforest"), + QStringLiteral("OpenStreetMap contributors") + ))); + m_providers.push_back( + new QGeoTileProviderOsm(domain + "terrain", + nm, + QGeoMapType(QGeoMapType::TerrainMap, tr("Terrain Map"), tr("Terrain map view"), false, false, 6), + QGeoTileProviderOsm::TileProvider(QStringLiteral("http://a.tile.thunderforest.com/landscape/%z/%x/%y.png"), + QStringLiteral("png"), + QStringLiteral("Thunderforest"), + QStringLiteral("OpenStreetMap contributors") + ))); + m_providers.push_back( + new QGeoTileProviderOsm(domain + "hiking", + nm, + QGeoMapType(QGeoMapType::PedestrianMap, tr("Hiking Map"), tr("Hiking map view"), false, false, 7), + QGeoTileProviderOsm::TileProvider(QStringLiteral("http://a.tile.thunderforest.com/outdoors/%z/%x/%y.png"), + QStringLiteral("png"), + QStringLiteral("Thunderforest"), + QStringLiteral("OpenStreetMap contributors") + ))); + + if (parameters.contains(QStringLiteral("osm.mapping.custom.host")) + || parameters.contains(QStringLiteral("osm.mapping.host"))) { + // Adding a custom provider + QString tmsServer; + if (parameters.contains(QStringLiteral("osm.mapping.host"))) + tmsServer = parameters.value(QStringLiteral("osm.mapping.host")).toString(); + if (parameters.contains(QStringLiteral("osm.mapping.custom.host"))) // priority to the new one + tmsServer = parameters.value(QStringLiteral("osm.mapping.custom.host")).toString(); + + QString mapCopyright; + QString dataCopyright; + if (parameters.contains(QStringLiteral("osm.mapping.custom.mapcopyright"))) + mapCopyright = parameters.value(QStringLiteral("osm.mapping.custom.mapcopyright")).toString(); + if (parameters.contains(QStringLiteral("osm.mapping.custom.datacopyright"))) + dataCopyright = parameters.value(QStringLiteral("osm.mapping.custom.datacopyright")).toString(); + + if (parameters.contains(QStringLiteral("osm.mapping.copyright"))) + m_customCopyright = parameters.value(QStringLiteral("osm.mapping.copyright")).toString(); + + m_providers.push_back( + new QGeoTileProviderOsm("", + nm, + QGeoMapType(QGeoMapType::CustomMap, tr("Custom URL Map"), tr("Custom url map view set via urlprefix parameter"), false, false, 8), + QGeoTileProviderOsm::TileProvider(tmsServer + QStringLiteral("%z/%x/%y.png"), + QStringLiteral("png"), + mapCopyright, + dataCopyright + ))); + + m_providers.last()->disableRedirection(); + } + + bool disableRedirection = false; + if (parameters.contains(QStringLiteral("osm.mapping.providersrepository.disabled"))) + disableRedirection = parameters.value(QStringLiteral("osm.mapping.providersrepository.disabled")).toBool(); + + foreach (QGeoTileProviderOsm * provider, m_providers) { + provider->setParent(this); + if (disableRedirection) + provider->disableRedirection(); + + connect(provider, &QGeoTileProviderOsm::resolutionFinished, + this, &QGeoTiledMappingManagerEngineOsm::onProviderResolutionFinished); + connect(provider, &QGeoTileProviderOsm::resolutionError, + this, &QGeoTiledMappingManagerEngineOsm::onProviderResolutionError); + } + updateMapTypes(); + + QGeoTileFetcherOsm *tileFetcher = new QGeoTileFetcherOsm(m_providers, nm, this); + if (parameters.contains(QStringLiteral("osm.useragent"))) { + const QByteArray ua = parameters.value(QStringLiteral("osm.useragent")).toString().toLatin1(); + tileFetcher->setUserAgent(ua); + } + + + setTileFetcher(tileFetcher); + + QAbstractGeoTileCache *tileCache = new QGeoFileTileCache(QAbstractGeoTileCache::baseCacheDirectory() + QStringLiteral("osm")); + // 50mb of disk cache by default to minimize n. of accesses to public OSM servers + tileCache->setMaxDiskUsage(50 * 1024 * 1024); + setTileCache(tileCache); + + *error = QGeoServiceProvider::NoError; + errorString->clear(); +} + +QGeoTiledMappingManagerEngineOsm::~QGeoTiledMappingManagerEngineOsm() +{ +} + +QGeoMap *QGeoTiledMappingManagerEngineOsm::createMap() +{ + return new QGeoTiledMapOsm(this); +} + +const QVector &QGeoTiledMappingManagerEngineOsm::providers() +{ + return m_providers; +} + +QString QGeoTiledMappingManagerEngineOsm::customCopyright() const +{ + return m_customCopyright; +} + +void QGeoTiledMappingManagerEngineOsm::onProviderResolutionFinished(const QGeoTileProviderOsm *provider) +{ + if (!provider->isResolved()) + return; + updateMapTypes(); +} + +void QGeoTiledMappingManagerEngineOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error) +{ + Q_UNUSED(error) + if (!provider->isResolved()) + return; + updateMapTypes(); +} + +void QGeoTiledMappingManagerEngineOsm::updateMapTypes() +{ + QList mapTypes; + foreach (QGeoTileProviderOsm * provider, m_providers) { + // assume provider are ok until they have been resolved invalid + if (!provider->isResolved() || provider->isValid()) + mapTypes << provider->mapType(); + } + const QList currentlySupportedMapTypes = supportedMapTypes(); + if (currentlySupportedMapTypes != mapTypes) + // See map type implementations in QGeoTiledMapOsm and QGeoTileFetcherOsm. + setSupportedMapTypes(mapTypes); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.h b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.h new file mode 100644 index 0000000..247e437 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeotiledmappingmanagerengineosm.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEDMAPPINGMANAGERENGINEOSM_H +#define QGEOTILEDMAPPINGMANAGERENGINEOSM_H + +#include "qgeotileproviderosm.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoTiledMappingManagerEngineOsm : public QGeoTiledMappingManagerEngine +{ + Q_OBJECT + + friend class QGeoTiledMapOsm; +public: + QGeoTiledMappingManagerEngineOsm(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString); + ~QGeoTiledMappingManagerEngineOsm(); + + QGeoMap *createMap(); + const QVector &providers(); + QString customCopyright() const; + +protected Q_SLOTS: + void onProviderResolutionFinished(const QGeoTileProviderOsm *provider); + void onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error); + +protected: + void updateMapTypes(); + +private: + QVector m_providers; + QString m_customCopyright; +}; + +QT_END_NAMESPACE + +#endif // QGEOTILEDMAPPINGMANAGERENGINEOSM_H diff --git a/src/plugins/geoservices/osm/qgeotilefetcherosm.cpp b/src/plugins/geoservices/osm/qgeotilefetcherosm.cpp new file mode 100644 index 0000000..9862141 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeotilefetcherosm.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotilefetcherosm.h" +#include "qgeomapreplyosm.h" + +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +static bool providersResolved(const QVector &providers) +{ + foreach (const QGeoTileProviderOsm *provider, providers) + if (!provider->isResolved()) + return false; + return true; +} + +QGeoTileFetcherOsm::QGeoTileFetcherOsm(const QVector &providers, + QNetworkAccessManager *nm, + QObject *parent) +: QGeoTileFetcher(parent), m_userAgent("Qt Location based application"), + m_providers(providers), m_nm(nm), m_ready(true) +{ + m_nm->setParent(this); + foreach (QGeoTileProviderOsm *provider, m_providers) { + if (!provider->isResolved()) { + m_ready = false; + connect(provider, &QGeoTileProviderOsm::resolutionFinished, + this, &QGeoTileFetcherOsm::onProviderResolutionFinished); + connect(provider, &QGeoTileProviderOsm::resolutionError, + this, &QGeoTileFetcherOsm::onProviderResolutionError); + provider->resolveProvider(); + } + } + if (m_ready) + readyUpdated(); +} + +void QGeoTileFetcherOsm::setUserAgent(const QByteArray &userAgent) +{ + m_userAgent = userAgent; +} + +bool QGeoTileFetcherOsm::initialized() const +{ + if (!m_ready) { + foreach (QGeoTileProviderOsm *provider, m_providers) + if (!provider->isResolved()) + provider->resolveProvider(); + } + return m_ready; +} + +void QGeoTileFetcherOsm::onProviderResolutionFinished(const QGeoTileProviderOsm *provider) +{ + if ((m_ready = providersResolved(m_providers))) { + qWarning("QGeoTileFetcherOsm: all providers resolved"); + readyUpdated(); + } + emit providerDataUpdated(provider); +} + +void QGeoTileFetcherOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error) +{ + Q_UNUSED(error) + if ((m_ready = providersResolved(m_providers))) { + qWarning("QGeoTileFetcherOsm: all providers resolved"); + readyUpdated(); + } + emit providerDataUpdated(provider); +} + +QGeoTiledMapReply *QGeoTileFetcherOsm::getTileImage(const QGeoTileSpec &spec) +{ + int id = spec.mapId(); + if (id < 1 || id > m_providers.size()) { + qWarning("Unknown map id %d\n", spec.mapId()); + if (m_providers.isEmpty()) + return Q_NULLPTR; + else + id = 1; + } + id -= 1; // TODO: make OSM map ids start from 0. + + const QUrl url = m_providers[id]->tileAddress(spec.x(), spec.y(), spec.zoom()); + QNetworkRequest request; + request.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent); + request.setUrl(url); + QNetworkReply *reply = m_nm->get(request); + return new QGeoMapReplyOsm(reply, spec, m_providers[id]->format()); +} + +void QGeoTileFetcherOsm::readyUpdated() +{ + updateTileRequests(QSet(), QSet()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeotilefetcherosm.h b/src/plugins/geoservices/osm/qgeotilefetcherosm.h new file mode 100644 index 0000000..8d69cc5 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeotilefetcherosm.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEFETCHEROSM_H +#define QGEOTILEFETCHEROSM_H + +#include "qgeotileproviderosm.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QNetworkAccessManager; + +class QGeoTileFetcherOsm : public QGeoTileFetcher +{ + Q_OBJECT + + friend class QGeoMapReplyOsm; + friend class QGeoTiledMappingManagerEngineOsm; +public: + QGeoTileFetcherOsm(const QVector &providers, + QNetworkAccessManager *nm, + QObject *parent = 0); + + void setUserAgent(const QByteArray &userAgent); + +Q_SIGNALS: + void providerDataUpdated(const QGeoTileProviderOsm *provider); + +protected: + bool initialized() const Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void onProviderResolutionFinished(const QGeoTileProviderOsm *provider); + void onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error); + +private: + QGeoTiledMapReply *getTileImage(const QGeoTileSpec &spec); + void readyUpdated(); + + QByteArray m_userAgent; + QVector m_providers; + QNetworkAccessManager *m_nm; + bool m_ready; +}; + +QT_END_NAMESPACE + +#endif // QGEOTILEFETCHEROSM_H + diff --git a/src/plugins/geoservices/osm/qgeotileproviderosm.cpp b/src/plugins/geoservices/osm/qgeotileproviderosm.cpp new file mode 100644 index 0000000..3d46a42 --- /dev/null +++ b/src/plugins/geoservices/osm/qgeotileproviderosm.cpp @@ -0,0 +1,318 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotileproviderosm.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static const int maxValidZoom = 30; + +QGeoTileProviderOsm::QGeoTileProviderOsm(const QString &urlRedir, + QNetworkAccessManager *nm, + const QGeoMapType &mapType, + const QGeoTileProviderOsm::TileProvider &providerFallback) + : m_nm(nm), m_urlRedirector(urlRedir), + m_providerFallback(providerFallback), + m_mapType(mapType), m_status(Idle) +{ + if (!m_urlRedirector.isValid()) + disableRedirection(); +} + +QGeoTileProviderOsm::~QGeoTileProviderOsm() +{ + +} + +void QGeoTileProviderOsm::resolveProvider() +{ + switch (m_status) { + case Resolving: + case Invalid: + case Valid: + return; + case Idle: + m_status = Resolving; + break; + } + + QNetworkRequest request; + request.setHeader(QNetworkRequest::UserAgentHeader, QByteArrayLiteral("QGeoTileFetcherOsm")); + request.setUrl(m_urlRedirector); + QNetworkReply *reply = m_nm->get(request); + connect(reply, SIGNAL(finished()), this, SLOT(onNetworkReplyFinished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(onNetworkReplyError(QNetworkReply::NetworkError))); +} + +void QGeoTileProviderOsm::disableRedirection() +{ + m_status = Invalid; + m_provider.m_valid = false; +} + +void QGeoTileProviderOsm::handleError(QNetworkReply::NetworkError error) +{ + switch (error) { + case QNetworkReply::ConnectionRefusedError: + case QNetworkReply::TooManyRedirectsError: + case QNetworkReply::InsecureRedirectError: + case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentOperationNotPermittedError: + case QNetworkReply::ContentNotFoundError: + case QNetworkReply::AuthenticationRequiredError: + case QNetworkReply::ContentGoneError: + case QNetworkReply::OperationNotImplementedError: + case QNetworkReply::ServiceUnavailableError: + // Errors we don't expect to recover from in the near future, which + // prevent accessing the redirection info but not the actual providers. + m_status = Invalid; + default: + break; + } +} + +QUrl QGeoTileProviderOsm::tileAddress(int x, int y, int z) const +{ + if (m_provider.isValid()) + return m_provider.tileAddress(x,y,z); + if (m_providerFallback.isValid()) + return m_providerFallback.tileAddress(x,y,z); + return QUrl(); +} + +QString QGeoTileProviderOsm::mapCopyRight() const +{ + if (m_provider.isValid()) + return m_provider.mapCopyRight(); + if (m_providerFallback.isValid()) + return m_providerFallback.mapCopyRight(); + return QString(); +} + +QString QGeoTileProviderOsm::dataCopyRight() const +{ + if (m_provider.isValid()) + return m_provider.dataCopyRight(); + if (m_providerFallback.isValid()) + return m_providerFallback.dataCopyRight(); + return QString(); +} + +QString QGeoTileProviderOsm::styleCopyRight() const +{ + if (m_provider.isValid()) + return m_provider.styleCopyRight(); + if (m_providerFallback.isValid()) + return m_providerFallback.styleCopyRight(); + return QString(); +} + +QString QGeoTileProviderOsm::format() const +{ + if (m_provider.isValid()) + return m_provider.format(); + if (m_providerFallback.isValid()) + return m_providerFallback.format(); + return QString(); +} + +const QGeoMapType &QGeoTileProviderOsm::mapType() const +{ + return m_mapType; +} + +bool QGeoTileProviderOsm::isValid() const +{ + return (m_provider.isValid() || m_providerFallback.isValid()); +} + +bool QGeoTileProviderOsm::isResolved() const +{ + return (m_status == Valid || m_status == Invalid); +} + +void QGeoTileProviderOsm::onNetworkReplyFinished() +{ + QNetworkReply *reply = static_cast(sender()); + reply->deleteLater(); + + switch (m_status) { + case Resolving: + m_status = Idle; + case Idle: // should not happen + case Invalid: // should not happen + break; + case Valid: // should not happen + return; + } + + if (reply->error() != QNetworkReply::NoError) { + handleError(reply->error()); + if (m_status == Invalid) + emit resolutionError(this, reply->error()); + return; + } + m_status = Invalid; + + /* + * The content of a provider information file must be in JSON format, containing + * (as of Qt 5.6.2) the following fields: + * + * { + * "Enabled" : bool, (optional) + * "UrlTemplate" : "", (mandatory) + * "ImageFormat" : "", (mandatory) + * "MapCopyRight" : "", (mandatory) + * "DataCopyRight" : "", (mandatory) + * "StyleCopyRight" : "", (optional) + * "MinimumZoomLevel" : , (optional) + * "MaximumZoomLevel" : , (optional) + * } + * + * Enabled is optional, and allows to temporarily disable a tile provider if it becomes + * unavailable, without making the osm plugin fire requests to it. Default is true. + * + * MinimumZoomLevel and MaximumZoomLevel are also optional, and allow to prevent invalid tile + * requests to the providers, if they do not support the specific ZL. Default is 0 and 19, + * respectively. + * + * is required, and is the tile url template, with %x, %y and %z as + * placeholders for the actual parameters. + * Example: + * http://localhost:8080/maps/%z/%x/%y.png + * + * is required, and is the format of the tile. + * Examples: + * "png", "jpg" + * + * is required and is the string that will be displayed in the "Map (c)" part + * of the on-screen copyright notice. Can be an empty string. + * Example: + * "MapQuest" + * + * is required and is the string that will be displayed in the "Data (c)" part + * of the on-screen copyright notice. Can be an empty string. + * Example: + * "OpenStreetMap contributors" + * + * is optional and is the string that will be displayed in the optional "Style (c)" part + * of the on-screen copyright notice. + */ + + QJsonParseError error; + QJsonDocument d = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "QGeoTileProviderOsm: Error parsing redirection data: "<(sender())->deleteLater(); + if (m_status == Invalid) + emit resolutionError(this, error); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qgeotileproviderosm.h b/src/plugins/geoservices/osm/qgeotileproviderosm.h new file mode 100644 index 0000000..f396b3b --- /dev/null +++ b/src/plugins/geoservices/osm/qgeotileproviderosm.h @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTILEPROVIDEROSM_H +#define QTILEPROVIDEROSM_H + +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoTileProviderOsm: public QObject +{ + Q_OBJECT + + friend class QGeoTileFetcherOsm; + friend class QGeoMapReplyOsm; + friend class QGeoTiledMappingManagerEngineOsm; +public: + struct TileProvider { + + static inline void sort2(int &a, int &b) + { + if (a > b) { + int temp=a; + a=b; + b=temp; + } + } + + TileProvider() : m_valid(false) + { + + } + + TileProvider(const QString &urlTemplate, + const QString &format, + const QString ©RightMap, + const QString ©RightData, + int minimumZoomLevel = 0, + int maximumZoomLevel = 19) : m_valid(false) + { + if (urlTemplate.isEmpty()) + return; + m_urlTemplate = urlTemplate; + + if (format.isEmpty()) + return; + m_format = format; + + m_copyRightMap = copyRightMap; + m_copyRightData = copyRightData; + + if (minimumZoomLevel < 0 || minimumZoomLevel > 30) + return; + m_minimumZoomLevel = minimumZoomLevel; + + if (maximumZoomLevel < 0 || maximumZoomLevel > 30 || maximumZoomLevel < minimumZoomLevel) + return; + m_maximumZoomLevel = maximumZoomLevel; + + // Currently supporting only %x, %y and &z + int offset[3]; + offset[0] = m_urlTemplate.indexOf(QLatin1String("%x")); + if (offset[0] < 0) + return; + + offset[1] = m_urlTemplate.indexOf(QLatin1String("%y")); + if (offset[1] < 0) + return; + + offset[2] = m_urlTemplate.indexOf(QLatin1String("%z")); + if (offset[2] < 0) + return; + + int sortedOffsets[3]; + std::copy(offset, offset + 3, sortedOffsets); + sort2(sortedOffsets[0] ,sortedOffsets[1]); + sort2(sortedOffsets[1] ,sortedOffsets[2]); + sort2(sortedOffsets[0] ,sortedOffsets[1]); + + int min = sortedOffsets[0]; + int max = sortedOffsets[2]; + int mid = sortedOffsets[1]; + + // Initing LUT + for (int i=0; i<3; i++) { + if (offset[0] == sortedOffsets[i]) + paramsLUT[i] = 0; + else if (offset[1] == sortedOffsets[i]) + paramsLUT[i] = 1; + else + paramsLUT[i] = 2; + } + + m_urlPrefix = m_urlTemplate.mid(0 , min); + m_urlSuffix = m_urlTemplate.mid(max + 2, m_urlTemplate.size() - max - 2); + + paramsSep[0] = m_urlTemplate.mid(min + 2, mid - min - 2); + paramsSep[1] = m_urlTemplate.mid(mid + 2, max - mid - 2); + m_valid = true; + } + + ~TileProvider() + { + } + + inline bool isValid() const + { + return m_valid; + } + + inline QString mapCopyRight() const + { + return m_copyRightMap; + } + + inline QString dataCopyRight() const + { + return m_copyRightData; + } + + inline QString styleCopyRight() const + { + return m_copyRightStyle; + } + + inline QString format() const + { + return m_format; + } + + // Optional properties, not needed to construct a provider + void setStyleCopyRight(const QString ©right) + { + m_copyRightStyle = copyright; + } + + QUrl tileAddress(int x, int y, int z) const + { + if (z < m_minimumZoomLevel || z > m_maximumZoomLevel) + return QUrl(); + int params[3] = { x, y, z}; + QString url; + url += m_urlPrefix; + url += QString::number(params[paramsLUT[0]]); + url += paramsSep[0]; + url += QString::number(params[paramsLUT[1]]); + url += paramsSep[1]; + url += QString::number(params[paramsLUT[2]]); + url += m_urlSuffix; + return QUrl(url); + } + + bool m_valid; + QString m_urlTemplate; + QString m_format; + QString m_copyRightMap; + QString m_copyRightData; + QString m_copyRightStyle; + QString m_urlPrefix; + QString m_urlSuffix; + int m_minimumZoomLevel; + int m_maximumZoomLevel; + + int paramsLUT[3]; //Lookup table to handle possibly shuffled x,y,z + QString paramsSep[2]; // what goes in between %x, %y and %z + }; + + enum Status {Idle, + Resolving, + Valid, + Invalid }; + + QGeoTileProviderOsm(const QString &urlRedir, + QNetworkAccessManager *nm, + const QGeoMapType &mapType, + const TileProvider &providerFallback); + + ~QGeoTileProviderOsm(); + + + + QUrl tileAddress(int x, int y, int z) const; + QString mapCopyRight() const; + QString dataCopyRight() const; + QString styleCopyRight() const; + QString format() const; + const QGeoMapType &mapType() const; + bool isValid() const; + bool isResolved() const; + +Q_SIGNALS: + void resolutionFinished(const QGeoTileProviderOsm *provider); + void resolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error); + +public Q_SLOTS: + void onNetworkReplyFinished(); + void onNetworkReplyError(QNetworkReply::NetworkError error); + void resolveProvider(); + +protected: + void disableRedirection(); + void handleError(QNetworkReply::NetworkError error); + + QNetworkAccessManager *m_nm; + QUrl m_urlRedirector; // The URL from where to fetch the URL template + TileProvider m_provider; + TileProvider m_providerFallback; + QGeoMapType m_mapType; + Status m_status; + QTimer m_retryTimer; +}; + +QT_END_NAMESPACE + +#endif // QTILEPROVIDEROSM_H diff --git a/src/plugins/geoservices/osm/qplacecategoriesreplyosm.cpp b/src/plugins/geoservices/osm/qplacecategoriesreplyosm.cpp new file mode 100644 index 0000000..fe506bb --- /dev/null +++ b/src/plugins/geoservices/osm/qplacecategoriesreplyosm.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacecategoriesreplyosm.h" + +QT_BEGIN_NAMESPACE + +QPlaceCategoriesReplyOsm::QPlaceCategoriesReplyOsm(QObject *parent) +: QPlaceReply(parent) +{ +} + +QPlaceCategoriesReplyOsm::~QPlaceCategoriesReplyOsm() +{ +} + +void QPlaceCategoriesReplyOsm::emitFinished() +{ + setFinished(true); + emit finished(); +} + +void QPlaceCategoriesReplyOsm::setError(QPlaceReply::Error errorCode, const QString &errorString) +{ + QPlaceReply::setError(errorCode, errorString); + emit error(errorCode, errorString); +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qplacecategoriesreplyosm.h b/src/plugins/geoservices/osm/qplacecategoriesreplyosm.h new file mode 100644 index 0000000..af2919d --- /dev/null +++ b/src/plugins/geoservices/osm/qplacecategoriesreplyosm.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACECATEGORIESREPLYOSM_H +#define QPLACECATEGORIESREPLYOSM_H + +#include + +QT_BEGIN_NAMESPACE + +class QPlaceCategoriesReplyOsm : public QPlaceReply +{ + Q_OBJECT + +public: + explicit QPlaceCategoriesReplyOsm(QObject *parent = 0); + ~QPlaceCategoriesReplyOsm(); + + void emitFinished(); + void setError(QPlaceReply::Error errorCode, const QString &errorString); +}; + +QT_END_NAMESPACE + +#endif // QPLACECATEGORIESREPLYOSM_H diff --git a/src/plugins/geoservices/osm/qplacemanagerengineosm.cpp b/src/plugins/geoservices/osm/qplacemanagerengineosm.cpp new file mode 100644 index 0000000..719769f --- /dev/null +++ b/src/plugins/geoservices/osm/qplacemanagerengineosm.cpp @@ -0,0 +1,365 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtFoo module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacemanagerengineosm.h" +#include "qplacesearchreplyosm.h" +#include "qplacecategoriesreplyosm.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +QString SpecialPhrasesBaseUrl = QStringLiteral("http://wiki.openstreetmap.org/wiki/Special:Export/Nominatim/Special_Phrases/"); + +QString nameForTagKey(const QString &tagKey) +{ + if (tagKey == QLatin1String("aeroway")) + return QPlaceManagerEngineOsm::tr("Aeroway"); + else if (tagKey == QLatin1String("amenity")) + return QPlaceManagerEngineOsm::tr("Amenity"); + else if (tagKey == QLatin1String("building")) + return QPlaceManagerEngineOsm::tr("Building"); + else if (tagKey == QLatin1String("highway")) + return QPlaceManagerEngineOsm::tr("Highway"); + else if (tagKey == QLatin1String("historic")) + return QPlaceManagerEngineOsm::tr("Historic"); + else if (tagKey == QLatin1String("landuse")) + return QPlaceManagerEngineOsm::tr("Land use"); + else if (tagKey == QLatin1String("leisure")) + return QPlaceManagerEngineOsm::tr("Leisure"); + else if (tagKey == QLatin1String("man_made")) + return QPlaceManagerEngineOsm::tr("Man made"); + else if (tagKey == QLatin1String("natural")) + return QPlaceManagerEngineOsm::tr("Natural"); + else if (tagKey == QLatin1String("place")) + return QPlaceManagerEngineOsm::tr("Place"); + else if (tagKey == QLatin1String("railway")) + return QPlaceManagerEngineOsm::tr("Railway"); + else if (tagKey == QLatin1String("shop")) + return QPlaceManagerEngineOsm::tr("Shop"); + else if (tagKey == QLatin1String("tourism")) + return QPlaceManagerEngineOsm::tr("Tourism"); + else if (tagKey == QLatin1String("waterway")) + return QPlaceManagerEngineOsm::tr("Waterway"); + else + return tagKey; +} + +} + +QPlaceManagerEngineOsm::QPlaceManagerEngineOsm(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) +: QPlaceManagerEngine(parameters), m_networkManager(new QNetworkAccessManager(this)), + m_categoriesReply(0) +{ + if (parameters.contains(QStringLiteral("osm.useragent"))) + m_userAgent = parameters.value(QStringLiteral("osm.useragent")).toString().toLatin1(); + else + m_userAgent = "Qt Location based application"; + + if (parameters.contains(QStringLiteral("osm.places.host"))) + m_urlPrefix = parameters.value(QStringLiteral("osm.places.host")).toString(); + else + m_urlPrefix = QStringLiteral("http://nominatim.openstreetmap.org/search"); + + *error = QGeoServiceProvider::NoError; + errorString->clear(); +} + +QPlaceManagerEngineOsm::~QPlaceManagerEngineOsm() +{ +} + +QPlaceSearchReply *QPlaceManagerEngineOsm::search(const QPlaceSearchRequest &request) +{ + bool unsupported = false; + + // Only public visibility supported + unsupported |= request.visibilityScope() != QLocation::UnspecifiedVisibility && + request.visibilityScope() != QLocation::PublicVisibility; + unsupported |= request.searchTerm().isEmpty() && request.categories().isEmpty(); + + if (unsupported) + return QPlaceManagerEngine::search(request); + + QUrlQuery queryItems; + + queryItems.addQueryItem(QStringLiteral("format"), QStringLiteral("jsonv2")); + + //queryItems.addQueryItem(QStringLiteral("accept-language"), QStringLiteral("en")); + + QGeoRectangle boundingBox; + QGeoShape searchArea = request.searchArea(); + switch (searchArea.type()) { + case QGeoShape::CircleType: { + QGeoCircle c(searchArea); + qreal radius = c.radius(); + if (radius < 0) + radius = 50000; + + boundingBox = QGeoRectangle(c.center().atDistanceAndAzimuth(radius, -45), + c.center().atDistanceAndAzimuth(radius, 135)); + break; + } + case QGeoShape::RectangleType: + boundingBox = searchArea; + break; + default: + ; + } + + if (!boundingBox.isEmpty()) { + queryItems.addQueryItem(QStringLiteral("bounded"), QStringLiteral("1")); + QString coordinates; + coordinates = QString::number(boundingBox.topLeft().longitude()) + QLatin1Char(',') + + QString::number(boundingBox.topLeft().latitude()) + QLatin1Char(',') + + QString::number(boundingBox.bottomRight().longitude()) + QLatin1Char(',') + + QString::number(boundingBox.bottomRight().latitude()); + queryItems.addQueryItem(QStringLiteral("viewbox"), coordinates); + } + + QStringList queryParts; + if (!request.searchTerm().isEmpty()) + queryParts.append(request.searchTerm()); + + foreach (const QPlaceCategory &category, request.categories()) { + QString id = category.categoryId(); + int index = id.indexOf(QLatin1Char('=')); + if (index != -1) + id = id.mid(index+1); + queryParts.append(QLatin1Char('[') + id + QLatin1Char(']')); + } + + queryItems.addQueryItem(QStringLiteral("q"), queryParts.join(QLatin1Char('+'))); + + QVariantMap parameters = request.searchContext().toMap(); + + QStringList placeIds = parameters.value(QStringLiteral("ExcludePlaceIds")).toStringList(); + if (!placeIds.isEmpty()) + queryItems.addQueryItem(QStringLiteral("exclude_place_ids"), placeIds.join(QLatin1Char(','))); + + queryItems.addQueryItem(QStringLiteral("addressdetails"), QStringLiteral("1")); + + QUrl requestUrl(m_urlPrefix); + requestUrl.setQuery(queryItems); + + QNetworkReply *networkReply = m_networkManager->get(QNetworkRequest(requestUrl)); + + QPlaceSearchReplyOsm *reply = new QPlaceSearchReplyOsm(request, networkReply, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + return reply; +} + +QPlaceReply *QPlaceManagerEngineOsm::initializeCategories() +{ + // Only fetch categories once + if (m_categories.isEmpty() && !m_categoriesReply) { + m_categoryLocales = m_locales; + m_categoryLocales.append(QLocale(QLocale::English)); + fetchNextCategoryLocale(); + } + + QPlaceCategoriesReplyOsm *reply = new QPlaceCategoriesReplyOsm(this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + // TODO delayed finished() emission + if (!m_categories.isEmpty()) + reply->emitFinished(); + + m_pendingCategoriesReply.append(reply); + return reply; +} + +QString QPlaceManagerEngineOsm::parentCategoryId(const QString &categoryId) const +{ + Q_UNUSED(categoryId) + + // Only a two category levels + return QString(); +} + +QStringList QPlaceManagerEngineOsm::childCategoryIds(const QString &categoryId) const +{ + return m_subcategories.value(categoryId); +} + +QPlaceCategory QPlaceManagerEngineOsm::category(const QString &categoryId) const +{ + return m_categories.value(categoryId); +} + +QList QPlaceManagerEngineOsm::childCategories(const QString &parentId) const +{ + QList categories; + foreach (const QString &id, m_subcategories.value(parentId)) + categories.append(m_categories.value(id)); + return categories; +} + +QList QPlaceManagerEngineOsm::locales() const +{ + return m_locales; +} + +void QPlaceManagerEngineOsm::setLocales(const QList &locales) +{ + m_locales = locales; +} + +void QPlaceManagerEngineOsm::categoryReplyFinished() +{ + QNetworkReply *reply = qobject_cast(sender()); + if (!reply) + return; + + reply->deleteLater(); + + QXmlStreamReader parser(reply); + while (!parser.atEnd() && parser.readNextStartElement()) { + if (parser.name() == QLatin1String("mediawiki")) + continue; + if (parser.name() == QLatin1String("page")) + continue; + if (parser.name() == QLatin1String("revision")) + continue; + if (parser.name() == QLatin1String("text")) { + // parse + QString page = parser.readElementText(); + QRegularExpression regex(QStringLiteral("\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([\\-YN])")); + QRegularExpressionMatchIterator i = regex.globalMatch(page); + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + QString name = match.capturedRef(1).toString(); + QString tagKey = match.capturedRef(2).toString(); + QString tagValue = match.capturedRef(3).toString(); + QString op = match.capturedRef(4).toString(); + QString plural = match.capturedRef(5).toString(); + + // Only interested in any operator plural forms + if (op != QLatin1String("-") || plural != QLatin1String("Y")) + continue; + + if (!m_categories.contains(tagKey)) { + QPlaceCategory category; + category.setCategoryId(tagKey); + category.setName(nameForTagKey(tagKey)); + m_categories.insert(category.categoryId(), category); + m_subcategories[QString()].append(tagKey); + emit categoryAdded(category, QString()); + } + + QPlaceCategory category; + category.setCategoryId(tagKey + QLatin1Char('=') + tagValue); + category.setName(name); + + if (!m_categories.contains(category.categoryId())) { + m_categories.insert(category.categoryId(), category); + m_subcategories[tagKey].append(category.categoryId()); + emit categoryAdded(category, tagKey); + } + } + } + + parser.skipCurrentElement(); + } + + if (m_categories.isEmpty() && !m_categoryLocales.isEmpty()) { + fetchNextCategoryLocale(); + return; + } else { + m_categoryLocales.clear(); + } + + foreach (QPlaceCategoriesReplyOsm *reply, m_pendingCategoriesReply) + reply->emitFinished(); + m_pendingCategoriesReply.clear(); +} + +void QPlaceManagerEngineOsm::categoryReplyError() +{ + foreach (QPlaceCategoriesReplyOsm *reply, m_pendingCategoriesReply) + reply->setError(QPlaceReply::CommunicationError, tr("Network request error")); +} + +void QPlaceManagerEngineOsm::replyFinished() +{ + QPlaceReply *reply = qobject_cast(sender()); + if (reply) + emit finished(reply); +} + +void QPlaceManagerEngineOsm::replyError(QPlaceReply::Error errorCode, const QString &errorString) +{ + QPlaceReply *reply = qobject_cast(sender()); + if (reply) + emit error(reply, errorCode, errorString); +} + +void QPlaceManagerEngineOsm::fetchNextCategoryLocale() +{ + if (m_categoryLocales.isEmpty()) { + qWarning("No locales specified to fetch categories for"); + return; + } + + QLocale locale = m_categoryLocales.takeFirst(); + + // FIXME: Categories should be cached. + QUrl requestUrl = QUrl(SpecialPhrasesBaseUrl + locale.name().left(2).toUpper()); + + m_categoriesReply = m_networkManager->get(QNetworkRequest(requestUrl)); + connect(m_categoriesReply, SIGNAL(finished()), this, SLOT(categoryReplyFinished())); + connect(m_categoriesReply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(categoryReplyError())); +} diff --git a/src/plugins/geoservices/osm/qplacemanagerengineosm.h b/src/plugins/geoservices/osm/qplacemanagerengineosm.h new file mode 100644 index 0000000..675c4ea --- /dev/null +++ b/src/plugins/geoservices/osm/qplacemanagerengineosm.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtFoo module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEMANAGERENGINEOSM_H +#define QPLACEMANAGERENGINEOSM_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QNetworkAccessManager; +class QNetworkReply; +class QPlaceCategoriesReplyOsm; + +class QPlaceManagerEngineOsm : public QPlaceManagerEngine +{ + Q_OBJECT + +public: + QPlaceManagerEngineOsm(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, + QString *errorString); + ~QPlaceManagerEngineOsm(); + + QPlaceSearchReply *search(const QPlaceSearchRequest &request) Q_DECL_OVERRIDE; + + QPlaceReply *initializeCategories() Q_DECL_OVERRIDE; + QString parentCategoryId(const QString &categoryId) const Q_DECL_OVERRIDE; + QStringList childCategoryIds(const QString &categoryId) const Q_DECL_OVERRIDE; + QPlaceCategory category(const QString &categoryId) const Q_DECL_OVERRIDE; + + QList childCategories(const QString &parentId) const Q_DECL_OVERRIDE; + + QList locales() const Q_DECL_OVERRIDE; + void setLocales(const QList &locales) Q_DECL_OVERRIDE; + +private slots: + void categoryReplyFinished(); + void categoryReplyError(); + void replyFinished(); + void replyError(QPlaceReply::Error errorCode, const QString &errorString); + +private: + void fetchNextCategoryLocale(); + + QNetworkAccessManager *m_networkManager; + QByteArray m_userAgent; + QString m_urlPrefix; + QList m_locales; + + QNetworkReply *m_categoriesReply; + QList m_pendingCategoriesReply; + QHash m_categories; + QHash m_subcategories; + + QList m_categoryLocales; +}; + +QT_END_NAMESPACE + +#endif // QPLACEMANAGERENGINEOSM_H diff --git a/src/plugins/geoservices/osm/qplacesearchreplyosm.cpp b/src/plugins/geoservices/osm/qplacesearchreplyosm.cpp new file mode 100644 index 0000000..3cb0ab6 --- /dev/null +++ b/src/plugins/geoservices/osm/qplacesearchreplyosm.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtFoo module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacesearchreplyosm.h" +#include "qplacemanagerengineosm.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QPlaceSearchReplyOsm::QPlaceSearchReplyOsm(const QPlaceSearchRequest &request, + QNetworkReply *reply, QPlaceManagerEngineOsm *parent) +: QPlaceSearchReply(parent), m_reply(reply) +{ + Q_ASSERT(parent); + + setRequest(request); + + if (!m_reply) + return; + + m_reply->setParent(this); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); +} + +QPlaceSearchReplyOsm::~QPlaceSearchReplyOsm() +{ +} + +void QPlaceSearchReplyOsm::abort() +{ + if (m_reply) + m_reply->abort(); +} + +void QPlaceSearchReplyOsm::setError(QPlaceReply::Error errorCode, const QString &errorString) +{ + QPlaceReply::setError(errorCode, errorString); + emit error(errorCode, errorString); + setFinished(true); + emit finished(); +} + +static QGeoRectangle parseBoundingBox(const QJsonArray &coordinates) +{ + if (coordinates.count() != 4) + return QGeoRectangle(); + + double bottom = coordinates.at(0).toString().toDouble(); + double top = coordinates.at(1).toString().toDouble(); + double left = coordinates.at(2).toString().toDouble(); + double right = coordinates.at(3).toString().toDouble(); + + return QGeoRectangle(QGeoCoordinate(top, left), QGeoCoordinate(bottom, right)); +} + +void QPlaceSearchReplyOsm::replyFinished() +{ + QNetworkReply *reply = m_reply; + m_reply->deleteLater(); + m_reply = 0; + + if (reply->error() != QNetworkReply::NoError) { + setError(CommunicationError, tr("Communication error")); + return; + } + + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + if (!document.isArray()) { + setError(ParseError, tr("Response parse error")); + return; + } + + QJsonArray resultsArray = document.array(); + + QGeoCoordinate searchCenter = request().searchArea().center(); + + QStringList placeIds; + + QList results; + for (int i = 0; i < resultsArray.count(); ++i) { + QJsonObject item = resultsArray.at(i).toObject(); + QPlaceResult pr = parsePlaceResult(item); + pr.setDistance(searchCenter.distanceTo(pr.place().location().coordinate())); + placeIds.append(pr.place().placeId()); + results.append(pr); + } + + QVariantMap searchContext = request().searchContext().toMap(); + QStringList excludePlaceIds = + searchContext.value(QStringLiteral("ExcludePlaceIds")).toStringList(); + + if (!excludePlaceIds.isEmpty()) { + QPlaceSearchRequest r = request(); + QVariantMap parameters = searchContext; + + QStringList epi = excludePlaceIds; + epi.removeLast(); + + parameters.insert(QStringLiteral("ExcludePlaceIds"), epi); + r.setSearchContext(parameters); + setPreviousPageRequest(r); + } + + if (!placeIds.isEmpty()) { + QPlaceSearchRequest r = request(); + QVariantMap parameters = searchContext; + + QStringList epi = excludePlaceIds; + epi.append(placeIds.join(QLatin1Char(','))); + + parameters.insert(QStringLiteral("ExcludePlaceIds"), epi); + r.setSearchContext(parameters); + setNextPageRequest(r); + } + + setResults(results); + + setFinished(true); + emit finished(); +} + +QPlaceResult QPlaceSearchReplyOsm::parsePlaceResult(const QJsonObject &item) const +{ + QPlace place; + + QGeoCoordinate coordinate = QGeoCoordinate(item.value(QStringLiteral("lat")).toString().toDouble(), + item.value(QStringLiteral("lon")).toString().toDouble()); + + //const QString placeRank = item.value(QStringLiteral("place_rank")).toString(); + //const QString category = item.value(QStringLiteral("category")).toString(); + const QString type = item.value(QStringLiteral("type")).toString(); + //double importance = item.value(QStringLiteral("importance")).toDouble(); + + place.setAttribution(item.value(QStringLiteral("licence")).toString()); + place.setPlaceId(item.value(QStringLiteral("place_id")).toString()); + + QVariantMap iconParameters; + iconParameters.insert(QPlaceIcon::SingleUrl, + QUrl(item.value(QStringLiteral("icon")).toString())); + QPlaceIcon icon; + icon.setParameters(iconParameters); + place.setIcon(icon); + + QJsonObject addressDetails = item.value(QStringLiteral("address")).toObject(); + + const QString title = addressDetails.value(type).toString(); + + place.setName(title); + + QGeoAddress address; + address.setCity(addressDetails.value(QStringLiteral("city")).toString()); + address.setCountry(addressDetails.value(QStringLiteral("country")).toString()); + // FIXME: country_code is alpha-2 setCountryCode takes alpha-3 + //address.setCountryCode(addressDetails.value(QStringLiteral("country_code")).toString()); + address.setPostalCode(addressDetails.value(QStringLiteral("postcode")).toString()); + address.setStreet(addressDetails.value(QStringLiteral("road")).toString()); + address.setState(addressDetails.value(QStringLiteral("state")).toString()); + address.setDistrict(addressDetails.value(QStringLiteral("suburb")).toString()); + + QGeoLocation location; + location.setCoordinate(coordinate); + location.setAddress(address); + location.setBoundingBox(parseBoundingBox(item.value(QStringLiteral("boundingbox")).toArray())); + + place.setLocation(location); + + QPlaceResult result; + result.setIcon(icon); + result.setPlace(place); + result.setTitle(title); + + return result; +} + +QT_END_NAMESPACE diff --git a/src/plugins/geoservices/osm/qplacesearchreplyosm.h b/src/plugins/geoservices/osm/qplacesearchreplyosm.h new file mode 100644 index 0000000..e495d4f --- /dev/null +++ b/src/plugins/geoservices/osm/qplacesearchreplyosm.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtFoo module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACESEARCHREPLYOSM_H +#define QPLACESEARCHREPLYOSM_H + +#include + +QT_BEGIN_NAMESPACE + +class QNetworkReply; +class QPlaceManagerEngineOsm; +class QPlaceResult; + +class QPlaceSearchReplyOsm : public QPlaceSearchReply +{ + Q_OBJECT + +public: + QPlaceSearchReplyOsm(const QPlaceSearchRequest &request, QNetworkReply *reply, + QPlaceManagerEngineOsm *parent); + ~QPlaceSearchReplyOsm(); + + void abort(); + +private slots: + void setError(QPlaceReply::Error errorCode, const QString &errorString); + void replyFinished(); + +private: + QPlaceResult parsePlaceResult(const QJsonObject &item) const; + + QNetworkReply *m_reply; +}; + +QT_END_NAMESPACE + +#endif // QPLACESEARCHREPLYOSM_H diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro new file mode 100644 index 0000000..35b462d --- /dev/null +++ b/src/plugins/plugins.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +qtHaveModule(positioning): SUBDIRS += position +qtHaveModule(location): SUBDIRS += geoservices diff --git a/src/plugins/position/android/android.pro b/src/plugins/position/android/android.pro new file mode 100644 index 0000000..0dc6a3f --- /dev/null +++ b/src/plugins/position/android/android.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += jar src diff --git a/src/plugins/position/android/jar/AndroidManifest.xml b/src/plugins/position/android/jar/AndroidManifest.xml new file mode 100644 index 0000000..2d066db --- /dev/null +++ b/src/plugins/position/android/jar/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/src/plugins/position/android/jar/bundledjar.pro b/src/plugins/position/android/jar/bundledjar.pro new file mode 100644 index 0000000..e7bd106 --- /dev/null +++ b/src/plugins/position/android/jar/bundledjar.pro @@ -0,0 +1,3 @@ +TARGET = QtPositioning-bundled +CONFIG += bundled_jar_file +include(jar.pri) diff --git a/src/plugins/position/android/jar/distributedjar.pro b/src/plugins/position/android/jar/distributedjar.pro new file mode 100644 index 0000000..4a5faaa --- /dev/null +++ b/src/plugins/position/android/jar/distributedjar.pro @@ -0,0 +1,3 @@ +TARGET = QtPositioning +include(jar.pri) + diff --git a/src/plugins/position/android/jar/jar.pri b/src/plugins/position/android/jar/jar.pri new file mode 100644 index 0000000..9fa548f --- /dev/null +++ b/src/plugins/position/android/jar/jar.pri @@ -0,0 +1,14 @@ +load(qt_build_paths) + +CONFIG += java +DESTDIR = $$MODULE_BASE_OUTDIR/jar + +JAVACLASSPATH += $$PWD/src + +JAVASOURCES += \ + $$PWD/src/org/qtproject/qt5/android/positioning/QtPositioning.java + +# install +target.path = $$[QT_INSTALL_PREFIX]/jar +INSTALLS += target + diff --git a/src/plugins/position/android/jar/jar.pro b/src/plugins/position/android/jar/jar.pro new file mode 100644 index 0000000..8d19c1b --- /dev/null +++ b/src/plugins/position/android/jar/jar.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += bundledjar.pro distributedjar.pro diff --git a/src/plugins/position/android/jar/src/org/qtproject/qt5/android/positioning/QtPositioning.java b/src/plugins/position/android/jar/src/org/qtproject/qt5/android/positioning/QtPositioning.java new file mode 100644 index 0000000..b55a90d --- /dev/null +++ b/src/plugins/position/android/jar/src/org/qtproject/qt5/android/positioning/QtPositioning.java @@ -0,0 +1,582 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt5.android.positioning; + +import android.content.Context; +import android.location.GpsSatellite; +import android.location.GpsStatus; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import android.util.Log; + +public class QtPositioning implements LocationListener +{ + + private static final String TAG = "QtPositioning"; + static LocationManager locationManager = null; + static Object m_syncObject = new Object(); + static HashMap runningListeners = new HashMap(); + + /* + The positionInfo instance to which this + QtPositioning instance is attached to. + */ + private int nativeClassReference = 0; + + /* + The provider type requested by Qt + */ + private int expectedProviders = 0; + + public static final int QT_GPS_PROVIDER = 1; + public static final int QT_NETWORK_PROVIDER = 2; + + /* The following values must match the corresponding error enums in the Qt API*/ + public static final int QT_ACCESS_ERROR = 0; + public static final int QT_CLOSED_ERROR = 1; + public static final int QT_POSITION_UNKNOWN_SOURCE_ERROR = 2; + public static final int QT_POSITION_NO_ERROR = 3; + public static final int QT_SATELLITE_NO_ERROR = 2; + public static final int QT_SATELLITE_UNKNOWN_SOURCE_ERROR = -1; + + /* True, if updates were caused by requestUpdate() */ + private boolean isSingleUpdate = false; + /* The length requested for regular intervals in msec. */ + private int updateIntervalTime = 0; + + /* The last received GPS update */ + private Location lastGps = null; + /* The last received network update */ + private Location lastNetwork = null; + /* If true this class acts as satellite signal monitor rather than location monitor */ + private boolean isSatelliteUpdate = false; + + private PositioningLooper looperThread; + + static public void setContext(Context context) + { + try { + locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE); + } catch(Exception e) { + e.printStackTrace(); + } + } + + static private int[] providerList() + { + List providers = locationManager.getAllProviders(); + int retList[] = new int[providers.size()]; + for (int i = 0; i < providers.size(); i++) { + if (providers.get(i).equals(LocationManager.GPS_PROVIDER)) { + //must be in sync with AndroidPositioning::PositionProvider::PROVIDER_GPS + retList[i] = 0; + } else if (providers.get(i).equals(LocationManager.NETWORK_PROVIDER)) { + //must be in sync with AndroidPositioning::PositionProvider::PROVIDER_NETWORK + retList[i] = 1; + } else if (providers.get(i).equals(LocationManager.PASSIVE_PROVIDER)) { + //must be in sync with AndroidPositioning::PositionProvider::PROVIDER_PASSIVE + retList[i] = 2; + } else { + retList[i] = -1; + } + } + return retList; + } + + static public Location lastKnownPosition(boolean fromSatelliteOnly) + { + Location gps = null; + Location network = null; + try { + gps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + if (!fromSatelliteOnly) + network = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + } catch(Exception e) { + e.printStackTrace(); + gps = network = null; + } + + if (gps != null && network != null) { + //we return the most recent location but slightly prefer GPS + //prefer GPS if it is max 4 hrs older than network + long delta = network.getTime() - gps.getTime(); + if (delta < 4*60*60*1000) { + return gps; + } else { + return network; + } + } else if (gps != null ) { + return gps; + } else if (network != null) { + return network; + } + + return null; + } + + /* Returns true if at least on of the given providers is enabled. */ + static private boolean expectedProvidersAvailable(int desiredProviders) + { + List enabledProviders = locationManager.getProviders(true); + if ((desiredProviders & QT_GPS_PROVIDER) > 0) { //gps desired + if (enabledProviders.contains(LocationManager.GPS_PROVIDER)) { + return true; + } + } + if ((desiredProviders & QT_NETWORK_PROVIDER) > 0) { //network desired + if (enabledProviders.contains(LocationManager.NETWORK_PROVIDER)) { + return true; + } + } + + return false; + } + + + static private void addActiveListener(QtPositioning listener, String provider) + { + int androidClassKey = listener.nativeClassReference; + //start update thread + listener.setActiveLooper(true); + + if (runningListeners.containsKey(androidClassKey) && runningListeners.get(androidClassKey) != listener) { + removeActiveListener(androidClassKey); + } + + locationManager.requestSingleUpdate(provider, + listener, + listener.looper()); + + runningListeners.put(androidClassKey, listener); + } + + + static private void addActiveListener(QtPositioning listener, String provider, long minTime, float minDistance) + { + int androidClassKey = listener.nativeClassReference; + //start update thread + listener.setActiveLooper(true); + + if (runningListeners.containsKey(androidClassKey) && runningListeners.get(androidClassKey) != listener) { + removeActiveListener(androidClassKey); + } + + locationManager.requestLocationUpdates(provider, + minTime, minDistance, + listener, + listener.looper()); + + runningListeners.put(androidClassKey, listener); + } + + + static private void removeActiveListener(QtPositioning listener) + { + removeActiveListener(listener.nativeClassReference); + } + + + static private void removeActiveListener(int androidClassKey) + { + QtPositioning listener = runningListeners.remove(androidClassKey); + locationManager.removeUpdates(listener); + listener.setActiveLooper(false); + } + + + static public int startUpdates(int androidClassKey, int locationProvider, int updateInterval) + { + synchronized (m_syncObject) { + try { + boolean exceptionOccurred = false; + QtPositioning positioningListener = new QtPositioning(); + positioningListener.nativeClassReference = androidClassKey; + positioningListener.expectedProviders = locationProvider; + positioningListener.isSatelliteUpdate = false; + + if (updateInterval == 0) + updateInterval = 50; //don't update more often than once per 50ms + + positioningListener.updateIntervalTime = updateInterval; + if ((locationProvider & QT_GPS_PROVIDER) > 0) { + Log.d(TAG, "Regular updates using GPS " + updateInterval); + try { + addActiveListener(positioningListener, + LocationManager.GPS_PROVIDER, + updateInterval, 0); + } catch (SecurityException se) { + se.printStackTrace(); + exceptionOccurred = true; + } + } + + if ((locationProvider & QT_NETWORK_PROVIDER) > 0) { + Log.d(TAG, "Regular updates using network " + updateInterval); + try { + addActiveListener(positioningListener, + LocationManager.NETWORK_PROVIDER, + updateInterval, 0); + } catch (SecurityException se) { + se.printStackTrace(); + exceptionOccurred = true; + } + } + if (exceptionOccurred) { + removeActiveListener(positioningListener); + return QT_ACCESS_ERROR; + } + + if (!expectedProvidersAvailable(locationProvider)) { + //all location providers unavailbe -> when they come back we resume automatically + return QT_CLOSED_ERROR; + } + + } catch(Exception e) { + e.printStackTrace(); + return QT_POSITION_UNKNOWN_SOURCE_ERROR; + } + + return QT_POSITION_NO_ERROR; + } + } + + static public void stopUpdates(int androidClassKey) + { + synchronized (m_syncObject) { + try { + Log.d(TAG, "Stopping updates"); + removeActiveListener(androidClassKey); + } catch(Exception e) { + e.printStackTrace(); + return; + } + } + } + + static public int requestUpdate(int androidClassKey, int locationProvider) + { + synchronized (m_syncObject) { + try { + boolean exceptionOccurred = false; + QtPositioning positioningListener = new QtPositioning(); + positioningListener.nativeClassReference = androidClassKey; + positioningListener.isSingleUpdate = true; + positioningListener.expectedProviders = locationProvider; + positioningListener.isSatelliteUpdate = false; + + if ((locationProvider & QT_GPS_PROVIDER) > 0) { + Log.d(TAG, "Single update using GPS"); + try { + addActiveListener(positioningListener, LocationManager.GPS_PROVIDER); + } catch (SecurityException se) { + se.printStackTrace(); + exceptionOccurred = true; + } + } + + if ((locationProvider & QT_NETWORK_PROVIDER) > 0) { + Log.d(TAG, "Single update using network"); + try { + addActiveListener(positioningListener, LocationManager.NETWORK_PROVIDER); + } catch (SecurityException se) { + se.printStackTrace(); + exceptionOccurred = true; + } + } + if (exceptionOccurred) { + removeActiveListener(positioningListener); + return QT_ACCESS_ERROR; + } + + if (!expectedProvidersAvailable(locationProvider)) { + //all location providers unavailable -> when they come back we resume automatically + //in the mean time return ClosedError + return QT_CLOSED_ERROR; + } + + } catch(Exception e) { + e.printStackTrace(); + return QT_POSITION_UNKNOWN_SOURCE_ERROR; + } + + return QT_POSITION_NO_ERROR; + } + } + + static public int startSatelliteUpdates(int androidClassKey, int updateInterval, boolean isSingleRequest) + { + synchronized (m_syncObject) { + try { + boolean exceptionOccurred = false; + QtPositioning positioningListener = new QtPositioning(); + positioningListener.isSatelliteUpdate = true; + positioningListener.nativeClassReference = androidClassKey; + positioningListener.expectedProviders = 1; //always satellite provider + positioningListener.isSingleUpdate = isSingleRequest; + + if (updateInterval == 0) + updateInterval = 50; //don't update more often than once per 50ms + + if (isSingleRequest) + Log.d(TAG, "Single update for Satellites " + updateInterval); + else + Log.d(TAG, "Regular updates for Satellites " + updateInterval); + try { + addActiveListener(positioningListener, LocationManager.GPS_PROVIDER, + updateInterval, 0); + } catch (SecurityException se) { + se.printStackTrace(); + exceptionOccurred = true; + } + + if (exceptionOccurred) { + removeActiveListener(positioningListener); + return QT_ACCESS_ERROR; + } + + if (!expectedProvidersAvailable(positioningListener.expectedProviders)) { + //all location providers unavailable -> when they come back we resume automatically + //in the mean time return ClosedError + return QT_CLOSED_ERROR; + } + + } catch(Exception e) { + e.printStackTrace(); + return QT_SATELLITE_UNKNOWN_SOURCE_ERROR; + } + + return QT_SATELLITE_NO_ERROR; + } + } + + public QtPositioning() + { + looperThread = new PositioningLooper(); + } + + public Looper looper() + { + return looperThread.looper(); + } + + private void setActiveLooper(boolean setActive) + { + try{ + if (setActive) { + if (looperThread.isAlive()) + return; + + if (isSatelliteUpdate) + looperThread.isSatelliteListener(true); + + long start = System.currentTimeMillis(); + looperThread.start(); + + //busy wait but lasts ~20-30 ms only + while (!looperThread.isReady()); + + long stop = System.currentTimeMillis(); + Log.d(TAG, "Looper Thread startup time in ms: " + (stop-start)); + } else { + looperThread.quitLooper(); + } + } catch(Exception e) { + e.printStackTrace(); + } + } + + private class PositioningLooper extends Thread implements GpsStatus.Listener{ + private boolean looperRunning; + private Looper posLooper; + private boolean isSatelliteLooper = false; + private LocationManager locManager = null; + + private PositioningLooper() + { + looperRunning = false; + } + + public void run() + { + Looper.prepare(); + Handler handler = new Handler(); + + if (isSatelliteLooper) { + try { + locationManager.addGpsStatusListener(this); + } catch(Exception e) { + e.printStackTrace(); + } + } + + posLooper = Looper.myLooper(); + synchronized (this) { + looperRunning = true; + } + Looper.loop(); + synchronized (this) { + looperRunning = false; + } + } + + public void quitLooper() + { + if (isSatelliteLooper) + locationManager.removeGpsStatusListener(this); + looper().quit(); + } + + public synchronized boolean isReady() + { + return looperRunning; + } + + public void isSatelliteListener(boolean isListener) + { + isSatelliteLooper = isListener; + } + + public Looper looper() + { + return posLooper; + } + + @Override + public void onGpsStatusChanged(int event) { + switch (event) { + case GpsStatus.GPS_EVENT_FIRST_FIX: + break; + case GpsStatus.GPS_EVENT_SATELLITE_STATUS: + GpsStatus status = locationManager.getGpsStatus(null); + Iterable iterable = status.getSatellites(); + Iterator it = iterable.iterator(); + + ArrayList list = new ArrayList(); + while (it.hasNext()) { + GpsSatellite sat = (GpsSatellite) it.next(); + list.add(sat); + } + GpsSatellite[] sats = list.toArray(new GpsSatellite[list.size()]); + satelliteUpdated(sats, nativeClassReference, isSingleUpdate); + + break; + case GpsStatus.GPS_EVENT_STARTED: + break; + case GpsStatus.GPS_EVENT_STOPPED: + break; + } + } + } + + + + public static native void positionUpdated(Location update, int androidClassKey, boolean isSingleUpdate); + public static native void locationProvidersDisabled(int androidClassKey); + public static native void satelliteUpdated(GpsSatellite[] update, int androidClassKey, boolean isSingleUpdate); + + @Override + public void onLocationChanged(Location location) { + //Log.d(TAG, "**** Position Update ****: " + location.toString() + " " + isSingleUpdate); + if (location == null) + return; + + if (isSatelliteUpdate) //we are a QGeoSatelliteInfoSource -> ignore + return; + + if (isSingleUpdate || expectedProviders < 3) { + positionUpdated(location, nativeClassReference, isSingleUpdate); + return; + } + + /* + We can use GPS and Network, pick the better location provider. + Generally we prefer GPS data due to their higher accurancy but we + let Network data pass until GPS fix is available + */ + + if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) { + lastGps = location; + + // assumption: GPS always better -> pass it on + positionUpdated(location, nativeClassReference, isSingleUpdate); + } else if (location.getProvider().equals(LocationManager.NETWORK_PROVIDER)) { + lastNetwork = location; + + if (lastGps == null) { //no GPS fix yet use network location + positionUpdated(location, nativeClassReference, isSingleUpdate); + return; + } + + long delta = location.getTime() - lastGps.getTime(); + + // Ignore if network update is older than last GPS (delta < 0) + // Ignore if gps update still has time to provide next location (delta < updateInterval) + if (delta < updateIntervalTime) + return; + + // Use network data -> GPS has timed out on updateInterval + positionUpdated(location, nativeClassReference, isSingleUpdate); + } + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) {} + + @Override + public void onProviderEnabled(String provider) { + Log.d(TAG, "Enabled provider: " + provider); + } + + @Override + public void onProviderDisabled(String provider) { + Log.d(TAG, "Disabled provider: " + provider); + if (!expectedProvidersAvailable(expectedProviders)) + locationProvidersDisabled(nativeClassReference); + } +} diff --git a/src/plugins/position/android/src/jnipositioning.cpp b/src/plugins/position/android/src/jnipositioning.cpp new file mode 100644 index 0000000..e0124eb --- /dev/null +++ b/src/plugins/position/android/src/jnipositioning.cpp @@ -0,0 +1,577 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include "qgeopositioninfosource_android_p.h" +#include "qgeosatelliteinfosource_android_p.h" + +#include "jnipositioning.h" + +static JavaVM *javaVM = 0; +jclass positioningClass; + +static jmethodID providerListMethodId; +static jmethodID lastKnownPositionMethodId; +static jmethodID startUpdatesMethodId; +static jmethodID stopUpdatesMethodId; +static jmethodID requestUpdateMethodId; +static jmethodID startSatelliteUpdatesMethodId; + +static const char logTag[] = "QtPositioning"; +static const char classErrorMsg[] = "Can't find class \"%s\""; +static const char methodErrorMsg[] = "Can't find method \"%s%s\""; + +namespace AndroidPositioning { + typedef QMap PositionSourceMap; + typedef QMap SatelliteSourceMap; + + Q_GLOBAL_STATIC(PositionSourceMap, idToPosSource) + + Q_GLOBAL_STATIC(SatelliteSourceMap, idToSatSource) + + struct AttachedJNIEnv + { + AttachedJNIEnv() + { + attached = false; + if (javaVM->GetEnv((void**)&jniEnv, JNI_VERSION_1_6) < 0) { + if (javaVM->AttachCurrentThread(&jniEnv, NULL) < 0) { + __android_log_print(ANDROID_LOG_ERROR, logTag, "AttachCurrentThread failed"); + jniEnv = 0; + return; + } + attached = true; + } + } + + ~AttachedJNIEnv() + { + if (attached) + javaVM->DetachCurrentThread(); + } + bool attached; + JNIEnv *jniEnv; + }; + + int registerPositionInfoSource(QObject *obj) + { + static bool firstInit = true; + if (firstInit) { + qsrand( QDateTime::currentDateTime().toTime_t() ); + firstInit = false; + } + + int key = -1; + if (obj->inherits("QGeoPositionInfoSource")) { + QGeoPositionInfoSourceAndroid *src = qobject_cast(obj); + Q_ASSERT(src); + do { + key = qrand(); + } while (idToPosSource()->contains(key)); + + idToPosSource()->insert(key, src); + } else if (obj->inherits("QGeoSatelliteInfoSource")) { + QGeoSatelliteInfoSourceAndroid *src = qobject_cast(obj); + Q_ASSERT(src); + do { + key = qrand(); + } while (idToSatSource()->contains(key)); + + idToSatSource()->insert(key, src); + } + + return key; + } + + void unregisterPositionInfoSource(int key) + { + idToPosSource()->remove(key); + idToSatSource()->remove(key); + } + + enum PositionProvider + { + PROVIDER_GPS = 0, + PROVIDER_NETWORK = 1, + PROVIDER_PASSIVE = 2 + }; + + + QGeoPositionInfoSource::PositioningMethods availableProviders() + { + QGeoPositionInfoSource::PositioningMethods ret = + static_cast(0); + AttachedJNIEnv env; + if (!env.jniEnv) + return ret; + jintArray jProviders = static_cast(env.jniEnv->CallStaticObjectMethod( + positioningClass, providerListMethodId)); + jint *providers = env.jniEnv->GetIntArrayElements(jProviders, 0); + const uint size = env.jniEnv->GetArrayLength(jProviders); + for (uint i = 0; i < size; i++) { + switch (providers[i]) { + case PROVIDER_GPS: + ret |= QGeoPositionInfoSource::SatellitePositioningMethods; + break; + case PROVIDER_NETWORK: + ret |= QGeoPositionInfoSource::NonSatellitePositioningMethods; + break; + case PROVIDER_PASSIVE: + //we ignore as Qt doesn't have interface for it right now + break; + default: + __android_log_print(ANDROID_LOG_INFO, logTag, "Unknown positioningMethod"); + } + } + + env.jniEnv->ReleaseIntArrayElements(jProviders, providers, 0); + env.jniEnv->DeleteLocalRef(jProviders); + + return ret; + } + + //caching originally taken from corelib/kernel/qjni.cpp + typedef QHash JMethodIDHash; + Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID) + + static jmethodID getCachedMethodID(JNIEnv *env, + jclass clazz, + const char *name, + const char *sig) + { + jmethodID id = 0; + int offset_name = qstrlen(name); + int offset_signal = qstrlen(sig); + QByteArray key(offset_name + offset_signal, Qt::Uninitialized); + memcpy(key.data(), name, offset_name); + memcpy(key.data()+offset_name, sig, offset_signal); + QHash::iterator it = cachedMethodID->find(key); + if (it == cachedMethodID->end()) { + id = env->GetMethodID(clazz, name, sig); + if (env->ExceptionCheck()) { + id = 0; + #ifdef QT_DEBUG + env->ExceptionDescribe(); + #endif // QT_DEBUG + env->ExceptionClear(); + } + + cachedMethodID->insert(key, id); + } else { + id = it.value(); + } + return id; + } + + QGeoPositionInfo positionInfoFromJavaLocation(JNIEnv * jniEnv, const jobject &location) + { + QGeoPositionInfo info; + jclass thisClass = jniEnv->GetObjectClass(location); + if (!thisClass) + return QGeoPositionInfo(); + + jmethodID mid = getCachedMethodID(jniEnv, thisClass, "getLatitude", "()D"); + jdouble latitude = jniEnv->CallDoubleMethod(location, mid); + mid = getCachedMethodID(jniEnv, thisClass, "getLongitude", "()D"); + jdouble longitude = jniEnv->CallDoubleMethod(location, mid); + QGeoCoordinate coordinate(latitude, longitude); + + //altitude + mid = getCachedMethodID(jniEnv, thisClass, "hasAltitude", "()Z"); + jboolean attributeExists = jniEnv->CallBooleanMethod(location, mid); + if (attributeExists) { + mid = getCachedMethodID(jniEnv, thisClass, "getAltitude", "()D"); + jdouble value = jniEnv->CallDoubleMethod(location, mid); + coordinate.setAltitude(value); + } + + info.setCoordinate(coordinate); + + //time stamp + mid = getCachedMethodID(jniEnv, thisClass, "getTime", "()J"); + jlong timestamp = jniEnv->CallLongMethod(location, mid); + info.setTimestamp(QDateTime::fromMSecsSinceEpoch(timestamp)); + + //accuracy + mid = getCachedMethodID(jniEnv, thisClass, "hasAccuracy", "()Z"); + attributeExists = jniEnv->CallBooleanMethod(location, mid); + if (attributeExists) { + mid = getCachedMethodID(jniEnv, thisClass, "getAccuracy", "()F"); + jfloat accuracy = jniEnv->CallFloatMethod(location, mid); + info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy); + } + + //ground speed + mid = getCachedMethodID(jniEnv, thisClass, "hasSpeed", "()Z"); + attributeExists = jniEnv->CallBooleanMethod(location, mid); + if (attributeExists) { + mid = getCachedMethodID(jniEnv, thisClass, "getSpeed", "()F"); + jfloat speed = jniEnv->CallFloatMethod(location, mid); + info.setAttribute(QGeoPositionInfo::GroundSpeed, speed); + } + + //bearing + mid = getCachedMethodID(jniEnv, thisClass, "hasBearing", "()Z"); + attributeExists = jniEnv->CallBooleanMethod(location, mid); + if (attributeExists) { + mid = getCachedMethodID(jniEnv, thisClass, "getBearing", "()F"); + jfloat bearing = jniEnv->CallFloatMethod(location, mid); + info.setAttribute(QGeoPositionInfo::Direction, bearing); + } + + jniEnv->DeleteLocalRef(thisClass); + return info; + } + + QList satelliteInfoFromJavaLocation(JNIEnv *jniEnv, + jobjectArray satellites, + QList* usedInFix) + { + QList sats; + jsize length = jniEnv->GetArrayLength(satellites); + for (int i = 0; iGetObjectArrayElement(satellites, i); + if (jniEnv->ExceptionOccurred()) { + qWarning() << "Cannot process all satellite data due to exception."; + break; + } + + jclass thisClass = jniEnv->GetObjectClass(element); + if (!thisClass) + continue; + + QGeoSatelliteInfo info; + + //signal strength + jmethodID mid = getCachedMethodID(jniEnv, thisClass, "getSnr", "()F"); + jfloat snr = jniEnv->CallFloatMethod(element, mid); + info.setSignalStrength((int)snr); + + //ignore any satellite with no signal whatsoever + if (qFuzzyIsNull(snr)) + continue; + + //prn + mid = getCachedMethodID(jniEnv, thisClass, "getPrn", "()I"); + jint prn = jniEnv->CallIntMethod(element, mid); + info.setSatelliteIdentifier(prn); + + if (prn >= 1 && prn <= 32) + info.setSatelliteSystem(QGeoSatelliteInfo::GPS); + else if (prn >= 65 && prn <= 96) + info.setSatelliteSystem(QGeoSatelliteInfo::GLONASS); + + //azimuth + mid = getCachedMethodID(jniEnv, thisClass, "getAzimuth", "()F"); + jfloat azimuth = jniEnv->CallFloatMethod(element, mid); + info.setAttribute(QGeoSatelliteInfo::Azimuth, azimuth); + + //elevation + mid = getCachedMethodID(jniEnv, thisClass, "getElevation", "()F"); + jfloat elevation = jniEnv->CallFloatMethod(element, mid); + info.setAttribute(QGeoSatelliteInfo::Elevation, elevation); + + //used in a fix + mid = getCachedMethodID(jniEnv, thisClass, "usedInFix", "()Z"); + jboolean inFix = jniEnv->CallBooleanMethod(element, mid); + + sats.append(info); + + if (inFix) + usedInFix->append(info); + + jniEnv->DeleteLocalRef(thisClass); + jniEnv->DeleteLocalRef(element); + } + + return sats; + } + + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly) + { + AttachedJNIEnv env; + if (!env.jniEnv) + return QGeoPositionInfo(); + + jobject location = env.jniEnv->CallStaticObjectMethod(positioningClass, + lastKnownPositionMethodId, + fromSatellitePositioningMethodsOnly); + if (location == 0) + return QGeoPositionInfo(); + + const QGeoPositionInfo info = positionInfoFromJavaLocation(env.jniEnv, location); + env.jniEnv->DeleteLocalRef(location); + + return info; + } + + inline int positioningMethodToInt(QGeoPositionInfoSource::PositioningMethods m) + { + int providerSelection = 0; + if (m & QGeoPositionInfoSource::SatellitePositioningMethods) + providerSelection |= 1; + if (m & QGeoPositionInfoSource::NonSatellitePositioningMethods) + providerSelection |= 2; + + return providerSelection; + } + + QGeoPositionInfoSource::Error startUpdates(int androidClassKey) + { + AttachedJNIEnv env; + if (!env.jniEnv) + return QGeoPositionInfoSource::UnknownSourceError; + + QGeoPositionInfoSourceAndroid *source = AndroidPositioning::idToPosSource()->value(androidClassKey); + + if (source) { + int errorCode = env.jniEnv->CallStaticIntMethod(positioningClass, startUpdatesMethodId, + androidClassKey, + positioningMethodToInt(source->preferredPositioningMethods()), + source->updateInterval()); + switch (errorCode) { + case 0: + case 1: + case 2: + case 3: + return static_cast(errorCode); + default: + break; + } + } + + return QGeoPositionInfoSource::UnknownSourceError; + } + + //used for stopping regular and single updates + void stopUpdates(int androidClassKey) + { + AttachedJNIEnv env; + if (!env.jniEnv) + return; + + env.jniEnv->CallStaticVoidMethod(positioningClass, stopUpdatesMethodId, androidClassKey); + } + + QGeoPositionInfoSource::Error requestUpdate(int androidClassKey) + { + AttachedJNIEnv env; + if (!env.jniEnv) + return QGeoPositionInfoSource::UnknownSourceError; + + QGeoPositionInfoSourceAndroid *source = AndroidPositioning::idToPosSource()->value(androidClassKey); + + if (source) { + int errorCode = env.jniEnv->CallStaticIntMethod(positioningClass, requestUpdateMethodId, + androidClassKey, + positioningMethodToInt(source->preferredPositioningMethods())); + switch (errorCode) { + case 0: + case 1: + case 2: + case 3: + return static_cast(errorCode); + default: + break; + } + } + return QGeoPositionInfoSource::UnknownSourceError; + } + + QGeoSatelliteInfoSource::Error startSatelliteUpdates(int androidClassKey, bool isSingleRequest, int requestTimeout) + { + AttachedJNIEnv env; + if (!env.jniEnv) + return QGeoSatelliteInfoSource::UnknownSourceError; + + QGeoSatelliteInfoSourceAndroid *source = AndroidPositioning::idToSatSource()->value(androidClassKey); + + if (source) { + int interval = source->updateInterval(); + if (isSingleRequest) + interval = requestTimeout; + int errorCode = env.jniEnv->CallStaticIntMethod(positioningClass, startSatelliteUpdatesMethodId, + androidClassKey, + interval, isSingleRequest); + switch (errorCode) { + case -1: + case 0: + case 1: + case 2: + return static_cast(errorCode); + default: + qWarning() << "startSatelliteUpdates: Unknown error code " << errorCode; + break; + } + } + return QGeoSatelliteInfoSource::UnknownSourceError; + } +} + + +static void positionUpdated(JNIEnv *env, jobject /*thiz*/, jobject location, jint androidClassKey, jboolean isSingleUpdate) +{ + QGeoPositionInfo info = AndroidPositioning::positionInfoFromJavaLocation(env, location); + + QGeoPositionInfoSourceAndroid *source = AndroidPositioning::idToPosSource()->value(androidClassKey); + if (!source) { + qWarning("positionUpdated: source == 0"); + return; + } + + //we need to invoke indirectly as the Looper thread is likely to be not the same thread + if (!isSingleUpdate) + QMetaObject::invokeMethod(source, "processPositionUpdate", Qt::AutoConnection, + Q_ARG(QGeoPositionInfo, info)); + else + QMetaObject::invokeMethod(source, "processSinglePositionUpdate", Qt::AutoConnection, + Q_ARG(QGeoPositionInfo, info)); +} + +static void locationProvidersDisabled(JNIEnv *env, jobject /*thiz*/, jint androidClassKey) +{ + Q_UNUSED(env); + QObject *source = AndroidPositioning::idToPosSource()->value(androidClassKey); + if (!source) + source = AndroidPositioning::idToSatSource()->value(androidClassKey); + if (!source) { + qWarning("locationProvidersDisabled: source == 0"); + return; + } + + QMetaObject::invokeMethod(source, "locationProviderDisabled", Qt::AutoConnection); +} + +static void satelliteUpdated(JNIEnv *env, jobject /*thiz*/, jobjectArray satellites, jint androidClassKey, jboolean isSingleUpdate) +{ + QList inUse; + QList sats = AndroidPositioning::satelliteInfoFromJavaLocation(env, satellites, &inUse); + + QGeoSatelliteInfoSourceAndroid *source = AndroidPositioning::idToSatSource()->value(androidClassKey); + if (!source) { + qFatal("satelliteUpdated: source == 0"); + return; + } + + QMetaObject::invokeMethod(source, "processSatelliteUpdateInView", Qt::AutoConnection, + Q_ARG(QList, sats), Q_ARG(bool, isSingleUpdate)); + + QMetaObject::invokeMethod(source, "processSatelliteUpdateInUse", Qt::AutoConnection, + Q_ARG(QList, inUse), Q_ARG(bool, isSingleUpdate)); +} + + +#define FIND_AND_CHECK_CLASS(CLASS_NAME) \ +clazz = env->FindClass(CLASS_NAME); \ +if (!clazz) { \ + __android_log_print(ANDROID_LOG_FATAL, logTag, classErrorMsg, CLASS_NAME); \ + return JNI_FALSE; \ +} + +#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \ +VAR = env->GetStaticMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \ +if (!VAR) { \ + __android_log_print(ANDROID_LOG_FATAL, logTag, methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE); \ + return JNI_FALSE; \ +} + +static JNINativeMethod methods[] = { + {"positionUpdated", "(Landroid/location/Location;IZ)V", (void *)positionUpdated}, + {"locationProvidersDisabled", "(I)V", (void *) locationProvidersDisabled}, + {"satelliteUpdated", "([Landroid/location/GpsSatellite;IZ)V", (void *)satelliteUpdated} +}; + +static bool registerNatives(JNIEnv *env) +{ + jclass clazz; + FIND_AND_CHECK_CLASS("org/qtproject/qt5/android/positioning/QtPositioning"); + positioningClass = static_cast(env->NewGlobalRef(clazz)); + + if (env->RegisterNatives(positioningClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) { + __android_log_print(ANDROID_LOG_FATAL, logTag, "RegisterNatives failed"); + return JNI_FALSE; + } + + GET_AND_CHECK_STATIC_METHOD(providerListMethodId, positioningClass, "providerList", "()[I"); + GET_AND_CHECK_STATIC_METHOD(lastKnownPositionMethodId, positioningClass, "lastKnownPosition", "(Z)Landroid/location/Location;"); + GET_AND_CHECK_STATIC_METHOD(startUpdatesMethodId, positioningClass, "startUpdates", "(III)I"); + GET_AND_CHECK_STATIC_METHOD(stopUpdatesMethodId, positioningClass, "stopUpdates", "(I)V"); + GET_AND_CHECK_STATIC_METHOD(requestUpdateMethodId, positioningClass, "requestUpdate", "(II)I"); + GET_AND_CHECK_STATIC_METHOD(startSatelliteUpdatesMethodId, positioningClass, "startSatelliteUpdates", "(IIZ)I"); + + return true; +} + +Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * /*reserved*/) +{ + static bool initialized = false; + if (initialized) + return JNI_VERSION_1_6; + initialized = true; + + typedef union { + JNIEnv *nativeEnvironment; + void *venv; + } UnionJNIEnvToVoid; + + __android_log_print(ANDROID_LOG_INFO, logTag, "Positioning start"); + UnionJNIEnvToVoid uenv; + uenv.venv = NULL; + javaVM = 0; + + if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print(ANDROID_LOG_FATAL, logTag, "GetEnv failed"); + return -1; + } + JNIEnv *env = uenv.nativeEnvironment; + if (!registerNatives(env)) { + __android_log_print(ANDROID_LOG_FATAL, logTag, "registerNatives failed"); + return -1; + } + + javaVM = vm; + return JNI_VERSION_1_4; +} + diff --git a/src/plugins/position/android/src/jnipositioning.h b/src/plugins/position/android/src/jnipositioning.h new file mode 100644 index 0000000..0de6a5a --- /dev/null +++ b/src/plugins/position/android/src/jnipositioning.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef JNIPOSITIONING_H +#define JNIPOSITIONING_H + +#include +#include + +namespace AndroidPositioning +{ + int registerPositionInfoSource(QObject *obj); + void unregisterPositionInfoSource(int key); + + QGeoPositionInfoSource::PositioningMethods availableProviders(); + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly); + + QGeoPositionInfoSource::Error startUpdates(int androidClassKey); + void stopUpdates(int androidClassKey); + QGeoPositionInfoSource::Error requestUpdate(int androidClassKey); + + QGeoSatelliteInfoSource::Error startSatelliteUpdates(int androidClassKey, + bool isSingleRequest, + int updateRequestTimeout); +} + +#endif // JNIPOSITIONING_H diff --git a/src/plugins/position/android/src/plugin.json b/src/plugins/position/android/src/plugin.json new file mode 100644 index 0000000..4fd8789 --- /dev/null +++ b/src/plugins/position/android/src/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["android"], + "Provider": "android", + "Position": true, + "Satellite": true, + "Monitor": false, + "Priority": 1000, + "Testable": false +} diff --git a/src/plugins/position/android/src/positionfactory_android.cpp b/src/plugins/position/android/src/positionfactory_android.cpp new file mode 100644 index 0000000..25d6ed0 --- /dev/null +++ b/src/plugins/position/android/src/positionfactory_android.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "positionfactory_android.h" +#include "qgeopositioninfosource_android_p.h" +#include "qgeosatelliteinfosource_android_p.h" + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactoryAndroid::positionInfoSource(QObject *parent) +{ + QGeoPositionInfoSourceAndroid *src = new QGeoPositionInfoSourceAndroid(parent); + return src; +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactoryAndroid::satelliteInfoSource(QObject *parent) +{ + QGeoSatelliteInfoSourceAndroid *src = new QGeoSatelliteInfoSourceAndroid(parent); + return src; +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactoryAndroid::areaMonitor(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} diff --git a/src/plugins/position/android/src/positionfactory_android.h b/src/plugins/position/android/src/positionfactory_android.h new file mode 100644 index 0000000..cdab6f1 --- /dev/null +++ b/src/plugins/position/android/src/positionfactory_android.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef POSITIONPOLLFACTORY_H +#define POSITIONPOLLFACTORY_H + +#include +#include + +class QGeoPositionInfoSourceFactoryAndroid : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + Q_INTERFACES(QGeoPositionInfoSourceFactory) +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent); + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent); + QGeoAreaMonitorSource *areaMonitor(QObject *parent); +}; + +#endif // POSITIONPOLLFACTORY_H diff --git a/src/plugins/position/android/src/qgeopositioninfosource_android.cpp b/src/plugins/position/android/src/qgeopositioninfosource_android.cpp new file mode 100644 index 0000000..2c30196 --- /dev/null +++ b/src/plugins/position/android/src/qgeopositioninfosource_android.cpp @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosource_android_p.h" +#include "jnipositioning.h" +//#include +#include + +Q_DECLARE_METATYPE(QGeoPositionInfo) +#define UPDATE_FROM_COLD_START 2*60*1000 + + +QGeoPositionInfoSourceAndroid::QGeoPositionInfoSourceAndroid(QObject *parent) : + QGeoPositionInfoSource(parent), updatesRunning(false), m_error(NoError) +{ + qRegisterMetaType< QGeoPositionInfo >(); + androidClassKeyForUpdate = AndroidPositioning::registerPositionInfoSource(this); + androidClassKeyForSingleRequest = AndroidPositioning::registerPositionInfoSource(this); + + //qDebug() << "androidClassKey: " << androidClassKeyForUpdate << androidClassKeyForSingleRequest; + //by default use all methods + setPreferredPositioningMethods(AllPositioningMethods); + + m_requestTimer.setSingleShot(true); + QObject::connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestTimeout())); +} + +QGeoPositionInfoSourceAndroid::~QGeoPositionInfoSourceAndroid() +{ + stopUpdates(); + + if (m_requestTimer.isActive()) { + m_requestTimer.stop(); + AndroidPositioning::stopUpdates(androidClassKeyForSingleRequest); + } + + AndroidPositioning::unregisterPositionInfoSource(androidClassKeyForUpdate); + AndroidPositioning::unregisterPositionInfoSource(androidClassKeyForSingleRequest); +} + +void QGeoPositionInfoSourceAndroid::setUpdateInterval(int msec) +{ + int previousInterval = updateInterval(); + msec = (((msec > 0) && (msec < minimumUpdateInterval())) || msec < 0)? minimumUpdateInterval() : msec; + + if (msec == previousInterval) + return; + + QGeoPositionInfoSource::setUpdateInterval(msec); + + if (updatesRunning) + reconfigureRunningSystem(); +} + +QGeoPositionInfo QGeoPositionInfoSourceAndroid::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const +{ + return AndroidPositioning::lastKnownPosition(fromSatellitePositioningMethodsOnly); +} + +QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSourceAndroid::supportedPositioningMethods() const +{ + return AndroidPositioning::availableProviders(); +} + +void QGeoPositionInfoSourceAndroid::setPreferredPositioningMethods(QGeoPositionInfoSource::PositioningMethods methods) +{ + PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods(); + QGeoPositionInfoSource::setPreferredPositioningMethods(methods); + if (previousPreferredPositioningMethods == preferredPositioningMethods()) + return; + + if (updatesRunning) + reconfigureRunningSystem(); +} + +int QGeoPositionInfoSourceAndroid::minimumUpdateInterval() const +{ + return 50; +} + +QGeoPositionInfoSource::Error QGeoPositionInfoSourceAndroid::error() const +{ + return m_error; +} + +void QGeoPositionInfoSourceAndroid::setError(Error error) +{ + // qDebug() << "setError: " << error; + if (error != QGeoPositionInfoSource::NoError) + { + m_error = error; + emit QGeoPositionInfoSource::error(m_error); + } +} + +void QGeoPositionInfoSourceAndroid::startUpdates() +{ + if (updatesRunning) + return; + + if (preferredPositioningMethods() == 0) { + setError(UnknownSourceError); + return; + } + + updatesRunning = true; + QGeoPositionInfoSource::Error error = AndroidPositioning::startUpdates(androidClassKeyForUpdate); + if (error != QGeoPositionInfoSource::NoError) + updatesRunning = false; + + setError(error); +} + +void QGeoPositionInfoSourceAndroid::stopUpdates() +{ + if (!updatesRunning) + return; + + updatesRunning = false; + AndroidPositioning::stopUpdates(androidClassKeyForUpdate); +} + +void QGeoPositionInfoSourceAndroid::requestUpdate(int timeout) +{ + if (m_requestTimer.isActive()) + return; + + if (timeout != 0 && timeout < minimumUpdateInterval()) { + emit updateTimeout(); + return; + } + + if (timeout == 0) + timeout = UPDATE_FROM_COLD_START; + + m_requestTimer.start(timeout); + + // if updates already running with interval equal to timeout + // then we wait for next update coming through + // assume that a single update will not be quicker than regular updates anyway + if (updatesRunning && updateInterval() <= timeout) + return; + + QGeoPositionInfoSource::Error error = AndroidPositioning::requestUpdate(androidClassKeyForSingleRequest); + if (error != QGeoPositionInfoSource::NoError) + m_requestTimer.stop(); + + setError(error); +} + +void QGeoPositionInfoSourceAndroid::processPositionUpdate(const QGeoPositionInfo &pInfo) +{ + //single update request and served as part of regular update + if (m_requestTimer.isActive()) + m_requestTimer.stop(); + + emit positionUpdated(pInfo); +} + +// Might still be called multiple times (once for each provider) +void QGeoPositionInfoSourceAndroid::processSinglePositionUpdate(const QGeoPositionInfo &pInfo) +{ + //timeout but we received a late update -> ignore + if (!m_requestTimer.isActive()) + return; + + queuedSingleUpdates.append(pInfo); +} + +void QGeoPositionInfoSourceAndroid::locationProviderDisabled() +{ + setError(QGeoPositionInfoSource::ClosedError); +} + +void QGeoPositionInfoSourceAndroid::requestTimeout() +{ + AndroidPositioning::stopUpdates(androidClassKeyForSingleRequest); + //no queued update to process -> timeout + const int count = queuedSingleUpdates.count(); + + if (!count) { + emit updateTimeout(); + return; + } + + //pick best + QGeoPositionInfo best = queuedSingleUpdates[0]; + for (int i = 1; i < count; i++) { + const QGeoPositionInfo info = queuedSingleUpdates[i]; + + //anything newer by 20s is always better + const int timeDelta = best.timestamp().secsTo(info.timestamp()); + if (abs(timeDelta) > 20) { + if (timeDelta > 0) + best = info; + continue; + } + + //compare accuracy + if (info.hasAttribute(QGeoPositionInfo::HorizontalAccuracy) && + info.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) + { + best = info.attribute(QGeoPositionInfo::HorizontalAccuracy) < + best.attribute(QGeoPositionInfo::HorizontalAccuracy) ? info : best; + continue; + } + + //prefer info with accuracy information + if (info.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) + best = info; + } + + queuedSingleUpdates.clear(); + emit positionUpdated(best); +} + +/* + Updates the system assuming that updateInterval + and/or preferredPositioningMethod have changed. + */ +void QGeoPositionInfoSourceAndroid::reconfigureRunningSystem() +{ + if (!updatesRunning) + return; + + stopUpdates(); + startUpdates(); +} diff --git a/src/plugins/position/android/src/qgeopositioninfosource_android_p.h b/src/plugins/position/android/src/qgeopositioninfosource_android_p.h new file mode 100644 index 0000000..dbb27f8 --- /dev/null +++ b/src/plugins/position/android/src/qgeopositioninfosource_android_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCE_ANDROID_P_H +#define QGEOPOSITIONINFOSOURCE_ANDROID_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +class QGeoPositionInfoSourceAndroid : public QGeoPositionInfoSource +{ + Q_OBJECT +public: + QGeoPositionInfoSourceAndroid(QObject *parent = 0); + ~QGeoPositionInfoSourceAndroid(); + + // From QGeoPositionInfoSource + void setUpdateInterval(int msec); + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const; + PositioningMethods supportedPositioningMethods() const; + void setPreferredPositioningMethods(PositioningMethods methods); + int minimumUpdateInterval() const; + Error error() const; + +public Q_SLOTS: + virtual void startUpdates(); + virtual void stopUpdates(); + + virtual void requestUpdate(int timeout = 0); + + void processPositionUpdate(const QGeoPositionInfo& pInfo); + void processSinglePositionUpdate(const QGeoPositionInfo& pInfo); + + void locationProviderDisabled(); +private Q_SLOTS: + void requestTimeout(); + +private: + void reconfigureRunningSystem(); + void setError(Error error); + + bool updatesRunning; + int androidClassKeyForUpdate; + int androidClassKeyForSingleRequest; + QList queuedSingleUpdates; + Error m_error; + QTimer m_requestTimer; +}; + +#endif // QGEOPOSITIONINFOSOURCE_ANDROID_P_H diff --git a/src/plugins/position/android/src/qgeosatelliteinfosource_android.cpp b/src/plugins/position/android/src/qgeosatelliteinfosource_android.cpp new file mode 100644 index 0000000..f89f666 --- /dev/null +++ b/src/plugins/position/android/src/qgeosatelliteinfosource_android.cpp @@ -0,0 +1,216 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qgeosatelliteinfosource_android_p.h" +#include "jnipositioning.h" + +Q_DECLARE_METATYPE(QGeoSatelliteInfo) +Q_DECLARE_METATYPE(QList) + +#define UPDATE_FROM_COLD_START 2*60*1000 + +QGeoSatelliteInfoSourceAndroid::QGeoSatelliteInfoSourceAndroid(QObject *parent) : + QGeoSatelliteInfoSource(parent), m_error(NoError), updatesRunning(false) +{ + qRegisterMetaType< QGeoSatelliteInfo >(); + qRegisterMetaType< QList >(); + androidClassKeyForUpdate = AndroidPositioning::registerPositionInfoSource(this); + androidClassKeyForSingleRequest = AndroidPositioning::registerPositionInfoSource(this); + + requestTimer.setSingleShot(true); + QObject::connect(&requestTimer, SIGNAL(timeout()), + this, SLOT(requestTimeout())); +} + +QGeoSatelliteInfoSourceAndroid::~QGeoSatelliteInfoSourceAndroid() +{ + stopUpdates(); + + if (requestTimer.isActive()) { + requestTimer.stop(); + AndroidPositioning::stopUpdates(androidClassKeyForSingleRequest); + } + + AndroidPositioning::unregisterPositionInfoSource(androidClassKeyForUpdate); + AndroidPositioning::unregisterPositionInfoSource(androidClassKeyForSingleRequest); +} + + +void QGeoSatelliteInfoSourceAndroid::setUpdateInterval(int msec) +{ + int previousInterval = updateInterval(); + msec = (((msec > 0) && (msec < minimumUpdateInterval())) || msec < 0)? minimumUpdateInterval() : msec; + + if (msec == previousInterval) + return; + + QGeoSatelliteInfoSource::setUpdateInterval(msec); + + if (updatesRunning) + reconfigureRunningSystem(); +} + +int QGeoSatelliteInfoSourceAndroid::minimumUpdateInterval() const +{ + return 50; +} + +QGeoSatelliteInfoSource::Error QGeoSatelliteInfoSourceAndroid::error() const +{ + return m_error; +} + +void QGeoSatelliteInfoSourceAndroid::startUpdates() +{ + if (updatesRunning) + return; + + updatesRunning = true; + + QGeoSatelliteInfoSource::Error error = AndroidPositioning::startSatelliteUpdates( + androidClassKeyForUpdate, false, updateInterval()); + if (error != QGeoSatelliteInfoSource::NoError) { + updatesRunning = false; + m_error = error; + emit QGeoSatelliteInfoSource::error(m_error); + } +} + +void QGeoSatelliteInfoSourceAndroid::stopUpdates() +{ + if (!updatesRunning) + return; + + updatesRunning = false; + AndroidPositioning::stopUpdates(androidClassKeyForUpdate); +} + +void QGeoSatelliteInfoSourceAndroid::requestUpdate(int timeout) +{ + if (requestTimer.isActive()) + return; + + if (timeout != 0 && timeout < minimumUpdateInterval()) { + emit requestTimeout(); + return; + } + + if (timeout == 0) + timeout = UPDATE_FROM_COLD_START; + + requestTimer.start(timeout); + + // if updates already running with interval equal or less then timeout + // then we wait for next update coming through + // assume that a single update will not be quicker than regular updates anyway + if (updatesRunning && updateInterval() <= timeout) + return; + + QGeoSatelliteInfoSource::Error error = AndroidPositioning::startSatelliteUpdates( + androidClassKeyForSingleRequest, true, timeout); + if (error != QGeoSatelliteInfoSource::NoError) { + requestTimer.stop(); + m_error = error; + emit QGeoSatelliteInfoSource::error(m_error); + } +} + +void QGeoSatelliteInfoSourceAndroid::processSatelliteUpdateInView(const QList &satsInView, bool isSingleUpdate) +{ + if (!isSingleUpdate) { + //if requested while regular updates were running + if (requestTimer.isActive()) + requestTimer.stop(); + emit QGeoSatelliteInfoSource::satellitesInViewUpdated(satsInView); + return; + } + + m_satsInView = satsInView; +} + +void QGeoSatelliteInfoSourceAndroid::processSatelliteUpdateInUse(const QList &satsInUse, bool isSingleUpdate) +{ + if (!isSingleUpdate) { + //if requested while regular updates were running + if (requestTimer.isActive()) + requestTimer.stop(); + emit QGeoSatelliteInfoSource::satellitesInUseUpdated(satsInUse); + return; + } + + m_satsInUse = satsInUse; +} + +void QGeoSatelliteInfoSourceAndroid::requestTimeout() +{ + AndroidPositioning::stopUpdates(androidClassKeyForSingleRequest); + + const int count = m_satsInView.count(); + if (!count) { + emit requestTimeout(); + return; + } + + emit QGeoSatelliteInfoSource::satellitesInViewUpdated(m_satsInView); + emit QGeoSatelliteInfoSource::satellitesInUseUpdated(m_satsInUse); + + m_satsInUse.clear(); + m_satsInView.clear(); +} + +/* + Updates the system assuming that updateInterval + and/or preferredPositioningMethod have changed. + */ +void QGeoSatelliteInfoSourceAndroid::reconfigureRunningSystem() +{ + if (!updatesRunning) + return; + + stopUpdates(); + startUpdates(); +} + +void QGeoSatelliteInfoSourceAndroid::locationProviderDisabled() +{ + m_error = QGeoSatelliteInfoSource::ClosedError; + emit QGeoSatelliteInfoSource::error(m_error); +} diff --git a/src/plugins/position/android/src/qgeosatelliteinfosource_android_p.h b/src/plugins/position/android/src/qgeosatelliteinfosource_android_p.h new file mode 100644 index 0000000..37c64ad --- /dev/null +++ b/src/plugins/position/android/src/qgeosatelliteinfosource_android_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QGEOSATELLITEINFOSOURCEANDROID_H +#define QGEOSATELLITEINFOSOURCEANDROID_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +class QGeoSatelliteInfoSourceAndroid : public QGeoSatelliteInfoSource +{ + Q_OBJECT +public: + explicit QGeoSatelliteInfoSourceAndroid(QObject *parent = 0); + ~QGeoSatelliteInfoSourceAndroid(); + + //From QGeoSatelliteInfoSource + void setUpdateInterval(int msec); + int minimumUpdateInterval() const; + + Error error() const; + +public Q_SLOTS: + void startUpdates(); + void stopUpdates(); + void requestUpdate(int timeout = 0); + + void processSatelliteUpdateInView(const QList &satsInView, bool isSingleUpdate); + void processSatelliteUpdateInUse(const QList &satsInUse, bool isSingleUpdate); + + void locationProviderDisabled(); +private Q_SLOTS: + void requestTimeout(); + +private: + void reconfigureRunningSystem(); + + Error m_error; + int androidClassKeyForUpdate; + int androidClassKeyForSingleRequest; + bool updatesRunning; + + QTimer requestTimer; + QList m_satsInUse; + QList m_satsInView; + +}; + +#endif // QGEOSATELLITEINFOSOURCEANDROID_H diff --git a/src/plugins/position/android/src/src.pro b/src/plugins/position/android/src/src.pro new file mode 100644 index 0000000..3a19c85 --- /dev/null +++ b/src/plugins/position/android/src/src.pro @@ -0,0 +1,21 @@ +TARGET = qtposition_android + +QT = core positioning + +HEADERS = \ + positionfactory_android.h \ + qgeopositioninfosource_android_p.h \ + jnipositioning.h \ + qgeosatelliteinfosource_android_p.h + +SOURCES = \ + positionfactory_android.cpp \ + qgeopositioninfosource_android.cpp \ + jnipositioning.cpp \ + qgeosatelliteinfosource_android.cpp + +OTHER_FILES = plugin.json + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = QGeoPositionInfoSourceFactoryAndroid +load(qt_plugin) diff --git a/src/plugins/position/corelocation/corelocation.pro b/src/plugins/position/corelocation/corelocation.pro new file mode 100644 index 0000000..dbb5b6b --- /dev/null +++ b/src/plugins/position/corelocation/corelocation.pro @@ -0,0 +1,22 @@ +TARGET = qtposition_cl + +QT = core positioning + +OBJECTIVE_SOURCES += \ + qgeopositioninfosource_cl.mm \ + qgeopositioninfosourcefactory_cl.mm + +HEADERS += \ + qgeopositioninfosource_cl_p.h \ + qgeopositioninfosourcefactory_cl.h + +OTHER_FILES += \ + plugin.json + +osx: LIBS += -framework Foundation +else: ios|tvos: LIBS += -framework CoreFoundation +LIBS += -framework CoreLocation + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = QGeoPositionInfoSourceFactoryCL +load(qt_plugin) diff --git a/src/plugins/position/corelocation/plugin.json b/src/plugins/position/corelocation/plugin.json new file mode 100644 index 0000000..58e3acd --- /dev/null +++ b/src/plugins/position/corelocation/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["corelocation"], + "Provider": "corelocation", + "Position": true, + "Satellite": false, + "Monitor" : false, + "Priority": 1000, + "Testable": false +} diff --git a/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm b/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm new file mode 100644 index 0000000..54a079a --- /dev/null +++ b/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include "qgeopositioninfosource_cl_p.h" + +#define MINIMUM_UPDATE_INTERVAL 1000 + +@interface PositionLocationDelegate : NSObject +{ + QGeoPositionInfoSourceCL *m_positionInfoSource; +} +@end + +@implementation PositionLocationDelegate +- (id)initWithInfoSource:(QGeoPositionInfoSourceCL*) positionInfoSource +{ + self = [super init]; + if (self) { + m_positionInfoSource = positionInfoSource; + } + return self; +} + +- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation +{ + Q_UNUSED(manager) + Q_UNUSED(oldLocation) + + // Convert location timestamp to QDateTime + QDateTime timeStamp; + NSTimeInterval locationTimeStamp = [newLocation.timestamp timeIntervalSince1970]; + timeStamp.setTime_t((uint) locationTimeStamp); + timeStamp.setTime(timeStamp.time().addMSecs((uint)(locationTimeStamp * 1000) % 1000)); + + // Construct position info from location data + QGeoPositionInfo location(QGeoCoordinate(newLocation.coordinate.latitude, + newLocation.coordinate.longitude, + newLocation.altitude), + timeStamp); + if (newLocation.horizontalAccuracy >= 0) + location.setAttribute(QGeoPositionInfo::HorizontalAccuracy, newLocation.horizontalAccuracy); + if (newLocation.verticalAccuracy >= 0) + location.setAttribute(QGeoPositionInfo::VerticalAccuracy, newLocation.verticalAccuracy); +#ifndef Q_OS_TVOS + if (newLocation.course >= 0) + location.setAttribute(QGeoPositionInfo::Direction, newLocation.course); + if (newLocation.speed >= 0) + location.setAttribute(QGeoPositionInfo::GroundSpeed, newLocation.speed); +#endif + + m_positionInfoSource->locationDataAvailable(location); +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error +{ + Q_UNUSED(manager) + m_positionInfoSource->setError(QGeoPositionInfoSource::AccessError); + + qWarning() << QString::fromNSString([error localizedDescription]); + + if ([error code] == 0 + && QString::fromNSString([error domain]) == QStringLiteral("kCLErrorDomain")) + qWarning() << "(is Wi-Fi turned on?)"; +} +@end + +QT_BEGIN_NAMESPACE + +QGeoPositionInfoSourceCL::QGeoPositionInfoSourceCL(QObject *parent) + : QGeoPositionInfoSource(parent) + , m_locationManager(0) + , m_started(false) + , m_updateTimer(0) + , m_updateTimeout(0) + , m_positionError(QGeoPositionInfoSource::NoError) +{ +} + +QGeoPositionInfoSourceCL::~QGeoPositionInfoSourceCL() +{ + stopUpdates(); + [m_locationManager release]; +} + +void QGeoPositionInfoSourceCL::setUpdateInterval(int msec) +{ + // If msec is 0 we send updates as data becomes available, otherwise we force msec to be equal + // to or larger than the minimum update interval. + if (msec != 0 && msec < minimumUpdateInterval()) + msec = minimumUpdateInterval(); + + QGeoPositionInfoSource::setUpdateInterval(msec); + + // Must timeout if update takes longer than specified interval + m_updateTimeout = msec; + if (m_started) setTimeoutInterval(m_updateTimeout); +} + +bool QGeoPositionInfoSourceCL::enableLocationManager() +{ + if (!m_locationManager) { + m_locationManager = [[CLLocationManager alloc] init]; + m_locationManager.desiredAccuracy = kCLLocationAccuracyBest; + m_locationManager.delegate = [[PositionLocationDelegate alloc] initWithInfoSource:this]; + + // These two methods are new in iOS 8. They require NSLocationAlwaysUsageDescription + // and NSLocationWhenInUseUsageDescription to be set in Info.plist to work (methods are + // noop if there are no such entries in plist). + if ([m_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) + [m_locationManager performSelector:@selector(requestAlwaysAuthorization)]; + if ([m_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) + [m_locationManager performSelector:@selector(requestWhenInUseAuthorization)]; + } + + return (m_locationManager != 0); +} + +void QGeoPositionInfoSourceCL::setTimeoutInterval(int msec) +{ + // Start timeout timer + if (m_updateTimer) killTimer(m_updateTimer); + if (msec > 0) m_updateTimer = startTimer(msec); + else m_updateTimer = 0; +} + +void QGeoPositionInfoSourceCL::startUpdates() +{ + if (enableLocationManager()) { +#ifdef Q_OS_TVOS + [m_locationManager requestLocation]; // service will run long enough for one location update +#else + [m_locationManager startUpdatingLocation]; +#endif + m_started = true; + + setTimeoutInterval(m_updateTimeout); + } else setError(QGeoPositionInfoSource::AccessError); +} + +void QGeoPositionInfoSourceCL::stopUpdates() +{ + if (m_locationManager) { + [m_locationManager stopUpdatingLocation]; + m_started = false; + + // Stop timeout timer + setTimeoutInterval(0); + } else setError(QGeoPositionInfoSource::AccessError); +} + +void QGeoPositionInfoSourceCL::requestUpdate(int timeout) +{ + // Get a single update within timeframe + if (timeout < minimumUpdateInterval() && timeout != 0) + emit updateTimeout(); + else if (enableLocationManager()) { + // This will force LM to generate a new update + [m_locationManager stopUpdatingLocation]; +#ifdef Q_OS_TVOS + [m_locationManager requestLocation]; // service will run long enough for one location update +#else + [m_locationManager startUpdatingLocation]; +#endif + + setTimeoutInterval(timeout); + } else setError(QGeoPositionInfoSource::AccessError); +} + +void QGeoPositionInfoSourceCL::timerEvent( QTimerEvent * event ) +{ + // Update timed out? + if (event->timerId() == m_updateTimer) { + emit updateTimeout(); + + // Only timeout once since last data + setTimeoutInterval(0); + + // Started for single update? + if (!m_started) stopUpdates(); + } +} + +QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSourceCL::supportedPositioningMethods() const +{ + // CoreLocation doesn't say which positioning method(s) it used + return QGeoPositionInfoSource::AllPositioningMethods; +} + +int QGeoPositionInfoSourceCL::minimumUpdateInterval() const +{ + return MINIMUM_UPDATE_INTERVAL; +} + +void QGeoPositionInfoSourceCL::locationDataAvailable(QGeoPositionInfo location) +{ + // Signal position data available + m_lastUpdate = location; + emit positionUpdated(location); + + // Started for single update? + if (!m_started) stopUpdates(); + // ...otherwise restart timeout timer + else setTimeoutInterval(m_updateTimeout); +} + +QGeoPositionInfo QGeoPositionInfoSourceCL::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const +{ + Q_UNUSED(fromSatellitePositioningMethodsOnly) + + return m_lastUpdate; +} + +QGeoPositionInfoSource::Error QGeoPositionInfoSourceCL::error() const +{ + return m_positionError; +} + +void QGeoPositionInfoSourceCL::setError(QGeoPositionInfoSource::Error positionError) +{ + m_positionError = positionError; + emit QGeoPositionInfoSource::error(positionError); +} + +#include "moc_qgeopositioninfosource_cl_p.cpp" + +QT_END_NAMESPACE diff --git a/src/plugins/position/corelocation/qgeopositioninfosource_cl_p.h b/src/plugins/position/corelocation/qgeopositioninfosource_cl_p.h new file mode 100644 index 0000000..cfd66bf --- /dev/null +++ b/src/plugins/position/corelocation/qgeopositioninfosource_cl_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCECL_H +#define QGEOPOSITIONINFOSOURCECL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#import + +#include "qgeopositioninfosource.h" +#include "qgeopositioninfo.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QGeoPositionInfoSourceCL : public QGeoPositionInfoSource +{ + Q_OBJECT +public: + QGeoPositionInfoSourceCL(QObject *parent = 0); + ~QGeoPositionInfoSourceCL(); + + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const; + PositioningMethods supportedPositioningMethods() const; + + void setUpdateInterval(int msec); + int minimumUpdateInterval() const; + Error error() const; + + void locationDataAvailable(QGeoPositionInfo location); + void setError(QGeoPositionInfoSource::Error positionError); + +private: + bool enableLocationManager(); + void setTimeoutInterval(int msec); + +public Q_SLOTS: + void startUpdates(); + void stopUpdates(); + + void requestUpdate(int timeout = 0); + +protected: + virtual void timerEvent(QTimerEvent *event); + +private: + Q_DISABLE_COPY(QGeoPositionInfoSourceCL); + CLLocationManager *m_locationManager; + bool m_started; + + QGeoPositionInfo m_lastUpdate; + + int m_updateTimer; + int m_updateTimeout; + + QGeoPositionInfoSource::Error m_positionError; +}; + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFOSOURCECL_H diff --git a/src/plugins/position/corelocation/qgeopositioninfosourcefactory_cl.h b/src/plugins/position/corelocation/qgeopositioninfosourcefactory_cl.h new file mode 100644 index 0000000..5ab1ce6 --- /dev/null +++ b/src/plugins/position/corelocation/qgeopositioninfosourcefactory_cl.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCEFACTORY_CL_H +#define QGEOPOSITIONINFOSOURCEFACTORY_CL_H + +#include +#include + +class QGeoPositionInfoSourceFactoryCL : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + Q_INTERFACES(QGeoPositionInfoSourceFactory) +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent); + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent); + QGeoAreaMonitorSource *areaMonitor(QObject *parent); +}; + +#endif // QGEOPOSITIONINFOSOURCEFACTORY_CL_H diff --git a/src/plugins/position/corelocation/qgeopositioninfosourcefactory_cl.mm b/src/plugins/position/corelocation/qgeopositioninfosourcefactory_cl.mm new file mode 100644 index 0000000..06a3ad3 --- /dev/null +++ b/src/plugins/position/corelocation/qgeopositioninfosourcefactory_cl.mm @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosource_cl_p.h" +#include "qgeopositioninfosourcefactory_cl.h" + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactoryCL::positionInfoSource(QObject *parent) +{ + return new QGeoPositionInfoSourceCL(parent); +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactoryCL::satelliteInfoSource(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactoryCL::areaMonitor(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} diff --git a/src/plugins/position/geoclue/geoclue.pro b/src/plugins/position/geoclue/geoclue.pro new file mode 100644 index 0000000..3f75cbf --- /dev/null +++ b/src/plugins/position/geoclue/geoclue.pro @@ -0,0 +1,38 @@ +TARGET = qtposition_geoclue + +QT = core positioning dbus + +HEADERS += \ + qgeopositioninfosource_geocluemaster.h \ + qgeosatelliteinfosource_geocluemaster.h \ + qgeopositioninfosourcefactory_geoclue.h \ + qgeocluemaster.h \ + geocluetypes.h + +SOURCES += \ + qgeopositioninfosource_geocluemaster.cpp \ + qgeosatelliteinfosource_geocluemaster.cpp \ + qgeopositioninfosourcefactory_geoclue.cpp \ + qgeocluemaster.cpp \ + geocluetypes.cpp + +QDBUSXML2CPP_INTERFACE_HEADER_FLAGS += "-N -i geocluetypes.h" +DBUS_INTERFACES += \ + org.freedesktop.Geoclue.MasterClient.xml \ + org.freedesktop.Geoclue.Master.xml \ + org.freedesktop.Geoclue.Position.xml \ + org.freedesktop.Geoclue.Velocity.xml \ + org.freedesktop.Geoclue.Satellite.xml \ + org.freedesktop.Geoclue.xml + +OTHER_FILES += \ + $$DBUS_INTERFACES + +INCLUDEPATH += $$QT.location.includes $$OUT_PWD + +OTHER_FILES += \ + plugin.json + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = QGeoPositionInfoSourceFactoryGeoclue +load(qt_plugin) diff --git a/src/plugins/position/geoclue/geocluetypes.cpp b/src/plugins/position/geoclue/geocluetypes.cpp new file mode 100644 index 0000000..d50e624 --- /dev/null +++ b/src/plugins/position/geoclue/geocluetypes.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "geocluetypes.h" + +const QDBusArgument &dbus_argument_helper(const QDBusArgument &arg, Accuracy &accuracy) +{ + arg.beginStructure(); + qint32 level; + arg >> level; + accuracy.m_level = static_cast(level); + arg >> accuracy.m_horizontal; + arg >> accuracy.m_vertical; + arg.endStructure(); + + return arg; +} + +QT_BEGIN_NAMESPACE + +QDBusArgument &operator<<(QDBusArgument &arg, const Accuracy &accuracy) +{ + arg.beginStructure(); + arg << qint32(accuracy.level()); + arg << accuracy.horizontal(); + arg << accuracy.vertical(); + arg.endStructure(); + + return arg; +} + +const QDBusArgument &operator>>(const QDBusArgument &arg, Accuracy &accuracy) +{ + return dbus_argument_helper(arg, accuracy); +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QGeoSatelliteInfo &si) +{ + qint32 a; + + argument.beginStructure(); + argument >> a; + si.setSatelliteIdentifier(a); + argument >> a; + si.setAttribute(QGeoSatelliteInfo::Elevation, a); + argument >> a; + si.setAttribute(QGeoSatelliteInfo::Azimuth, a); + argument >> a; + si.setSignalStrength(a); + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QList &sis) +{ + sis.clear(); + + argument.beginArray(); + while (!argument.atEnd()) { + QGeoSatelliteInfo si; + argument >> si; + sis.append(si); + } + argument.endArray(); + + return argument; +} + +QT_END_NAMESPACE diff --git a/src/plugins/position/geoclue/geocluetypes.h b/src/plugins/position/geoclue/geocluetypes.h new file mode 100644 index 0000000..0e87b73 --- /dev/null +++ b/src/plugins/position/geoclue/geocluetypes.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GEOCLUETYPES_H +#define GEOCLUETYPES_H + +#include +#include + +class Accuracy +{ +public: + enum Level { + None = 0, + Country, + Region, + Locality, + PostalCode, + Street, + Detailed + }; + + Accuracy() + : m_level(None), m_horizontal(0), m_vertical(0) + { + } + + inline Level level() const { return m_level; } + inline double horizontal() const { return m_horizontal; } + inline double vertical() const { return m_vertical; } + +private: + Level m_level; + double m_horizontal; + double m_vertical; + + friend const QDBusArgument &dbus_argument_helper(const QDBusArgument &arg, Accuracy &accuracy); +}; + +Q_DECLARE_METATYPE(Accuracy) +Q_DECLARE_METATYPE(QList) + + +QT_BEGIN_NAMESPACE + +Q_DECLARE_TYPEINFO(Accuracy, Q_MOVABLE_TYPE); + +QDBusArgument &operator<<(QDBusArgument &arg, const Accuracy &accuracy); +const QDBusArgument &operator>>(const QDBusArgument &arg, Accuracy &accuracy); + +const QDBusArgument &operator>>(const QDBusArgument &arg, QGeoSatelliteInfo &si); +const QDBusArgument &operator>>(const QDBusArgument &arg, QList &sis); + +QT_END_NAMESPACE + +#endif // GEOCLUETYPES_H + diff --git a/src/plugins/position/geoclue/org.freedesktop.Geoclue.Master.xml b/src/plugins/position/geoclue/org.freedesktop.Geoclue.Master.xml new file mode 100644 index 0000000..e7df140 --- /dev/null +++ b/src/plugins/position/geoclue/org.freedesktop.Geoclue.Master.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/plugins/position/geoclue/org.freedesktop.Geoclue.MasterClient.xml b/src/plugins/position/geoclue/org.freedesktop.Geoclue.MasterClient.xml new file mode 100644 index 0000000..29c9588 --- /dev/null +++ b/src/plugins/position/geoclue/org.freedesktop.Geoclue.MasterClient.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/position/geoclue/org.freedesktop.Geoclue.Position.xml b/src/plugins/position/geoclue/org.freedesktop.Geoclue.Position.xml new file mode 100644 index 0000000..ce5c80d --- /dev/null +++ b/src/plugins/position/geoclue/org.freedesktop.Geoclue.Position.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/position/geoclue/org.freedesktop.Geoclue.Satellite.xml b/src/plugins/position/geoclue/org.freedesktop.Geoclue.Satellite.xml new file mode 100644 index 0000000..2ed112c --- /dev/null +++ b/src/plugins/position/geoclue/org.freedesktop.Geoclue.Satellite.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + ' + + + + + diff --git a/src/plugins/position/geoclue/org.freedesktop.Geoclue.Velocity.xml b/src/plugins/position/geoclue/org.freedesktop.Geoclue.Velocity.xml new file mode 100644 index 0000000..a1be122 --- /dev/null +++ b/src/plugins/position/geoclue/org.freedesktop.Geoclue.Velocity.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/position/geoclue/org.freedesktop.Geoclue.xml b/src/plugins/position/geoclue/org.freedesktop.Geoclue.xml new file mode 100644 index 0000000..c9b6f63 --- /dev/null +++ b/src/plugins/position/geoclue/org.freedesktop.Geoclue.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/position/geoclue/plugin.json b/src/plugins/position/geoclue/plugin.json new file mode 100644 index 0000000..82f8afc --- /dev/null +++ b/src/plugins/position/geoclue/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["geoclue"], + "Provider": "geoclue", + "Position": true, + "Satellite": true, + "Monitor": false, + "Priority": 999, + "Testable": false +} diff --git a/src/plugins/position/geoclue/qgeocluemaster.cpp b/src/plugins/position/geoclue/qgeocluemaster.cpp new file mode 100644 index 0000000..962cc7f --- /dev/null +++ b/src/plugins/position/geoclue/qgeocluemaster.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd, author: Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocluemaster.h" + +#include +#include +#include + +#include + +Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue) + +QT_BEGIN_NAMESPACE + +QGeoclueMaster::QGeoclueMaster(QObject *parent) +: QObject(parent), m_master(0), m_provider(0), m_client(0) +{ +} + +QGeoclueMaster::~QGeoclueMaster() +{ + releaseMasterClient(); + + delete m_master; +} + +bool QGeoclueMaster::hasMasterClient() const +{ + return m_client; +} + +bool QGeoclueMaster::createMasterClient(Accuracy::Level accuracyLevel, ResourceFlags resourceFlags) +{ + Q_ASSERT(!m_provider || !m_client); + + if (!m_master) { + qCDebug(lcPositioningGeoclue) << "creating master interface"; + m_master = new OrgFreedesktopGeoclueMasterInterface(QStringLiteral("org.freedesktop.Geoclue.Master"), + QStringLiteral("/org/freedesktop/Geoclue/Master"), + QDBusConnection::sessionBus()); + } + + qCDebug(lcPositioningGeoclue) << "creating client"; + QDBusPendingReply client = m_master->Create(); + if (client.isError()) { + QDBusError e = client.error(); + qCritical("Failed to create Geoclue client interface. Geoclue error: %s", + qPrintable(e.errorString(e.type()))); + return false; + } + + qCDebug(lcPositioningGeoclue) << "Geoclue client path:" << client.value().path(); + + m_provider = new OrgFreedesktopGeoclueInterface(QStringLiteral("org.freedesktop.Geoclue.Master"), + client.value().path(), QDBusConnection::sessionBus()); + m_provider->AddReference(); + + m_client = new OrgFreedesktopGeoclueMasterClientInterface(QStringLiteral("org.freedesktop.Geoclue.Master"), + client.value().path(), + QDBusConnection::sessionBus()); + + connect(m_client, SIGNAL(PositionProviderChanged(QString,QString,QString,QString)), + this, SIGNAL(positionProviderChanged(QString,QString,QString,QString))); + + QDBusPendingReply<> reply = m_client->SetRequirements(accuracyLevel, 0, true, resourceFlags); + if (reply.isError()) { + QDBusError e = reply.error(); + qCritical("Failed to set Geoclue positioning requirements. Geoclue error: %s", + qPrintable(e.errorString(e.type()))); + + releaseMasterClient(); + return false; + } + + // Need to create the master position interface even though it will not be used, otherwise + // GetPositionProvider always returns empty strings. + reply = m_client->PositionStart(); + if (reply.isError()) { + QDBusError e = reply.error(); + qCritical("Failed to start positioning. Geoclue error: %s", + qPrintable(e.errorString(e.type()))); + + releaseMasterClient(); + return false; + } + + return true; +} + +void QGeoclueMaster::releaseMasterClient() +{ + if (m_provider) + m_provider->RemoveReference(); + delete m_provider; + m_provider = 0; + delete m_client; + m_client = 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/position/geoclue/qgeocluemaster.h b/src/plugins/position/geoclue/qgeocluemaster.h new file mode 100644 index 0000000..c623dbd --- /dev/null +++ b/src/plugins/position/geoclue/qgeocluemaster.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd, author: Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCLUEMASTER_H +#define QGEOCLUEMASTER_H + +#include "geocluetypes.h" + +#include + +class OrgFreedesktopGeoclueMasterInterface; +class OrgFreedesktopGeoclueInterface; +class OrgFreedesktopGeoclueMasterClientInterface; + +QT_BEGIN_NAMESPACE + +class QGeoclueMaster : public QObject +{ + Q_OBJECT + +public: + QGeoclueMaster(QObject *parent = 0); + ~QGeoclueMaster(); + + enum ResourceFlag + { + ResourceNone = 0, + ResourceNetwork = 1 << 0, + ResourceCell = 1 << 1, + ResourceGps = 1 << 2, + ResourceAll = (1 << 10) - 1 + }; + + Q_DECLARE_FLAGS(ResourceFlags, ResourceFlag) + + bool hasMasterClient() const; + bool createMasterClient(Accuracy::Level accuracyLevel, ResourceFlags resourceFlags); + void releaseMasterClient(); + +signals: + void positionProviderChanged(const QString &name, const QString &description, + const QString &service, const QString &path); + +private: + OrgFreedesktopGeoclueMasterInterface *m_master; + OrgFreedesktopGeoclueInterface *m_provider; + OrgFreedesktopGeoclueMasterClientInterface *m_client; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoclueMaster::ResourceFlags) + +QT_END_NAMESPACE + +#endif // QGEOCLUEMASTER_H diff --git a/src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp b/src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp new file mode 100644 index 0000000..1fba2e9 --- /dev/null +++ b/src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp @@ -0,0 +1,495 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosource_geocluemaster.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_DATASTREAM +#include +#endif + +Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue) + +#define MINIMUM_UPDATE_INTERVAL 1000 +#define UPDATE_TIMEOUT_COLD_START 120000 + +QT_BEGIN_NAMESPACE + +namespace +{ + +double knotsToMetersPerSecond(double knots) +{ + return knots * 1852.0 / 3600.0; +} + +} + +QGeoPositionInfoSourceGeoclueMaster::QGeoPositionInfoSourceGeoclueMaster(QObject *parent) +: QGeoPositionInfoSource(parent), m_master(new QGeoclueMaster(this)), m_provider(0), m_pos(0), + m_vel(0), m_lastVelocityIsFresh(false), m_regularUpdateTimedOut(false), m_lastVelocity(qQNaN()), + m_lastDirection(qQNaN()), m_lastClimb(qQNaN()), m_lastPositionFromSatellite(false), + m_running(false), m_error(NoError) +{ + qDBusRegisterMetaType(); + +#ifndef QT_NO_DATASTREAM + // Load the last known location + QFile file(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + + QStringLiteral("/qtposition-geoclue")); + if (file.open(QIODevice::ReadOnly)) { + QDataStream out(&file); + out >> m_lastPosition; + } +#endif + + connect(m_master, SIGNAL(positionProviderChanged(QString,QString,QString,QString)), + this, SLOT(positionProviderChanged(QString,QString,QString,QString))); + + m_requestTimer.setSingleShot(true); + connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestUpdateTimeout())); + + setPreferredPositioningMethods(AllPositioningMethods); +} + +QGeoPositionInfoSourceGeoclueMaster::~QGeoPositionInfoSourceGeoclueMaster() +{ +#ifndef QT_NO_DATASTREAM + if (m_lastPosition.isValid()) { + QSaveFile file(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + + QStringLiteral("/qtposition-geoclue")); + if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + QDataStream out(&file); + // Only save position and timestamp. + out << QGeoPositionInfo(m_lastPosition.coordinate(), m_lastPosition.timestamp()); + file.commit(); + } + } +#endif + + cleanupPositionSource(); +} + +void QGeoPositionInfoSourceGeoclueMaster::positionUpdateFailed() +{ + qCDebug(lcPositioningGeoclue) << "position update failed."; + + m_lastVelocityIsFresh = false; + if (m_running && !m_regularUpdateTimedOut) { + m_regularUpdateTimedOut = true; + emit updateTimeout(); + } +} + +void QGeoPositionInfoSourceGeoclueMaster::updatePosition(PositionFields fields, int timestamp, + double latitude, double longitude, + double altitude, Accuracy accuracy) +{ + if (m_requestTimer.isActive()) + m_requestTimer.stop(); + + QGeoCoordinate coordinate(latitude, longitude); + if (fields & Altitude) + coordinate.setAltitude(altitude); + + m_lastPosition = QGeoPositionInfo(coordinate, QDateTime::fromTime_t(timestamp)); + + m_lastPositionFromSatellite = accuracy.level() == Accuracy::Detailed; + + if (!qIsNaN(accuracy.horizontal())) + m_lastPosition.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy.horizontal()); + if (!qIsNaN(accuracy.vertical())) + m_lastPosition.setAttribute(QGeoPositionInfo::VerticalAccuracy, accuracy.vertical()); + + if (m_lastVelocityIsFresh) { + if (!qIsNaN(m_lastVelocity)) + m_lastPosition.setAttribute(QGeoPositionInfo::GroundSpeed, m_lastVelocity); + if (!qIsNaN(m_lastDirection)) + m_lastPosition.setAttribute(QGeoPositionInfo::Direction, m_lastDirection); + if (!qIsNaN(m_lastClimb)) + m_lastPosition.setAttribute(QGeoPositionInfo::VerticalSpeed, m_lastClimb); + m_lastVelocityIsFresh = false; + } + + m_regularUpdateTimedOut = false; + + emit positionUpdated(m_lastPosition); + + qCDebug(lcPositioningGeoclue) << m_lastPosition; + + // Only stop positioning if regular updates not active. + if (!m_running) { + cleanupPositionSource(); + m_master->releaseMasterClient(); + } +} + +void QGeoPositionInfoSourceGeoclueMaster::velocityUpdateFailed() +{ + qCDebug(lcPositioningGeoclue) << "velocity update failed."; + + // Set the velocitydata non-fresh. + m_lastVelocityIsFresh = false; +} + +void QGeoPositionInfoSourceGeoclueMaster::updateVelocity(VelocityFields fields, int timestamp, + double speed, double direction, + double climb) +{ + Q_UNUSED(timestamp) + + // Store the velocity and mark it as fresh. Simple but hopefully adequate. + m_lastVelocity = (fields & Speed) ? knotsToMetersPerSecond(speed) : qQNaN(); + m_lastDirection = (fields & Direction) ? direction : qQNaN(); + m_lastClimb = (fields & Climb) ? climb : qQNaN(); + m_lastVelocityIsFresh = true; + + qCDebug(lcPositioningGeoclue) << m_lastVelocity << m_lastDirection << m_lastClimb; +} + +void QGeoPositionInfoSourceGeoclueMaster::cleanupPositionSource() +{ + qCDebug(lcPositioningGeoclue) << "cleaning up position source"; + + if (m_provider) + m_provider->RemoveReference(); + delete m_provider; + m_provider = 0; + delete m_pos; + m_pos = 0; + delete m_vel; + m_vel = 0; +} + +void QGeoPositionInfoSourceGeoclueMaster::setOptions() +{ + if (!m_provider) + return; + + QVariantMap options; + options.insert(QStringLiteral("UpdateInterval"), updateInterval()); + + m_provider->SetOptions(options); +} + +void QGeoPositionInfoSourceGeoclueMaster::setUpdateInterval(int msec) +{ + QGeoPositionInfoSource::setUpdateInterval(qMax(minimumUpdateInterval(), msec)); + setOptions(); +} + +void QGeoPositionInfoSourceGeoclueMaster::setPreferredPositioningMethods(PositioningMethods methods) +{ + PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods(); + QGeoPositionInfoSource::setPreferredPositioningMethods(methods); + if (previousPreferredPositioningMethods == preferredPositioningMethods()) + return; + + qCDebug(lcPositioningGeoclue) << "requested to set methods to" << methods + << ", and set them to:" << preferredPositioningMethods(); + + m_lastVelocityIsFresh = false; + m_regularUpdateTimedOut = false; + + // Don't start Geoclue provider until necessary. Don't currently have a master client, no need + // no recreate one. + if (!m_master->hasMasterClient()) + return; + + // Free potential previous sources, because new requirements can't be set for the client + // (creating a position object after changing requirements seems to fail). + cleanupPositionSource(); + m_master->releaseMasterClient(); + + // Restart Geoclue provider with new requirements. + configurePositionSource(); + setOptions(); +} + +QGeoPositionInfo QGeoPositionInfoSourceGeoclueMaster::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const +{ + if (fromSatellitePositioningMethodsOnly && !m_lastPositionFromSatellite) + return QGeoPositionInfo(); + + return m_lastPosition; +} + +QGeoPositionInfoSourceGeoclueMaster::PositioningMethods QGeoPositionInfoSourceGeoclueMaster::supportedPositioningMethods() const +{ + return AllPositioningMethods; +} + +void QGeoPositionInfoSourceGeoclueMaster::startUpdates() +{ + if (m_running) { + qCDebug(lcPositioningGeoclue) << "already running."; + return; + } + + m_running = true; + + qCDebug(lcPositioningGeoclue) << "starting updates"; + + // Start Geoclue provider. + if (!m_master->hasMasterClient()) { + configurePositionSource(); + setOptions(); + } + + // Emit last known position on start. + if (m_lastPosition.isValid()) { + QMetaObject::invokeMethod(this, "positionUpdated", Qt::QueuedConnection, + Q_ARG(QGeoPositionInfo, m_lastPosition)); + } +} + +int QGeoPositionInfoSourceGeoclueMaster::minimumUpdateInterval() const +{ + return MINIMUM_UPDATE_INTERVAL; +} + +void QGeoPositionInfoSourceGeoclueMaster::stopUpdates() +{ + if (!m_running) { + qCDebug(lcPositioningGeoclue) << "already stopped."; + return; + } + + qCDebug(lcPositioningGeoclue) << "stopping updates"; + + if (m_pos) { + disconnect(m_pos, SIGNAL(PositionChanged(qint32,qint32,double,double,double,Accuracy)), + this, SLOT(positionChanged(qint32,qint32,double,double,double,Accuracy))); + } + + if (m_vel) { + disconnect(m_vel, SIGNAL(VelocityChanged(qint32,qint32,double,double,double)), + this, SLOT(velocityChanged(qint32,qint32,double,double,double))); + } + + m_running = false; + + // Only stop positioning if single update not requested. + if (!m_requestTimer.isActive()) { + cleanupPositionSource(); + m_master->releaseMasterClient(); + } +} + +void QGeoPositionInfoSourceGeoclueMaster::requestUpdate(int timeout) +{ + if (timeout < minimumUpdateInterval() && timeout != 0) { + emit updateTimeout(); + return; + } + if (m_requestTimer.isActive()) { + qCDebug(lcPositioningGeoclue) << "request timer was active, ignoring startUpdates."; + return; + } + + if (!m_master->hasMasterClient()) { + configurePositionSource(); + setOptions(); + } + + // Create better logic for timeout value (specs leave it impl dependant). + // Especially if there are active updates ongoing, there is no point of waiting + // for whole cold start time. + m_requestTimer.start(timeout ? timeout : UPDATE_TIMEOUT_COLD_START); + + if (m_pos) { + QDBusPendingReply reply = m_pos->GetPosition(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(getPositionFinished(QDBusPendingCallWatcher*))); + } +} + +void QGeoPositionInfoSourceGeoclueMaster::positionProviderChanged(const QString &name, + const QString &description, + const QString &service, + const QString &path) +{ + Q_UNUSED(name) + Q_UNUSED(description) + + cleanupPositionSource(); + + if (service.isEmpty() || path.isEmpty()) { + if (!m_regularUpdateTimedOut) { + m_regularUpdateTimedOut = true; + emit updateTimeout(); + } + return; + } + + qCDebug(lcPositioningGeoclue) << "position provider changed to" << name; + + m_provider = new OrgFreedesktopGeoclueInterface(service, path, QDBusConnection::sessionBus()); + m_provider->AddReference(); + + m_pos = new OrgFreedesktopGeocluePositionInterface(service, path, QDBusConnection::sessionBus()); + + if (m_running) { + connect(m_pos, SIGNAL(PositionChanged(qint32,qint32,double,double,double,Accuracy)), + this, SLOT(positionChanged(qint32,qint32,double,double,double,Accuracy))); + } + + // Get the current position immediately. + QDBusPendingReply reply = m_pos->GetPosition(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(getPositionFinished(QDBusPendingCallWatcher*))); + + setOptions(); + + m_vel = new OrgFreedesktopGeoclueVelocityInterface(service, path, QDBusConnection::sessionBus()); + if (m_vel->isValid() && m_running) { + connect(m_vel, SIGNAL(VelocityChanged(qint32,qint32,double,double,double)), + this, SLOT(velocityChanged(qint32,qint32,double,double,double))); + } +} + +void QGeoPositionInfoSourceGeoclueMaster::requestUpdateTimeout() +{ + qCDebug(lcPositioningGeoclue) << "request update timeout occurred."; + + // If we end up here, there has not been valid position update. + emit updateTimeout(); + + // Only stop positioning if regular updates not active. + if (!m_running) { + cleanupPositionSource(); + m_master->releaseMasterClient(); + } +} + +void QGeoPositionInfoSourceGeoclueMaster::getPositionFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + + if (reply.isError()) + return; + + PositionFields fields = static_cast(reply.argumentAt<0>()); + + qCDebug(lcPositioningGeoclue) << "got position update with fields" << int(fields); + + if (fields & Latitude && fields & Longitude) { + qint32 timestamp = reply.argumentAt<1>(); + double latitude = reply.argumentAt<2>(); + double longitude = reply.argumentAt<3>(); + double altitude = reply.argumentAt<4>(); + Accuracy accuracy = reply.argumentAt<5>(); + updatePosition(fields, timestamp, latitude, longitude, altitude, accuracy); + } +} + +void QGeoPositionInfoSourceGeoclueMaster::positionChanged(qint32 fields, qint32 timestamp, double latitude, double longitude, double altitude, const Accuracy &accuracy) +{ + PositionFields pFields = static_cast(fields); + + qCDebug(lcPositioningGeoclue) << "position changed with fields" << fields; + + if (pFields & Latitude && pFields & Longitude) + updatePosition(pFields, timestamp, latitude, longitude, altitude, accuracy); + else + positionUpdateFailed(); +} + +void QGeoPositionInfoSourceGeoclueMaster::velocityChanged(qint32 fields, qint32 timestamp, double speed, double direction, double climb) +{ + VelocityFields vFields = static_cast(fields); + + if (vFields == NoVelocityFields) + velocityUpdateFailed(); + else + updateVelocity(vFields, timestamp, speed, direction, climb); +} + +void QGeoPositionInfoSourceGeoclueMaster::configurePositionSource() +{ + qCDebug(lcPositioningGeoclue); + + bool created = false; + + switch (preferredPositioningMethods()) { + case SatellitePositioningMethods: + created = m_master->createMasterClient(Accuracy::Detailed, QGeoclueMaster::ResourceGps); + break; + case NonSatellitePositioningMethods: + created = m_master->createMasterClient(Accuracy::None, QGeoclueMaster::ResourceCell | QGeoclueMaster::ResourceNetwork); + break; + case AllPositioningMethods: + created = m_master->createMasterClient(Accuracy::None, QGeoclueMaster::ResourceAll); + break; + default: + qWarning("QGeoPositionInfoSourceGeoclueMaster unknown preferred method."); + m_error = UnknownSourceError; + emit QGeoPositionInfoSource::error(m_error); + return; + } + + if (!created) { + m_error = UnknownSourceError; + emit QGeoPositionInfoSource::error(m_error); + } +} + +QGeoPositionInfoSource::Error QGeoPositionInfoSourceGeoclueMaster::error() const +{ + return m_error; +} + +QT_END_NAMESPACE diff --git a/src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.h b/src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.h new file mode 100644 index 0000000..f2d817b --- /dev/null +++ b/src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCE_GEOCLUEMASTER_H +#define QGEOPOSITIONINFOSOURCE_GEOCLUEMASTER_H + +#include "qgeocluemaster.h" +#include "geocluetypes.h" + +#include +#include + +class OrgFreedesktopGeoclueInterface; +class OrgFreedesktopGeocluePositionInterface; +class OrgFreedesktopGeoclueVelocityInterface; + +QT_BEGIN_NAMESPACE + +class QDBusPendingCallWatcher; + +class QGeoPositionInfoSourceGeoclueMaster : public QGeoPositionInfoSource +{ + Q_OBJECT + +public: + explicit QGeoPositionInfoSourceGeoclueMaster(QObject *parent = 0); + ~QGeoPositionInfoSourceGeoclueMaster(); + + // From QGeoPositionInfoSource + void setUpdateInterval(int msec); + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const; + PositioningMethods supportedPositioningMethods() const; + void setPreferredPositioningMethods(PositioningMethods methods); + int minimumUpdateInterval() const; + + Error error() const; + + virtual void startUpdates() Q_DECL_OVERRIDE; + virtual void stopUpdates() Q_DECL_OVERRIDE; + virtual void requestUpdate(int timeout = 5000) Q_DECL_OVERRIDE; + +private slots: + void positionProviderChanged(const QString &name, const QString &description, + const QString &service, const QString &path); + void requestUpdateTimeout(); + + void getPositionFinished(QDBusPendingCallWatcher *watcher); + void positionChanged(qint32 fields, qint32 timestamp, double latitude, double longitude, + double altitude, const Accuracy &accuracy); + void velocityChanged(qint32 fields, qint32 timestamp, double speed, double direction, + double climb); + +private: + void configurePositionSource(); + void cleanupPositionSource(); + void setOptions(); + + enum PositionField + { + NoPositionFields = 0, + Latitude = 1 << 0, + Longitude = 1 << 1, + Altitude = 1 << 2 + }; + Q_DECLARE_FLAGS(PositionFields, PositionField) + + void updatePosition(PositionFields fields, int timestamp, double latitude, + double longitude, double altitude, Accuracy accuracy); + void positionUpdateFailed(); + + enum VelocityField + { + NoVelocityFields = 0, + Speed = 1 << 0, + Direction = 1 << 1, + Climb = 1 << 2 + }; + Q_DECLARE_FLAGS(VelocityFields, VelocityField) + + void updateVelocity(VelocityFields fields, int timestamp, double speed, double direction, + double climb); + void velocityUpdateFailed(); + +private: + QGeoclueMaster *m_master; + + OrgFreedesktopGeoclueInterface *m_provider; + OrgFreedesktopGeocluePositionInterface *m_pos; + OrgFreedesktopGeoclueVelocityInterface *m_vel; + + QTimer m_requestTimer; + bool m_lastVelocityIsFresh; + bool m_regularUpdateTimedOut; + double m_lastVelocity; + double m_lastDirection; + double m_lastClimb; + bool m_lastPositionFromSatellite; + QGeoPositionInfo m_lastPosition; + bool m_running; + QGeoPositionInfoSource::Error m_error; +}; + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFOSOURCE_GEOCLUEMASTER_H diff --git a/src/plugins/position/geoclue/qgeopositioninfosourcefactory_geoclue.cpp b/src/plugins/position/geoclue/qgeopositioninfosourcefactory_geoclue.cpp new file mode 100644 index 0000000..9b39bfb --- /dev/null +++ b/src/plugins/position/geoclue/qgeopositioninfosourcefactory_geoclue.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosourcefactory_geoclue.h" +#include "qgeopositioninfosource_geocluemaster.h" +#include "qgeosatelliteinfosource_geocluemaster.h" + +#include + +Q_DECLARE_METATYPE(QGeoPositionInfo) + +Q_LOGGING_CATEGORY(lcPositioningGeoclue, "qt.positioning.geoclue") + +QT_BEGIN_NAMESPACE + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactoryGeoclue::positionInfoSource(QObject *parent) +{ + qRegisterMetaType(); + return new QGeoPositionInfoSourceGeoclueMaster(parent); +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactoryGeoclue::satelliteInfoSource(QObject *parent) +{ + return new QGeoSatelliteInfoSourceGeoclueMaster(parent); +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactoryGeoclue::areaMonitor(QObject *parent) +{ + Q_UNUSED(parent) + return 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/position/geoclue/qgeopositioninfosourcefactory_geoclue.h b/src/plugins/position/geoclue/qgeopositioninfosourcefactory_geoclue.h new file mode 100644 index 0000000..6125cb4 --- /dev/null +++ b/src/plugins/position/geoclue/qgeopositioninfosourcefactory_geoclue.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCEFACTORY_GEOCLUE_H +#define QGEOPOSITIONINFOSOURCEFACTORY_GEOCLUE_H + +#include +#include + +QT_BEGIN_NAMESPACE + +/* + Qt Positioning plugin for Geoclue. This plugin supports Geoclue version 0.12.99. +*/ +class QGeoPositionInfoSourceFactoryGeoclue : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + + Q_INTERFACES(QGeoPositionInfoSourceFactory) + +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent) Q_DECL_OVERRIDE; + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent) Q_DECL_OVERRIDE; + QGeoAreaMonitorSource *areaMonitor(QObject *parent) Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/position/geoclue/qgeosatelliteinfosource_geocluemaster.cpp b/src/plugins/position/geoclue/qgeosatelliteinfosource_geocluemaster.cpp new file mode 100644 index 0000000..3d70ea3 --- /dev/null +++ b/src/plugins/position/geoclue/qgeosatelliteinfosource_geocluemaster.cpp @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd, author: Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeosatelliteinfosource_geocluemaster.h" + +#include +#include + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue) + +#define MINIMUM_UPDATE_INTERVAL 1000 + +QT_BEGIN_NAMESPACE + +QGeoSatelliteInfoSourceGeoclueMaster::QGeoSatelliteInfoSourceGeoclueMaster(QObject *parent) +: QGeoSatelliteInfoSource(parent), m_master(new QGeoclueMaster(this)), m_provider(0), m_sat(0), + m_error(NoError), m_satellitesChangedConnected(false), m_running(false) +{ + connect(m_master, SIGNAL(positionProviderChanged(QString,QString,QString,QString)), + this, SLOT(positionProviderChanged(QString,QString,QString,QString))); + + m_requestTimer.setSingleShot(true); + connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestUpdateTimeout())); +} + +QGeoSatelliteInfoSourceGeoclueMaster::~QGeoSatelliteInfoSourceGeoclueMaster() +{ + cleanupSatelliteSource(); +} + +int QGeoSatelliteInfoSourceGeoclueMaster::minimumUpdateInterval() const +{ + return MINIMUM_UPDATE_INTERVAL; +} + +void QGeoSatelliteInfoSourceGeoclueMaster::setUpdateInterval(int msec) +{ + if (msec < 0 || (msec > 0 && msec < MINIMUM_UPDATE_INTERVAL)) + msec = MINIMUM_UPDATE_INTERVAL; + + QGeoSatelliteInfoSource::setUpdateInterval(msec); +} + +QGeoSatelliteInfoSource::Error QGeoSatelliteInfoSourceGeoclueMaster::error() const +{ + return m_error; +} + +void QGeoSatelliteInfoSourceGeoclueMaster::startUpdates() +{ + if (m_running) + return; + + m_running = true; + + // Start Geoclue provider. + if (!m_master->hasMasterClient()) + configureSatelliteSource(); + + m_requestTimer.start(updateInterval()); +} + +void QGeoSatelliteInfoSourceGeoclueMaster::stopUpdates() +{ + if (!m_running) + return; + + if (m_sat) { + disconnect(m_sat, SIGNAL(SatelliteChanged(qint32,qint32,qint32,QList,QList)), + this, SLOT(satelliteChanged(qint32,qint32,qint32,QList,QList))); + } + + m_running = false; + + // Only stop positioning if single update not requested. + if (!m_requestTimer.isActive()) { + cleanupSatelliteSource(); + m_master->releaseMasterClient(); + } +} + +void QGeoSatelliteInfoSourceGeoclueMaster::requestUpdate(int timeout) +{ + if (timeout < minimumUpdateInterval() && timeout != 0) { + emit requestTimeout(); + return; + } + + if (m_requestTimer.isActive()) + return; + + if (!m_master->hasMasterClient()) + configureSatelliteSource(); + + m_requestTimer.start(qMax(timeout, minimumUpdateInterval())); + + if (m_sat) { + QDBusPendingReply, QList > reply = + m_sat->GetSatellite(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(getSatelliteFinished(QDBusPendingCallWatcher*))); + } +} + +void QGeoSatelliteInfoSourceGeoclueMaster::updateSatelliteInfo(int timestamp, int satellitesUsed, + int satellitesVisible, + const QList &usedPrn, + const QList &satInfos) +{ + Q_UNUSED(timestamp) + + QList inUse; + + foreach (const QGeoSatelliteInfo &si, satInfos) + if (usedPrn.contains(si.satelliteIdentifier())) + inUse.append(si); + + if (satInfos.length() != satellitesVisible) { + qWarning("QGeoSatelliteInfoSourceGeoclueMaster number of in view QGeoSatelliteInfos (%d) " + "does not match expected number of in view satellites (%d).", satInfos.length(), + satellitesVisible); + } + + if (inUse.length() != satellitesUsed) { + qWarning("QGeoSatelliteInfoSourceGeoclueMaster number of in use QGeoSatelliteInfos (%d) " + "does not match expected number of in use satellites (%d).", inUse.length(), + satellitesUsed); + } + + m_inView = satInfos; + emit satellitesInViewUpdated(m_inView); + + m_inUse = inUse; + emit satellitesInUseUpdated(m_inUse); + + m_requestTimer.start(updateInterval()); +} + +void QGeoSatelliteInfoSourceGeoclueMaster::requestUpdateTimeout() +{ + // If we end up here, there has not been a valid satellite info update. + if (m_running) { + m_inView.clear(); + m_inUse.clear(); + emit satellitesInViewUpdated(m_inView); + emit satellitesInUseUpdated(m_inUse); + } else { + emit requestTimeout(); + + // Only stop satellite info if regular updates not active. + cleanupSatelliteSource(); + m_master->releaseMasterClient(); + } +} + +void QGeoSatelliteInfoSourceGeoclueMaster::getSatelliteFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply, QList > reply = *watcher; + watcher->deleteLater(); + + if (reply.isError()) + return; + + m_requestTimer.stop(); + updateSatelliteInfo(reply.argumentAt<0>(), reply.argumentAt<1>(), reply.argumentAt<2>(), + reply.argumentAt<3>(), reply.argumentAt<4>()); +} + +void QGeoSatelliteInfoSourceGeoclueMaster::satelliteChanged(int timestamp, int satellitesUsed, int satellitesVisible, const QList &usedPrn, const QList &satInfos) +{ + updateSatelliteInfo(timestamp, satellitesUsed, satellitesVisible, usedPrn, satInfos); +} + +void QGeoSatelliteInfoSourceGeoclueMaster::positionProviderChanged(const QString &name, + const QString &description, + const QString &service, + const QString &path) +{ + Q_UNUSED(name) + Q_UNUSED(description) + + cleanupSatelliteSource(); + + QString providerService; + QString providerPath; + + if (service.isEmpty() || path.isEmpty()) { + // No valid position provider has been selected. This probably means that the GPS provider + // has not yet obtained a position fix. It can still provide satellite information though. + if (!m_satellitesChangedConnected) { + QDBusConnection conn = QDBusConnection::sessionBus(); + conn.connect(QString(), QString(), QStringLiteral("org.freedesktop.Geoclue.Satellite"), + QStringLiteral("SatelliteChanged"), this, + SLOT(satelliteChanged(QDBusMessage))); + m_satellitesChangedConnected = true; + return; + } + } else { + if (m_satellitesChangedConnected) { + QDBusConnection conn = QDBusConnection::sessionBus(); + conn.disconnect(QString(), QString(), + QStringLiteral("org.freedesktop.Geoclue.Satellite"), + QStringLiteral("SatelliteChanged"), this, + SLOT(satelliteChanged(QDBusMessage))); + m_satellitesChangedConnected = false; + } + + providerService = service; + providerPath = path; + } + + if (providerService.isEmpty() || providerPath.isEmpty()) { + m_error = AccessError; + emit QGeoSatelliteInfoSource::error(m_error); + return; + } + + m_provider = new OrgFreedesktopGeoclueInterface(providerService, providerPath, QDBusConnection::sessionBus()); + m_provider->AddReference(); + + m_sat = new OrgFreedesktopGeoclueSatelliteInterface(providerService, providerPath, QDBusConnection::sessionBus()); + + if (m_running) { + connect(m_sat, SIGNAL(SatelliteChanged(qint32,qint32,qint32,QList,QList)), + this, SLOT(satelliteChanged(qint32,qint32,qint32,QList,QList))); + } +} + +void QGeoSatelliteInfoSourceGeoclueMaster::satelliteChanged(const QDBusMessage &message) +{ + QVariantList arguments = message.arguments(); + if (arguments.length() != 5) + return; + + int timestamp = arguments.at(0).toInt(); + int usedSatellites = arguments.at(1).toInt(); + int visibleSatellites = arguments.at(2).toInt(); + + QDBusArgument dbusArgument = arguments.at(3).value(); + + QList usedPrn; + dbusArgument >> usedPrn; + + dbusArgument = arguments.at(4).value(); + + QList satelliteInfos; + dbusArgument >> satelliteInfos; + + satelliteChanged(timestamp, usedSatellites, visibleSatellites, usedPrn, satelliteInfos); +} + +void QGeoSatelliteInfoSourceGeoclueMaster::configureSatelliteSource() +{ + if (!m_master->createMasterClient(Accuracy::Detailed, QGeoclueMaster::ResourceGps)) { + m_error = UnknownSourceError; + emit QGeoSatelliteInfoSource::error(m_error); + } +} + +void QGeoSatelliteInfoSourceGeoclueMaster::cleanupSatelliteSource() +{ + if (m_provider) + m_provider->RemoveReference(); + delete m_provider; + m_provider = 0; + delete m_sat; + m_sat = 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/position/geoclue/qgeosatelliteinfosource_geocluemaster.h b/src/plugins/position/geoclue/qgeosatelliteinfosource_geocluemaster.h new file mode 100644 index 0000000..45a0ed7 --- /dev/null +++ b/src/plugins/position/geoclue/qgeosatelliteinfosource_geocluemaster.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd, author: Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSATELLITEINFOSOURCE_GEOCLUEMASTER_H +#define QGEOSATELLITEINFOSOURCE_GEOCLUEMASTER_H + +#include "qgeocluemaster.h" + +#include +#include + +class OrgFreedesktopGeoclueInterface; +class OrgFreedesktopGeoclueSatelliteInterface; + +QT_BEGIN_NAMESPACE + +class QDBusMessage; +class QDBusPendingCallWatcher; + +class QGeoSatelliteInfoSourceGeoclueMaster : public QGeoSatelliteInfoSource +{ + Q_OBJECT + +public: + explicit QGeoSatelliteInfoSourceGeoclueMaster(QObject *parent = 0); + ~QGeoSatelliteInfoSourceGeoclueMaster(); + + int minimumUpdateInterval() const Q_DECL_OVERRIDE; + void setUpdateInterval(int msec) Q_DECL_OVERRIDE; + + Error error() const Q_DECL_OVERRIDE; + + void startUpdates() Q_DECL_OVERRIDE; + void stopUpdates() Q_DECL_OVERRIDE; + void requestUpdate(int timeout = 0) Q_DECL_OVERRIDE; + +private slots: + void positionProviderChanged(const QString &name, const QString &description, + const QString &service, const QString &path); + void requestUpdateTimeout(); + + void getSatelliteFinished(QDBusPendingCallWatcher *watcher); + void satelliteChanged(int timestamp, int satellitesUsed, int satellitesVisible, + const QList &usedPrn, const QList &satInfos); + void satelliteChanged(const QDBusMessage &message); + +private: + void configureSatelliteSource(); + void cleanupSatelliteSource(); + + void updateSatelliteInfo(int timestamp, int satellitesUsed, int satellitesVisible, + const QList &usedPrn, const QList &satInfos); + + QGeoclueMaster *m_master; + + OrgFreedesktopGeoclueInterface *m_provider; + OrgFreedesktopGeoclueSatelliteInterface *m_sat; + + QTimer m_requestTimer; + QList m_inView; + QList m_inUse; + Error m_error; + bool m_satellitesChangedConnected; + bool m_running; +}; + +QT_END_NAMESPACE + +#endif // QGEOSATELLITEINFOSOURCE_GEOCLUEMASTER_H diff --git a/src/plugins/position/gypsy/gypsy.pro b/src/plugins/position/gypsy/gypsy.pro new file mode 100644 index 0000000..be65f18 --- /dev/null +++ b/src/plugins/position/gypsy/gypsy.pro @@ -0,0 +1,21 @@ +TARGET = qtposition_gypsy + +QT = core positioning + +HEADERS += \ + qgeosatelliteinfosource_gypsy_p.h \ + qgeopositioninfosourcefactory_gypsy.h + +SOURCES += \ + qgeosatelliteinfosource_gypsy.cpp \ + qgeopositioninfosourcefactory_gypsy.cpp + +CONFIG += link_pkgconfig +PKGCONFIG += gypsy gconf-2.0 + +OTHER_FILES += \ + plugin.json + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = QGeoPositionInfoSourceFactoryGypsy +load(qt_plugin) diff --git a/src/plugins/position/gypsy/plugin.json b/src/plugins/position/gypsy/plugin.json new file mode 100644 index 0000000..9cef03f --- /dev/null +++ b/src/plugins/position/gypsy/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["gypsy"], + "Provider": "gypsy", + "Position": false, + "Satellite": true, + "Monitor" : false, + "Priority": 1000, + "Testable": false +} diff --git a/src/plugins/position/gypsy/qgeopositioninfosourcefactory_gypsy.cpp b/src/plugins/position/gypsy/qgeopositioninfosourcefactory_gypsy.cpp new file mode 100644 index 0000000..ecb1b2e --- /dev/null +++ b/src/plugins/position/gypsy/qgeopositioninfosourcefactory_gypsy.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosourcefactory_gypsy.h" +#include "qgeosatelliteinfosource_gypsy_p.h" + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactoryGypsy::positionInfoSource(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactoryGypsy::satelliteInfoSource(QObject *parent) +{ + QGeoSatelliteInfoSourceGypsy *src = new QGeoSatelliteInfoSourceGypsy(parent); + if (src->init() < 0) { + delete src; + src = 0; + } + return src; +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactoryGypsy::areaMonitor(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} diff --git a/src/plugins/position/gypsy/qgeopositioninfosourcefactory_gypsy.h b/src/plugins/position/gypsy/qgeopositioninfosourcefactory_gypsy.h new file mode 100644 index 0000000..fa18822 --- /dev/null +++ b/src/plugins/position/gypsy/qgeopositioninfosourcefactory_gypsy.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCEFACTORY_GYPSY_H +#define QGEOPOSITIONINFOSOURCEFACTORY_GYPSY_H + +#include +#include + +class QGeoPositionInfoSourceFactoryGypsy : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + Q_INTERFACES(QGeoPositionInfoSourceFactory) + +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent); + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent); + QGeoAreaMonitorSource *areaMonitor(QObject *parent); +}; + +#endif diff --git a/src/plugins/position/gypsy/qgeosatelliteinfosource_gypsy.cpp b/src/plugins/position/gypsy/qgeosatelliteinfosource_gypsy.cpp new file mode 100644 index 0000000..4e78265 --- /dev/null +++ b/src/plugins/position/gypsy/qgeosatelliteinfosource_gypsy.cpp @@ -0,0 +1,374 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeosatelliteinfosource_gypsy_p.h" + +#ifdef Q_LOCATION_GYPSY_DEBUG +#include +#endif +#include + +QT_BEGIN_NAMESPACE + +#define UPDATE_TIMEOUT_COLD_START 120000 + + +// Callback function for 'satellites-changed' -signal +static void satellites_changed (GypsySatellite *satellite, + GPtrArray *satellites, + gpointer userdata) +{ +#ifdef Q_LOCATION_GYPSY_DEBUG + qDebug() << "QGeoSatelliteInfoSourceGypsy Gypsy satellites-changed -signal received."; +#endif + ((QGeoSatelliteInfoSourceGypsy *)userdata)->satellitesChanged(satellite, satellites); +} + +SatelliteGypsyEngine::SatelliteGypsyEngine(QGeoSatelliteInfoSource *parent) : + m_owner(parent) +{ +} +SatelliteGypsyEngine::~SatelliteGypsyEngine() +{ +} + +// Glib symbols +gulong SatelliteGypsyEngine::eng_g_signal_connect(gpointer instance, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data) +{ + return ::g_signal_connect(instance, detailed_signal, c_handler, data); +} +guint SatelliteGypsyEngine::eng_g_signal_handlers_disconnect_by_func (gpointer instance, + gpointer func, + gpointer data) +{ + return ::g_signal_handlers_disconnect_by_func(instance, func, data); +} + +void SatelliteGypsyEngine::eng_g_free(gpointer mem) +{ + return ::g_free(mem); +} +// Gypsy symbols +GypsyControl *SatelliteGypsyEngine::eng_gypsy_control_get_default (void) +{ + return ::gypsy_control_get_default(); +} +char *SatelliteGypsyEngine::eng_gypsy_control_create (GypsyControl *control, const char *device_name, GError **error) +{ + return ::gypsy_control_create(control, device_name, error); +} +GypsyDevice *SatelliteGypsyEngine::eng_gypsy_device_new (const char *object_path) +{ + return ::gypsy_device_new(object_path); +} +GypsySatellite *SatelliteGypsyEngine::eng_gypsy_satellite_new (const char *object_path) +{ + return ::gypsy_satellite_new (object_path); +} +gboolean SatelliteGypsyEngine::eng_gypsy_device_start (GypsyDevice *device, GError **error) +{ + return ::gypsy_device_start(device, error); +} +gboolean SatelliteGypsyEngine::eng_gypsy_device_stop (GypsyDevice *device, GError **error) +{ + // Unfortunately this cannot be done; calling this will stop the GPS device + // (basically makes gypsy-daemon unusable for anyone), regardless of applications + // using it (see bug http://bugs.meego.com/show_bug.cgi?id=11707). + Q_UNUSED(device); + Q_UNUSED(error); + return true; + //return ::gypsy_device_stop (device, error); +} +GypsyDeviceFixStatus SatelliteGypsyEngine::eng_gypsy_device_get_fix_status (GypsyDevice *device, GError **error) +{ + return ::gypsy_device_get_fix_status (device, error); +} +GPtrArray *SatelliteGypsyEngine::eng_gypsy_satellite_get_satellites (GypsySatellite *satellite, GError **error) +{ + return ::gypsy_satellite_get_satellites (satellite, error); +} +void SatelliteGypsyEngine::eng_gypsy_satellite_free_satellite_array (GPtrArray *satellites) +{ + return ::gypsy_satellite_free_satellite_array(satellites); +} +// GConf symbols (mockability due to X11 requirement) +GConfClient *SatelliteGypsyEngine::eng_gconf_client_get_default(void) +{ + return ::gconf_client_get_default(); +} +gchar *SatelliteGypsyEngine::eng_gconf_client_get_string(GConfClient *client, const gchar *key, GError** err) +{ + return ::gconf_client_get_string(client, key, err); +} + +QGeoSatelliteInfoSourceGypsy::QGeoSatelliteInfoSourceGypsy(QObject *parent) : QGeoSatelliteInfoSource(parent), + m_engine(0), m_satellite(0), m_device(0), m_updatesOngoing(false), m_requestOngoing(false) +{ + m_requestTimer.setSingleShot(true); + QObject::connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestUpdateTimeout())); +} + +void QGeoSatelliteInfoSourceGypsy::createEngine() +{ + delete m_engine; + m_engine = new SatelliteGypsyEngine(this); +} + +QGeoSatelliteInfoSourceGypsy::~QGeoSatelliteInfoSourceGypsy() +{ + GError *error = NULL; + if (m_device) { + m_engine->eng_gypsy_device_stop (m_device, &error); + g_object_unref(m_device); + } + if (m_satellite) + g_object_unref(m_satellite); + if (error) + g_error_free(error); + delete m_engine; +} + +void QGeoSatelliteInfoSourceGypsy::satellitesChanged(GypsySatellite *satellite, + GPtrArray *satellites) +{ + if (!satellite || !satellites) + return; + // We have satellite data and assume it is valid. + // If a single updateRequest was active, send signals right away. + // If a periodic timer was running (meaning that the client wishes + // to have updates at defined intervals), store the data for later sending. + QList lastSatellitesInView; + QList lastSatellitesInUse; + + unsigned int i; + for (i = 0; i < satellites->len; i++) { + GypsySatelliteDetails *details = (GypsySatelliteDetails *)satellites->pdata[i]; + QGeoSatelliteInfo info; + info.setAttribute(QGeoSatelliteInfo::Elevation, details->elevation); + info.setAttribute(QGeoSatelliteInfo::Azimuth, details->azimuth); + info.setSignalStrength(details->snr); + if (details->in_use) + lastSatellitesInUse.append(info); + lastSatellitesInView.append(info); + } + bool sendUpdates(false); + // If a single updateRequest() has been issued: + if (m_requestOngoing) { + sendUpdates = true; + m_requestTimer.stop(); + m_requestOngoing = false; + // If there is no regular updates ongoing, disconnect now. + if (!m_updatesOngoing) { + m_engine->eng_g_signal_handlers_disconnect_by_func(G_OBJECT(m_satellite), (void *)satellites_changed, this); + } + } + // If regular updates are to be delivered as they come: + if (m_updatesOngoing) + sendUpdates = true; + + if (sendUpdates) { + emit satellitesInUseUpdated(lastSatellitesInUse); + emit satellitesInViewUpdated(lastSatellitesInView); + } +} + +int QGeoSatelliteInfoSourceGypsy::init() +{ + GError *error = NULL; + char *path; + GConfClient *client; + gchar *device_name; + + g_type_init (); + createEngine(); + + client = m_engine->eng_gconf_client_get_default(); + if (!client) { + qWarning ("QGeoSatelliteInfoSourceGypsy client creation failed."); + return -1; + } + device_name = m_engine->eng_gconf_client_get_string(client, "/apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice", NULL); + g_object_unref(client); + QString deviceName(QString::fromLatin1(device_name)); + if (deviceName.isEmpty() || + (deviceName.trimmed().at(0) == '/' && !QFile::exists(deviceName.trimmed()))) { + qWarning ("QGeoSatelliteInfoSourceGypsy Empty/nonexistent GPS device name detected."); + qWarning ("Use gconftool-2 to set it, e.g. on terminal: "); + qWarning ("gconftool-2 -t string -s /apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice /dev/ttyUSB0"); + m_engine->eng_g_free(device_name); + return -1; + } + GypsyControl *control = NULL; + control = m_engine->eng_gypsy_control_get_default(); + if (!control) { + qWarning("QGeoSatelliteInfoSourceGypsy unable to create Gypsy control."); + m_engine->eng_g_free(device_name); + return -1; + } + // (path is the DBus path) + path = m_engine->eng_gypsy_control_create (control, device_name, &error); + m_engine->eng_g_free(device_name); + g_object_unref(control); + if (!path) { + qWarning ("QGeoSatelliteInfoSourceGypsy error creating client."); + if (error) { + qWarning ("error message: %s", error->message); + g_error_free (error); + } + return -1; + } + m_device = m_engine->eng_gypsy_device_new (path); + m_satellite = m_engine->eng_gypsy_satellite_new (path); + m_engine->eng_g_free(path); + if (!m_device || !m_satellite) { + qWarning ("QGeoSatelliteInfoSourceGypsy error creating satellite device."); + qWarning ("Is GPS device set correctly? If not, use gconftool-2 to set it, e.g.: "); + qWarning ("gconftool-2 -t string -s /apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice /dev/ttyUSB0"); + if (m_device) + g_object_unref(m_device); + if (m_satellite) + g_object_unref(m_satellite); + return -1; + } + m_engine->eng_gypsy_device_start (m_device, &error); + if (error) { + qWarning ("QGeoSatelliteInfoSourceGypsy error starting device: %s ", + error->message); + g_error_free(error); + g_object_unref(m_device); + g_object_unref(m_satellite); + return -1; + } + return 0; +} + +int QGeoSatelliteInfoSourceGypsy::minimumUpdateInterval() const +{ + return 1; +} + +QGeoSatelliteInfoSource::Error QGeoSatelliteInfoSourceGypsy::error() const +{ + return NoError; +} + +void QGeoSatelliteInfoSourceGypsy::startUpdates() +{ + if (m_updatesOngoing) + return; + // If there is a request timer ongoing, we've connected to the signal already + if (!m_requestTimer.isActive()) { + m_engine->eng_g_signal_connect (m_satellite, "satellites-changed", + G_CALLBACK (satellites_changed), this); + } + m_updatesOngoing = true; +} + +void QGeoSatelliteInfoSourceGypsy::stopUpdates() +{ + if (!m_updatesOngoing) + return; + m_updatesOngoing = false; + // Disconnect only if there is no single update request ongoing. Once single update request + // is completed and it notices that there is no active update ongoing, it will disconnect + // the signal. + if (!m_requestTimer.isActive()) + m_engine->eng_g_signal_handlers_disconnect_by_func(G_OBJECT(m_satellite), (void *)satellites_changed, this); +} + +void QGeoSatelliteInfoSourceGypsy::requestUpdate(int timeout) +{ + if (m_requestOngoing) + return; + if (timeout < 0) { + emit requestTimeout(); + return; + } + m_requestOngoing = true; + GError *error = 0; + // If GPS has a fix a already, request current data. + GypsyDeviceFixStatus fixStatus = m_engine->eng_gypsy_device_get_fix_status(m_device, &error); + if (!error && (fixStatus != GYPSY_DEVICE_FIX_STATUS_INVALID && + fixStatus != GYPSY_DEVICE_FIX_STATUS_NONE)) { +#ifdef Q_LOCATION_GYPSY_DEBUG + qDebug() << "QGeoSatelliteInfoSourceGypsy fix available, requesting current satellite data"; +#endif + GPtrArray *satelliteData = m_engine->eng_gypsy_satellite_get_satellites(m_satellite, &error); + if (!error) { + // The fix was available and we have satellite data to deliver right away. + satellitesChanged(m_satellite, satelliteData); + m_engine->eng_gypsy_satellite_free_satellite_array(satelliteData); + return; + } + } + // No fix is available. If updates are not ongoing already, start them. + m_requestTimer.setInterval(timeout == 0? UPDATE_TIMEOUT_COLD_START: timeout); + if (!m_updatesOngoing) { + m_engine->eng_g_signal_connect (m_satellite, "satellites-changed", + G_CALLBACK (satellites_changed), this); + } + m_requestTimer.start(); + if (error) { +#ifdef Q_LOCATION_GYPSY_DEBUG + qDebug() << "QGeoSatelliteInfoSourceGypsy error asking fix status or satellite data: " << error->message; +#endif + g_error_free(error); + } +} + +void QGeoSatelliteInfoSourceGypsy::requestUpdateTimeout() +{ +#ifdef Q_LOCATION_GYPSY_DEBUG + qDebug("QGeoSatelliteInfoSourceGypsy request update timeout occurred."); +#endif + // If we end up here, there has not been valid satellite update. + // Emit timeout and disconnect from signal if regular updates are not + // ongoing (as we were listening just for one single requestUpdate). + if (!m_updatesOngoing) { + m_engine->eng_g_signal_handlers_disconnect_by_func(G_OBJECT(m_satellite), (void *)satellites_changed, this); + } + m_requestOngoing = false; + emit requestTimeout(); +} + +#include "moc_qgeosatelliteinfosource_gypsy_p.cpp" +QT_END_NAMESPACE diff --git a/src/plugins/position/gypsy/qgeosatelliteinfosource_gypsy_p.h b/src/plugins/position/gypsy/qgeosatelliteinfosource_gypsy_p.h new file mode 100644 index 0000000..ea6b6bc --- /dev/null +++ b/src/plugins/position/gypsy/qgeosatelliteinfosource_gypsy_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSATELLITEINFOSOURCE_GYPSY_H +#define QGEOSATELLITEINFOSOURCE_GYPSY_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeosatelliteinfosource.h" +#include "qgeosatelliteinfo.h" +#include +#include +#include +#include +#include + +// #define Q_LOCATION_GYPSY_DEBUG + +QT_BEGIN_NAMESPACE + +// An engine that encapsulates all symbols we want +// to be able to mock (for unit/autotest purposes). +class SatelliteGypsyEngine +{ +public: + SatelliteGypsyEngine(QGeoSatelliteInfoSource *parent = 0); + virtual ~SatelliteGypsyEngine(); + // Glib symbols + virtual gulong eng_g_signal_connect(gpointer instance, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data); + virtual guint eng_g_signal_handlers_disconnect_by_func(gpointer instance, + gpointer func, + gpointer data); + virtual void eng_g_free(gpointer mem); + // Gypsy symbols + virtual GypsyControl *eng_gypsy_control_get_default (void); + virtual char *eng_gypsy_control_create (GypsyControl *control, const char *device_name, GError **error); + virtual GypsyDevice *eng_gypsy_device_new (const char *object_path); + virtual GypsySatellite *eng_gypsy_satellite_new (const char *object_path); + virtual gboolean eng_gypsy_device_start (GypsyDevice *device, GError **error); + virtual gboolean eng_gypsy_device_stop (GypsyDevice *device, GError **error); + virtual GypsyDeviceFixStatus eng_gypsy_device_get_fix_status (GypsyDevice *device, GError **error); + virtual GPtrArray *eng_gypsy_satellite_get_satellites (GypsySatellite *satellite, GError **error); + virtual void eng_gypsy_satellite_free_satellite_array (GPtrArray *satellites); + // GConf symbols (mockability due to X11 requirement) + virtual GConfClient *eng_gconf_client_get_default(void); + virtual gchar *eng_gconf_client_get_string(GConfClient *client, const gchar *key, GError** err); +protected: + QGeoSatelliteInfoSource *m_owner; +}; + +class QGeoSatelliteInfoSourceGypsy : public QGeoSatelliteInfoSource + { + Q_OBJECT + +public: + explicit QGeoSatelliteInfoSourceGypsy(QObject *parent = 0); + ~QGeoSatelliteInfoSourceGypsy(); + int init(); + + int minimumUpdateInterval() const; + Error error() const; + +public slots: + virtual void startUpdates(); + void stopUpdates(); + void requestUpdate(int timeout = 5000); + void satellitesChanged(GypsySatellite *satellite, GPtrArray *satellites); + +signals: + void satellitesInViewUpdated(const QList &satellites); + void satellitesInUseUpdated(const QList &satellites); + +private slots: + void requestUpdateTimeout(); + +protected: + // Creates an engine which encapsulates all used symbols + // that we want to be also able to mock. + virtual void createEngine(); + SatelliteGypsyEngine *m_engine; + +private: + Q_DISABLE_COPY(QGeoSatelliteInfoSourceGypsy) + GypsySatellite *m_satellite; + GypsyDevice *m_device; + QTimer m_requestTimer; + bool m_updatesOngoing; + bool m_requestOngoing; + }; + +QT_END_NAMESPACE + +#endif // QGEOSATELLITEINFOSOURCE_GYPSY_H diff --git a/src/plugins/position/position.pro b/src/plugins/position/position.pro new file mode 100644 index 0000000..df1930b --- /dev/null +++ b/src/plugins/position/position.pro @@ -0,0 +1,12 @@ +TEMPLATE = subdirs + +qtHaveModule(dbus):SUBDIRS += geoclue +config_gypsy:SUBDIRS += gypsy +qtHaveModule(simulator):SUBDIRS += simulator +osx|ios|tvos:SUBDIRS += corelocation +android:SUBDIRS += android +winrt:SUBDIRS += winrt +win32:qtHaveModule(serialport):SUBDIRS += serialnmea + +SUBDIRS += \ + positionpoll diff --git a/src/plugins/position/positionpoll/plugin.json b/src/plugins/position/positionpoll/plugin.json new file mode 100644 index 0000000..df1f47d --- /dev/null +++ b/src/plugins/position/positionpoll/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["positionpoll"], + "Provider": "positionpoll", + "Position": false, + "Satellite": false, + "Monitor": true, + "Priority": 1000, + "Testable": true +} diff --git a/src/plugins/position/positionpoll/positionpoll.pro b/src/plugins/position/positionpoll/positionpoll.pro new file mode 100644 index 0000000..be60bf4 --- /dev/null +++ b/src/plugins/position/positionpoll/positionpoll.pro @@ -0,0 +1,18 @@ +TARGET = qtposition_positionpoll + +QT = core positioning + +SOURCES += \ + qgeoareamonitor_polling.cpp \ + positionpollfactory.cpp + +HEADERS += \ + qgeoareamonitor_polling.h \ + positionpollfactory.h + +OTHER_FILES += \ + plugin.json + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = QGeoPositionInfoSourceFactoryPoll +load(qt_plugin) diff --git a/src/plugins/position/positionpoll/positionpollfactory.cpp b/src/plugins/position/positionpoll/positionpollfactory.cpp new file mode 100644 index 0000000..20e2774 --- /dev/null +++ b/src/plugins/position/positionpoll/positionpollfactory.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "positionpollfactory.h" +#include "qgeoareamonitor_polling.h" + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactoryPoll::positionInfoSource(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactoryPoll::satelliteInfoSource(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactoryPoll::areaMonitor(QObject *parent) +{ + QGeoAreaMonitorPolling *ret = new QGeoAreaMonitorPolling(parent); + if (ret && ret->isValid()) + return ret; + delete ret; + return 0; +} diff --git a/src/plugins/position/positionpoll/positionpollfactory.h b/src/plugins/position/positionpoll/positionpollfactory.h new file mode 100644 index 0000000..6841fd0 --- /dev/null +++ b/src/plugins/position/positionpoll/positionpollfactory.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef POSITIONPOLLFACTORY_H +#define POSITIONPOLLFACTORY_H + +#include +#include + +class QGeoPositionInfoSourceFactoryPoll : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + Q_INTERFACES(QGeoPositionInfoSourceFactory) +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent); + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent); + QGeoAreaMonitorSource *areaMonitor(QObject *parent); +}; + +#endif // POSITIONPOLLFACTORY_H diff --git a/src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp b/src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp new file mode 100644 index 0000000..e39a621 --- /dev/null +++ b/src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp @@ -0,0 +1,497 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoareamonitor_polling.h" +#include +#include +#include + +#include +#include +#include +#include + +#define UPDATE_INTERVAL_5S 5000 + +typedef QHash MonitorTable; + + +static QMetaMethod areaEnteredSignal() +{ + static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::areaEntered); + return signal; +} + +static QMetaMethod areaExitedSignal() +{ + static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::areaExited); + return signal; +} + +static QMetaMethod monitorExpiredSignal() +{ + static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::monitorExpired); + return signal; +} + +class QGeoAreaMonitorPollingPrivate : public QObject +{ + Q_OBJECT +public: + QGeoAreaMonitorPollingPrivate() : source(0), mutex(QMutex::Recursive) + { + nextExpiryTimer = new QTimer(this); + nextExpiryTimer->setSingleShot(true); + connect(nextExpiryTimer, SIGNAL(timeout()), + this, SLOT(timeout())); + } + + void startMonitoring(const QGeoAreaMonitorInfo &monitor) + { + QMutexLocker locker(&mutex); + + activeMonitorAreas.insert(monitor.identifier(), monitor); + singleShotTrigger.remove(monitor.identifier()); + + checkStartStop(); + setupNextExpiryTimeout(); + } + + void requestUpdate(const QGeoAreaMonitorInfo &monitor, int signalId) + { + QMutexLocker locker(&mutex); + + activeMonitorAreas.insert(monitor.identifier(), monitor); + singleShotTrigger.insert(monitor.identifier(), signalId); + + checkStartStop(); + setupNextExpiryTimeout(); + } + + QGeoAreaMonitorInfo stopMonitoring(const QGeoAreaMonitorInfo &monitor) + { + QMutexLocker locker(&mutex); + + QGeoAreaMonitorInfo mon = activeMonitorAreas.take(monitor.identifier()); + + checkStartStop(); + setupNextExpiryTimeout(); + + return mon; + } + + void registerClient(QGeoAreaMonitorPolling *client) + { + QMutexLocker locker(&mutex); + + connect(this, SIGNAL(timeout(QGeoAreaMonitorInfo)), + client, SLOT(timeout(QGeoAreaMonitorInfo))); + + connect(this, SIGNAL(positionError(QGeoPositionInfoSource::Error)), + client, SLOT(positionError(QGeoPositionInfoSource::Error))); + + connect(this, SIGNAL(areaEventDetected(QGeoAreaMonitorInfo,QGeoPositionInfo,bool)), + client, SLOT(processAreaEvent(QGeoAreaMonitorInfo,QGeoPositionInfo,bool))); + + registeredClients.append(client); + } + + void deregisterClient(QGeoAreaMonitorPolling *client) + { + QMutexLocker locker(&mutex); + + registeredClients.removeAll(client); + if (registeredClients.isEmpty()) + checkStartStop(); + } + + void setPositionSource(QGeoPositionInfoSource *newSource) + { + QMutexLocker locker(&mutex); + + if (newSource == source) + return; + + if (source) + delete source; + + source = newSource; + + if (source) { + source->setParent(this); + source->moveToThread(this->thread()); + if (source->updateInterval() == 0) + source->setUpdateInterval(UPDATE_INTERVAL_5S); + disconnect(source, 0, 0, 0); //disconnect all + connect(source, SIGNAL(positionUpdated(QGeoPositionInfo)), + this, SLOT(positionUpdated(QGeoPositionInfo))); + connect(source, SIGNAL(error(QGeoPositionInfoSource::Error)), + this, SIGNAL(positionError(QGeoPositionInfoSource::Error))); + checkStartStop(); + } + } + + QGeoPositionInfoSource* positionSource() const + { + QMutexLocker locker(&mutex); + return source; + } + + MonitorTable activeMonitors() const + { + QMutexLocker locker(&mutex); + + return activeMonitorAreas; + } + + void checkStartStop() + { + QMutexLocker locker(&mutex); + + bool signalsConnected = false; + foreach (const QGeoAreaMonitorPolling *client, registeredClients) { + if (client->signalsAreConnected) { + signalsConnected = true; + break; + } + } + + if (signalsConnected && !activeMonitorAreas.isEmpty()) { + if (source) + source->startUpdates(); + else + //translated to InsufficientPositionInfo + emit positionError(QGeoPositionInfoSource::ClosedError); + } else { + if (source) + source->stopUpdates(); + } + } + +private: + void setupNextExpiryTimeout() + { + nextExpiryTimer->stop(); + activeExpiry.first = QDateTime(); + activeExpiry.second = QString(); + + foreach (const QGeoAreaMonitorInfo &info, activeMonitors()) { + if (info.expiration().isValid()) { + if (!activeExpiry.first.isValid()) { + activeExpiry.first = info.expiration(); + activeExpiry.second = info.identifier(); + continue; + } + if (info.expiration() < activeExpiry.first) { + activeExpiry.first = info.expiration(); + activeExpiry.second = info.identifier(); + } + } + } + + if (activeExpiry.first.isValid()) + nextExpiryTimer->start(QDateTime::currentDateTime().msecsTo(activeExpiry.first)); + } + + + //returns true if areaEntered should be emitted + bool processInsideArea(const QString &monitorIdent) + { + if (!insideArea.contains(monitorIdent)) { + if (singleShotTrigger.value(monitorIdent, -1) == areaEnteredSignal().methodIndex()) { + //this is the finishing singleshot event + singleShotTrigger.remove(monitorIdent); + activeMonitorAreas.remove(monitorIdent); + setupNextExpiryTimeout(); + } else { + insideArea.insert(monitorIdent); + } + return true; + } + + return false; + } + + //returns true if areaExited should be emitted + bool processOutsideArea(const QString &monitorIdent) + { + if (insideArea.contains(monitorIdent)) { + if (singleShotTrigger.value(monitorIdent, -1) == areaExitedSignal().methodIndex()) { + //this is the finishing singleShot event + singleShotTrigger.remove(monitorIdent); + activeMonitorAreas.remove(monitorIdent); + setupNextExpiryTimeout(); + } else { + insideArea.remove(monitorIdent); + } + return true; + } + return false; + } + + + +Q_SIGNALS: + void timeout(const QGeoAreaMonitorInfo &info); + void positionError(const QGeoPositionInfoSource::Error error); + void areaEventDetected(const QGeoAreaMonitorInfo &minfo, + const QGeoPositionInfo &pinfo, bool isEnteredEvent); +private Q_SLOTS: + void timeout() + { + /* + * Don't block timer firing even if monitorExpiredSignal is not connected. + * This allows us to continue to remove the existing monitors as they expire. + **/ + const QGeoAreaMonitorInfo info = activeMonitorAreas.take(activeExpiry.second); + setupNextExpiryTimeout(); + emit timeout(info); + + } + + void positionUpdated(const QGeoPositionInfo &info) + { + foreach (const QGeoAreaMonitorInfo &monInfo, activeMonitors()) { + const QString identifier = monInfo.identifier(); + if (monInfo.area().contains(info.coordinate())) { + if (processInsideArea(identifier)) + emit areaEventDetected(monInfo, info, true); + } else { + if (processOutsideArea(identifier)) + emit areaEventDetected(monInfo, info, false); + } + } + } + +private: + QPair activeExpiry; + QHash singleShotTrigger; + QTimer* nextExpiryTimer; + QSet insideArea; + + MonitorTable activeMonitorAreas; + + QGeoPositionInfoSource* source; + QList registeredClients; + mutable QMutex mutex; +}; + +Q_GLOBAL_STATIC(QGeoAreaMonitorPollingPrivate, pollingPrivate) + + +QGeoAreaMonitorPolling::QGeoAreaMonitorPolling(QObject *parent) + : QGeoAreaMonitorSource(parent), signalsAreConnected(false) +{ + d = pollingPrivate(); + lastError = QGeoAreaMonitorSource::NoError; + d->registerClient(this); + //hookup to default source if existing + if (!positionInfoSource()) + setPositionInfoSource(QGeoPositionInfoSource::createDefaultSource(this)); +} + +QGeoAreaMonitorPolling::~QGeoAreaMonitorPolling() +{ + d->deregisterClient(this); +} + +QGeoPositionInfoSource* QGeoAreaMonitorPolling::positionInfoSource() const +{ + return d->positionSource(); +} + +void QGeoAreaMonitorPolling::setPositionInfoSource(QGeoPositionInfoSource *source) +{ + d->setPositionSource(source); +} + +QGeoAreaMonitorSource::Error QGeoAreaMonitorPolling::error() const +{ + return lastError; +} + +bool QGeoAreaMonitorPolling::startMonitoring(const QGeoAreaMonitorInfo &monitor) +{ + if (!monitor.isValid()) + return false; + + //reject an expiry in the past + if (monitor.expiration().isValid() && + (monitor.expiration() < QDateTime::currentDateTime())) + return false; + + //don't accept persistent monitor since we don't support it + if (monitor.isPersistent()) + return false; + + //update or insert + d->startMonitoring(monitor); + + return true; +} + +int QGeoAreaMonitorPolling::idForSignal(const char *signal) +{ + const QByteArray sig = QMetaObject::normalizedSignature(signal + 1); + const QMetaObject * const mo = metaObject(); + + return mo->indexOfSignal(sig.constData()); +} + +bool QGeoAreaMonitorPolling::requestUpdate(const QGeoAreaMonitorInfo &monitor, const char *signal) +{ + if (!monitor.isValid()) + return false; + //reject an expiry in the past + if (monitor.expiration().isValid() && + (monitor.expiration() < QDateTime::currentDateTime())) + return false; + + //don't accept persistent monitor since we don't support it + if (monitor.isPersistent()) + return false; + + if (!signal) + return false; + + const int signalId = idForSignal(signal); + if (signalId < 0) + return false; + + //only accept area entered or exit signal + if (signalId != areaEnteredSignal().methodIndex() && + signalId != areaExitedSignal().methodIndex()) + { + return false; + } + + d->requestUpdate(monitor, signalId); + + return true; +} + +bool QGeoAreaMonitorPolling::stopMonitoring(const QGeoAreaMonitorInfo &monitor) +{ + QGeoAreaMonitorInfo info = d->stopMonitoring(monitor); + + return info.isValid(); +} + +QList QGeoAreaMonitorPolling::activeMonitors() const +{ + return d->activeMonitors().values(); +} + +QList QGeoAreaMonitorPolling::activeMonitors(const QGeoShape ®ion) const +{ + QList results; + if (region.isEmpty()) + return results; + + const MonitorTable list = d->activeMonitors(); + foreach (const QGeoAreaMonitorInfo &monitor, list) { + if (region.contains(monitor.area().center())) + results.append(monitor); + } + + return results; +} + +QGeoAreaMonitorSource::AreaMonitorFeatures QGeoAreaMonitorPolling::supportedAreaMonitorFeatures() const +{ + return 0; +} + +void QGeoAreaMonitorPolling::connectNotify(const QMetaMethod &/*signal*/) +{ + if (!signalsAreConnected && + (isSignalConnected(areaEnteredSignal()) || + isSignalConnected(areaExitedSignal())) ) + { + signalsAreConnected = true; + d->checkStartStop(); + } +} + +void QGeoAreaMonitorPolling::disconnectNotify(const QMetaMethod &/*signal*/) +{ + if (!isSignalConnected(areaEnteredSignal()) && + !isSignalConnected(areaExitedSignal())) + { + signalsAreConnected = false; + d->checkStartStop(); + } +} + +void QGeoAreaMonitorPolling::positionError(const QGeoPositionInfoSource::Error error) +{ + switch (error) { + case QGeoPositionInfoSource::AccessError: + lastError = QGeoAreaMonitorSource::AccessError; + break; + case QGeoPositionInfoSource::UnknownSourceError: + lastError = QGeoAreaMonitorSource::UnknownSourceError; + break; + case QGeoPositionInfoSource::ClosedError: + lastError = QGeoAreaMonitorSource::InsufficientPositionInfo; + break; + case QGeoPositionInfoSource::NoError: + return; + } + + emit QGeoAreaMonitorSource::error(lastError); +} + +void QGeoAreaMonitorPolling::timeout(const QGeoAreaMonitorInfo& monitor) +{ + if (isSignalConnected(monitorExpiredSignal())) + emit monitorExpired(monitor); +} + +void QGeoAreaMonitorPolling::processAreaEvent(const QGeoAreaMonitorInfo &minfo, + const QGeoPositionInfo &pinfo, bool isEnteredEvent) +{ + if (isEnteredEvent) + emit areaEntered(minfo, pinfo); + else + emit areaExited(minfo, pinfo); +} + +#include "qgeoareamonitor_polling.moc" +#include "moc_qgeoareamonitor_polling.cpp" diff --git a/src/plugins/position/positionpoll/qgeoareamonitor_polling.h b/src/plugins/position/positionpoll/qgeoareamonitor_polling.h new file mode 100644 index 0000000..c40d424 --- /dev/null +++ b/src/plugins/position/positionpoll/qgeoareamonitor_polling.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOAREAMONITORPOLLING_H +#define QGEOAREAMONITORPOLLING_H + +#include +#include + + +/** + * QGeoAreaMonitorPolling + * + */ + +class QGeoAreaMonitorPollingPrivate; +class QGeoAreaMonitorPolling : public QGeoAreaMonitorSource +{ + Q_OBJECT +public : + explicit QGeoAreaMonitorPolling(QObject *parent = 0); + ~QGeoAreaMonitorPolling(); + + void setPositionInfoSource(QGeoPositionInfoSource *source) Q_DECL_OVERRIDE; + QGeoPositionInfoSource* positionInfoSource() const Q_DECL_OVERRIDE; + + Error error() const Q_DECL_OVERRIDE; + + bool startMonitoring(const QGeoAreaMonitorInfo &monitor) Q_DECL_OVERRIDE; + bool requestUpdate(const QGeoAreaMonitorInfo &monitor, + const char *signal) Q_DECL_OVERRIDE; + bool stopMonitoring(const QGeoAreaMonitorInfo &monitor) Q_DECL_OVERRIDE; + + QList activeMonitors() const Q_DECL_OVERRIDE; + QList activeMonitors(const QGeoShape ®ion) const Q_DECL_OVERRIDE; + + QGeoAreaMonitorSource::AreaMonitorFeatures supportedAreaMonitorFeatures() const Q_DECL_OVERRIDE; + + inline bool isValid() { return positionInfoSource(); } + + bool signalsAreConnected; + +private Q_SLOTS: + void positionError(QGeoPositionInfoSource::Error error); + void timeout(const QGeoAreaMonitorInfo &monitor); + void processAreaEvent(const QGeoAreaMonitorInfo &minfo, const QGeoPositionInfo &pinfo, bool isEnteredEvent); + +private: + QGeoAreaMonitorPollingPrivate* d; + QGeoAreaMonitorSource::Error lastError; + + void connectNotify(const QMetaMethod &signal) Q_DECL_OVERRIDE; + void disconnectNotify(const QMetaMethod &signal) Q_DECL_OVERRIDE; + + int idForSignal(const char *signal); +}; + +#endif // QGEOAREAMONITORPOLLING_H diff --git a/src/plugins/position/serialnmea/plugin.json b/src/plugins/position/serialnmea/plugin.json new file mode 100644 index 0000000..826836c --- /dev/null +++ b/src/plugins/position/serialnmea/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["serialnmea"], + "Provider": "serialnmea", + "Position": true, + "Satellite": false, + "Monitor" : false, + "Priority": 1000, + "Testable": false +} diff --git a/src/plugins/position/serialnmea/qgeopositioninfosourcefactory_serialnmea.cpp b/src/plugins/position/serialnmea/qgeopositioninfosourcefactory_serialnmea.cpp new file mode 100644 index 0000000..1d1a6f4 --- /dev/null +++ b/src/plugins/position/serialnmea/qgeopositioninfosourcefactory_serialnmea.cpp @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosourcefactory_serialnmea.h" +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(lcSerial, "qt.positioning.serialnmea") + +class NmeaSource : public QNmeaPositionInfoSource +{ +public: + NmeaSource(QObject *parent); + bool isValid() const { return !m_port.isNull(); } + +private: + QScopedPointer m_port; +}; + +NmeaSource::NmeaSource(QObject *parent) + : QNmeaPositionInfoSource(RealTimeMode, parent), + m_port(new QSerialPort) +{ + QByteArray requestedPort = qgetenv("QT_NMEA_SERIAL_PORT"); + if (requestedPort.isEmpty()) { + const QList ports = QSerialPortInfo::availablePorts(); + qCDebug(lcSerial) << "Found" << ports.count() << "serial ports"; + if (ports.isEmpty()) { + qWarning("serialnmea: No serial ports found"); + m_port.reset(); + return; + } + + // Try to find a well-known device. + QSet supportedDevices; + supportedDevices << 0x67b; // GlobalSat (BU-353S4 and probably others) + supportedDevices << 0xe8d; // Qstarz MTK II + QString portName; + foreach (const QSerialPortInfo& port, ports) { + if (port.hasVendorIdentifier() && supportedDevices.contains(port.vendorIdentifier())) { + portName = port.portName(); + break; + } + } + + if (portName.isEmpty()) { + qWarning("serialnmea: No known GPS device found. Specify the COM port via QT_NMEA_SERIAL_PORT."); + m_port.reset(); + return; + } + + m_port->setPortName(portName); + } else { + m_port->setPortName(QString::fromUtf8(requestedPort)); + } + + m_port->setBaudRate(4800); + + qCDebug(lcSerial) << "Opening serial port" << m_port->portName(); + + if (!m_port->open(QIODevice::ReadOnly)) { + qWarning("serialnmea: Failed to open %s", qPrintable(m_port->portName())); + m_port.reset(); + return; + } + + setDevice(m_port.data()); + + qCDebug(lcSerial) << "Opened successfully"; +} + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactorySerialNmea::positionInfoSource(QObject *parent) +{ + QScopedPointer src(new NmeaSource(parent)); + return src->isValid() ? src.take() : Q_NULLPTR; +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactorySerialNmea::satelliteInfoSource(QObject *parent) +{ + Q_UNUSED(parent); + return Q_NULLPTR; +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactorySerialNmea::areaMonitor(QObject *parent) +{ + Q_UNUSED(parent); + return Q_NULLPTR; +} diff --git a/src/plugins/position/serialnmea/qgeopositioninfosourcefactory_serialnmea.h b/src/plugins/position/serialnmea/qgeopositioninfosourcefactory_serialnmea.h new file mode 100644 index 0000000..e372d56 --- /dev/null +++ b/src/plugins/position/serialnmea/qgeopositioninfosourcefactory_serialnmea.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCEFACTORY_SERIALNMEA_H +#define QGEOPOSITIONINFOSOURCEFACTORY_SERIALNMEA_H + +#include +#include + +class QGeoPositionInfoSourceFactorySerialNmea : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + Q_INTERFACES(QGeoPositionInfoSourceFactory) + +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent); + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent); + QGeoAreaMonitorSource *areaMonitor(QObject *parent); +}; + +#endif diff --git a/src/plugins/position/serialnmea/serialnmea.pro b/src/plugins/position/serialnmea/serialnmea.pro new file mode 100644 index 0000000..bdeb3f1 --- /dev/null +++ b/src/plugins/position/serialnmea/serialnmea.pro @@ -0,0 +1,16 @@ +TARGET = qtposition_serialnmea + +QT = core positioning serialport + +HEADERS += \ + qgeopositioninfosourcefactory_serialnmea.h + +SOURCES += \ + qgeopositioninfosourcefactory_serialnmea.cpp + +OTHER_FILES += \ + plugin.json + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = QGeoPositionInfoSourceFactorySerialNmea +load(qt_plugin) diff --git a/src/plugins/position/simulator/plugin.json b/src/plugins/position/simulator/plugin.json new file mode 100644 index 0000000..0935f4d --- /dev/null +++ b/src/plugins/position/simulator/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["simulator"], + "Provider": "simulator", + "Position": true, + "Satellite": true, + "Monitor" : false, + "Priority": 1000, + "Testable": true +} diff --git a/src/plugins/position/simulator/qgeopositioninfosource_simulator.cpp b/src/plugins/position/simulator/qgeopositioninfosource_simulator.cpp new file mode 100644 index 0000000..2942442 --- /dev/null +++ b/src/plugins/position/simulator/qgeopositioninfosource_simulator.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosource_simulator_p.h" +#include "qlocationdata_simulator_p.h" +#include "qlocationconnection_simulator_p.h" + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace Simulator +{ + QGeoPositionInfo toPositionInfo(const QGeoPositionInfoData &data) + { + QDateTime timestamp; + if (data.dateTime.isValid()) + timestamp = data.dateTime; + else + timestamp = QDateTime::currentDateTime(); + QGeoCoordinate coord(data.latitude, data.longitude, data.altitude); + QGeoPositionInfo info(coord, timestamp); + info.setAttribute(QGeoPositionInfo::Direction, data.direction); + info.setAttribute(QGeoPositionInfo::GroundSpeed, data.groundSpeed); + info.setAttribute(QGeoPositionInfo::VerticalSpeed, data.verticalSpeed); + info.setAttribute(QGeoPositionInfo::MagneticVariation, data.magneticVariation); + info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, data.horizontalAccuracy); + info.setAttribute(QGeoPositionInfo::VerticalAccuracy, data.verticalAccuracy); + return info; + } +} //namespace + +// Location API + +QGeoPositionInfoSourceSimulator::QGeoPositionInfoSourceSimulator(QObject *parent) + : QGeoPositionInfoSource(parent) + , timer(new QTimer(this)) + , requestTimer(new QTimer(this)) + , m_positionError(QGeoPositionInfoSource::NoError) +{ + Simulator::LocationConnection::ensureSimulatorConnection(); + + connect(timer, SIGNAL(timeout()), this, SLOT(updatePosition())); + requestTimer->setSingleShot(true); + connect(requestTimer, SIGNAL(timeout()), this, SLOT(updatePosition())); +} + +QGeoPositionInfoSourceSimulator::~QGeoPositionInfoSourceSimulator() +{ +} + +QGeoPositionInfo QGeoPositionInfoSourceSimulator::lastKnownPosition(bool /*fromSatellitePositioningMethodsOnly*/) const +{ + return lastPosition; +} + +QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSourceSimulator::supportedPositioningMethods() const +{ + // Is GPS now Satelite or not? Guessing so... + return QGeoPositionInfoSource::SatellitePositioningMethods; +} + +void QGeoPositionInfoSourceSimulator::setUpdateInterval(int msec) +{ + // If msec is 0 we send updates as data becomes available, otherwise we force msec to be equal + // to or larger than the minimum update interval. + if (msec != 0 && msec < minimumUpdateInterval()) + msec = minimumUpdateInterval(); + + QGeoPositionInfoSource::setUpdateInterval(msec); + if (timer->isActive()) { + timer->setInterval(msec); + timer->start(); + } +} + +int QGeoPositionInfoSourceSimulator::minimumUpdateInterval() const +{ + return qtPositionInfo()->minimumInterval; +} + +void QGeoPositionInfoSourceSimulator::startUpdates() +{ + int interval = updateInterval(); + if (interval < minimumUpdateInterval()) + interval = minimumUpdateInterval(); + timer->setInterval(interval); + timer->start(); +} + +void QGeoPositionInfoSourceSimulator::stopUpdates() +{ + timer->stop(); +} + +void QGeoPositionInfoSourceSimulator::requestUpdate(int timeout) +{ + if (!requestTimer->isActive()) { + // Get a single update within timeframe + if (timeout < minimumUpdateInterval() && timeout != 0) + emit updateTimeout(); + else { + requestTimer->start(timeout * qreal(0.75)); + } + } +} + +void QGeoPositionInfoSourceSimulator::updatePosition() +{ + if (qtPositionInfo()->enabled) { + lastPosition = Simulator::toPositionInfo(*qtPositionInfo()); + emit positionUpdated(lastPosition); + } else { + emit updateTimeout(); + } +} + +QGeoPositionInfoSource::Error QGeoPositionInfoSourceSimulator::error() const +{ + return m_positionError; +} + + +void QGeoPositionInfoSourceSimulator::setError(QGeoPositionInfoSource::Error positionError) +{ + m_positionError = positionError; + emit QGeoPositionInfoSource::error(positionError); +} + +#include "moc_qgeopositioninfosource_simulator_p.cpp" + +QT_END_NAMESPACE diff --git a/src/plugins/position/simulator/qgeopositioninfosource_simulator_p.h b/src/plugins/position/simulator/qgeopositioninfosource_simulator_p.h new file mode 100644 index 0000000..6b1acfc --- /dev/null +++ b/src/plugins/position/simulator/qgeopositioninfosource_simulator_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCESIMULATOR_H +#define QGEOPOSITIONINFOSOURCESIMULATOR_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeopositioninfosource.h" +#include "qgeopositioninfo.h" +#include "qlocationdata_simulator_p.h" + +QT_BEGIN_NAMESPACE + +class QTimer; + +class QGeoPositionInfoSourceSimulator : public QGeoPositionInfoSource +{ + Q_OBJECT +public: + QGeoPositionInfoSourceSimulator(QObject *parent = 0); + ~QGeoPositionInfoSourceSimulator(); + + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const; + PositioningMethods supportedPositioningMethods() const; + + void setUpdateInterval(int msec); + int minimumUpdateInterval() const; + Error error() const; + +public Q_SLOTS: + void startUpdates(); + void stopUpdates(); + + void requestUpdate(int timeout = 0); + +private slots: + void updatePosition(); +private: + Q_DISABLE_COPY(QGeoPositionInfoSourceSimulator); + QTimer *timer; + QTimer *requestTimer; + QGeoPositionInfo lastPosition; + QGeoPositionInfoSource::Error m_positionError; + void setError(QGeoPositionInfoSource::Error positionError); +}; + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFOSOURCESIMULATOR_H diff --git a/src/plugins/position/simulator/qgeopositioninfosourcefactory_simulator.cpp b/src/plugins/position/simulator/qgeopositioninfosourcefactory_simulator.cpp new file mode 100644 index 0000000..9ddc2b9 --- /dev/null +++ b/src/plugins/position/simulator/qgeopositioninfosourcefactory_simulator.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosourcefactory_simulator.h" + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactorySimulator::positionInfoSource(QObject *parent) +{ + return new QGeoPositionInfoSourceSimulator(parent); +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactorySimulator::satelliteInfoSource(QObject *parent) +{ + QGeoSatelliteInfoSourceSimulator *src = new QGeoSatelliteInfoSourceSimulator(parent); + if (!src->isConnected()) { + delete src; + src = 0; + } + return src; +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactorySimulator::areaMonitor(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} diff --git a/src/plugins/position/simulator/qgeopositioninfosourcefactory_simulator.h b/src/plugins/position/simulator/qgeopositioninfosourcefactory_simulator.h new file mode 100644 index 0000000..2d3146e --- /dev/null +++ b/src/plugins/position/simulator/qgeopositioninfosourcefactory_simulator.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCEFACTORY_SIMULATOR_H +#define QGEOPOSITIONINFOSOURCEFACTORY_SIMULATOR_H + +#include +#include + +#include "qgeopositioninfosource_simulator_p.h" +#include "qgeosatelliteinfosource_simulator_p.h" + +class QGeoPositionInfoSourceFactorySimulator : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + Q_INTERFACES(QGeoPositionInfoSourceFactory) +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent); + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent); + QGeoAreaMonitorSource *areaMonitor(QObject *parent); +}; + +#endif // QGEOPOSITIONINFOSOURCEFACTORY_SIMULATOR_H diff --git a/src/plugins/position/simulator/qgeosatelliteinfosource_simulator.cpp b/src/plugins/position/simulator/qgeosatelliteinfosource_simulator.cpp new file mode 100644 index 0000000..1ec62a7 --- /dev/null +++ b/src/plugins/position/simulator/qgeosatelliteinfosource_simulator.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeosatelliteinfosource_simulator_p.h" +#include "qlocationconnection_simulator_p.h" +#include "qlocationdata_simulator_p.h" + +QT_BEGIN_NAMESPACE + +QGeoSatelliteInfoSourceSimulator::QGeoSatelliteInfoSourceSimulator(QObject *parent) + : QGeoSatelliteInfoSource(parent) + , timer(new QTimer(this)) + , requestTimer(new QTimer(this)) +{ + Simulator::LocationConnection::ensureSimulatorConnection(); + + connect(timer, SIGNAL(timeout()), this, SLOT(updateData())); + requestTimer->setSingleShot(true); + connect(requestTimer, SIGNAL(timeout()), this, SLOT(updateData())); +} + +bool QGeoSatelliteInfoSourceSimulator::isConnected() const +{ + return Simulator::LocationConnection::ensureSimulatorConnection(); +} + +void QGeoSatelliteInfoSourceSimulator::startUpdates() +{ + int interval = updateInterval(); + if (interval < minimumUpdateInterval()) + interval = minimumUpdateInterval(); + timer->setInterval(interval); + timer->start(); +} + +void QGeoSatelliteInfoSourceSimulator::stopUpdates() +{ + timer->stop(); +} + +void QGeoSatelliteInfoSourceSimulator::requestUpdate(int timeout) +{ + if (!requestTimer->isActive()) { + // Get a single update within timeframe + if (timeout == 0) + timeout = minimumUpdateInterval(); + + if (timeout < minimumUpdateInterval()) + emit requestTimeout(); + else + requestTimer->start(timeout); + } +} + +void QGeoSatelliteInfoSourceSimulator::setUpdateInterval(int msec) +{ + // msec should be equal to or larger than the minimum update interval; 0 is a special case + // that currently behaves as if the interval is set to the minimum update interval + if (msec != 0 && msec < minimumUpdateInterval()) + msec = minimumUpdateInterval(); + + QGeoSatelliteInfoSource::setUpdateInterval(msec); + if (timer->isActive()) { + timer->setInterval(msec); + timer->start(); + } +} + +int QGeoSatelliteInfoSourceSimulator::minimumUpdateInterval() const +{ + return qtPositionInfo()->minimumInterval; +} + +void QGeoSatelliteInfoSourceSimulator::updateData() +{ + QList satellitesInUse; + QList satellitesInView; + + QGeoSatelliteInfoData *data = qtSatelliteInfo(); + for(int i = 0; i < data->satellites.count(); i++) { + QGeoSatelliteInfoData::SatelliteInfo info = data->satellites.at(i); + QGeoSatelliteInfo satInfo; + satInfo.setAttribute(QGeoSatelliteInfo::Azimuth, info.azimuth); + satInfo.setAttribute(QGeoSatelliteInfo::Elevation, info.elevation); + satInfo.setSignalStrength(info.signalStrength); + satInfo.setSatelliteSystem(static_cast(info.satelliteSystem)); + satInfo.setSatelliteIdentifier(info.satelliteIdentifier); + satellitesInView.append(satInfo); + if (info.inUse) + satellitesInUse.append(satInfo); + } + emit satellitesInViewUpdated(satellitesInView); + emit satellitesInUseUpdated(satellitesInUse); +} + +#include "moc_qgeosatelliteinfosource_simulator_p.cpp" +QT_END_NAMESPACE diff --git a/src/plugins/position/simulator/qgeosatelliteinfosource_simulator_p.h b/src/plugins/position/simulator/qgeosatelliteinfosource_simulator_p.h new file mode 100644 index 0000000..c2fb0d0 --- /dev/null +++ b/src/plugins/position/simulator/qgeosatelliteinfosource_simulator_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSATELLITEINFOSOURCE_SIMULATOR_H +#define QGEOSATELLITEINFOSOURCE_SIMULATOR_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include "qgeosatelliteinfosource.h" +#include "qgeosatelliteinfo.h" + +#define MINIMUM_UPDATE_INTERVAL 1000 +#define DEFAULT_UPDATE_INTERVAL 5000 + +QT_BEGIN_NAMESPACE + +class QGeoSatelliteInfoSourceSimulator : public QGeoSatelliteInfoSource +{ + Q_OBJECT + +public: + explicit QGeoSatelliteInfoSourceSimulator(QObject *parent = 0); + + bool isConnected() const; + + virtual void setUpdateInterval(int msec); + virtual int minimumUpdateInterval() const; + + // Default implementation for error() + Error error() const { return QGeoSatelliteInfoSource::NoError; } +public slots: + virtual void startUpdates(); + virtual void stopUpdates(); + virtual void requestUpdate(int timeout = 5000); + +private slots: + void updateData(); + +private: + Q_DISABLE_COPY(QGeoSatelliteInfoSourceSimulator) + QTimer *timer; + QTimer *requestTimer; +}; + +QT_END_NAMESPACE + +#endif // QGEOSATELLITEINFOSOURCE_SIMULATOR_H + diff --git a/src/plugins/position/simulator/qlocationconnection_simulator.cpp b/src/plugins/position/simulator/qlocationconnection_simulator.cpp new file mode 100644 index 0000000..f8e1da0 --- /dev/null +++ b/src/plugins/position/simulator/qlocationconnection_simulator.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocationconnection_simulator_p.h" +#include "qgeopositioninfosource_simulator_p.h" +#include "qlocationdata_simulator_p.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +const QString simulatorName(QString::fromLatin1("QtSimulator_Mobility_ServerName1.3.0.0")); +const quint16 simulatorPort = 0xbeef + 1; + +namespace Simulator +{ + LocationConnection::LocationConnection() + : mConnection(new Connection(Connection::Client, simulatorName, simulatorPort, Version(1,3,0,0))) + { + qt_registerLocationTypes(); + mWorker = mConnection->connectToServer(Connection::simulatorHostName(true), simulatorPort); + if (!mWorker) + return; + + mWorker->addReceiver(this); + + // register for location notifications + mWorker->call("setRequestsLocationInfo"); + } + + LocationConnection::~LocationConnection() + { + delete mWorker; + delete mConnection; + } + + bool LocationConnection::ensureSimulatorConnection() + { + static LocationConnection locationConnection; + return locationConnection.mWorker; + } + + void LocationConnection::initialLocationDataSent() + { + emit initialDataReceived(); + } + + void LocationConnection::setLocationData(const QGeoPositionInfoData &data) + { + *qtPositionInfo() = data; + } + + void LocationConnection::setSatelliteData(const QGeoSatelliteInfoData &data) + { + *qtSatelliteInfo() = data; + } + +#include "moc_qlocationconnection_simulator_p.cpp" + +} // namespace + +QGeoPositionInfoData *qtPositionInfo() +{ + static QGeoPositionInfoData *positionInfo = 0; + if (!positionInfo) { + positionInfo = new QGeoPositionInfoData; + } + + return positionInfo; +} + +QGeoSatelliteInfoData *qtSatelliteInfo() +{ + static QGeoSatelliteInfoData *satelliteInfo = 0; + if (!satelliteInfo) { + satelliteInfo = new QGeoSatelliteInfoData; + } + + return satelliteInfo; +} + + +QT_END_NAMESPACE diff --git a/src/plugins/position/simulator/qlocationconnection_simulator_p.h b/src/plugins/position/simulator/qlocationconnection_simulator_p.h new file mode 100644 index 0000000..a13c9e8 --- /dev/null +++ b/src/plugins/position/simulator/qlocationconnection_simulator_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCATIONCONNECTION_H +#define QLOCATIONCONNECTION_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qlocationdata_simulator_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace Simulator +{ + class Connection; + class ConnectionWorker; + + class LocationConnection : public QObject + { + Q_OBJECT + + public: + static bool ensureSimulatorConnection(); + virtual ~LocationConnection(); + + private: + LocationConnection(); + Q_DISABLE_COPY(LocationConnection) + + private slots: + // these will be called by the simulator + void setLocationData(const QGeoPositionInfoData &); + void setSatelliteData(const QGeoSatelliteInfoData &); + void initialLocationDataSent(); + + signals: + void initialDataReceived(); + + private: + Connection *mConnection; + ConnectionWorker *mWorker; + }; +} // end namespace Simulator + +QGeoPositionInfoData *qtPositionInfo(); +QGeoSatelliteInfoData *qtSatelliteInfo(); + +QT_END_NAMESPACE + +#endif // QLOCATIONCONNECTION_H diff --git a/src/plugins/position/simulator/simulator.pro b/src/plugins/position/simulator/simulator.pro new file mode 100644 index 0000000..c3e6ea3 --- /dev/null +++ b/src/plugins/position/simulator/simulator.pro @@ -0,0 +1,22 @@ +TARGET = qtposition_simulator + +QT = core network positioning simulator + +INCLUDEPATH += ../../../positioning + +DEFINES += QT_SIMULATOR +SOURCES += qgeopositioninfosource_simulator.cpp \ + qgeosatelliteinfosource_simulator.cpp \ + qlocationconnection_simulator.cpp \ + qgeopositioninfosourcefactory_simulator.cpp +HEADERS += qgeopositioninfosource_simulator_p.h \ + qgeosatelliteinfosource_simulator_p.h \ + qlocationconnection_simulator_p.h \ + qgeopositioninfosourcefactory_simulator.h + +OTHER_FILES += \ + plugin.json + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = QGeoPositionInfoSourceFactorySimulator +load(qt_plugin) diff --git a/src/plugins/position/winrt/plugin.json b/src/plugins/position/winrt/plugin.json new file mode 100644 index 0000000..0696cb0 --- /dev/null +++ b/src/plugins/position/winrt/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["winrt"], + "Provider": "winrt", + "Position": true, + "Satellite": false, + "Monitor" : false, + "Priority": 1000, + "Testable": false +} diff --git a/src/plugins/position/winrt/qgeopositioninfosource_winrt.cpp b/src/plugins/position/winrt/qgeopositioninfosource_winrt.cpp new file mode 100644 index 0000000..245d855 --- /dev/null +++ b/src/plugins/position/winrt/qgeopositioninfosource_winrt.cpp @@ -0,0 +1,535 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosource_winrt_p.h" + +#include +#include +#include +#ifdef Q_OS_WINRT +#include +#endif + +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Devices::Geolocation; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; + +typedef ITypedEventHandler GeoLocatorPositionHandler; +typedef ITypedEventHandler GeoLocatorStatusHandler; +typedef IAsyncOperationCompletedHandler PositionHandler; +#if _MSC_VER >= 1900 +typedef IAsyncOperationCompletedHandler AccessHandler; +#endif + +QT_BEGIN_NAMESPACE + +Q_DECLARE_METATYPE(QGeoPositionInfo) + +#ifndef Q_OS_WINRT +namespace QEventDispatcherWinRT { +HRESULT runOnXamlThread(const std::function &delegate, bool waitForRun = true) +{ + Q_UNUSED(waitForRun); + return delegate(); +} +} +#endif + +class QGeoPositionInfoSourceWinRTPrivate { +public: + ComPtr locator; + QTimer periodicTimer; + QTimer singleUpdateTimer; + QGeoPositionInfo lastPosition; + QGeoPositionInfoSource::Error positionError; + EventRegistrationToken statusToken; + EventRegistrationToken positionToken; + QMutex mutex; + bool updatesOngoing; +}; + + +QGeoPositionInfoSourceWinRT::QGeoPositionInfoSourceWinRT(QObject *parent) + : QGeoPositionInfoSource(parent) + , d_ptr(new QGeoPositionInfoSourceWinRTPrivate) +{ + Q_D(QGeoPositionInfoSourceWinRT); + d->positionError = QGeoPositionInfoSource::NoError; + d->updatesOngoing = false; + + qRegisterMetaType(); + + requestAccess(); + HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([this, d]() { + HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Devices_Geolocation_Geolocator).Get(), + &d->locator); + RETURN_HR_IF_FAILED("Could not initialize native location services."); + + // StatusChanged throws an exception on Windows 8.1 +#if _MSC_VER >= 1900 + hr = d->locator->add_StatusChanged(Callback(this, + &QGeoPositionInfoSourceWinRT::onStatusChanged).Get(), + &d->statusToken); + RETURN_HR_IF_FAILED("Could not add status callback."); +#endif + + hr = d->locator->put_ReportInterval(1000); + RETURN_HR_IF_FAILED("Could not initialize report interval."); + + return hr; + }); + Q_ASSERT_SUCCEEDED(hr); + + hr = d->locator->put_DesiredAccuracy(PositionAccuracy::PositionAccuracy_Default); + if (FAILED(hr)) { + setError(QGeoPositionInfoSource::UnknownSourceError); + qErrnoWarning(hr, "Could not initialize desired accuracy."); + return; + } + + d->positionToken.value = 0; + + d->periodicTimer.setSingleShot(true); + d->periodicTimer.setInterval(minimumUpdateInterval()); + connect(&d->periodicTimer, &QTimer::timeout, this, &QGeoPositionInfoSourceWinRT::virtualPositionUpdate); + + d->singleUpdateTimer.setSingleShot(true); + connect(&d->singleUpdateTimer, &QTimer::timeout, this, &QGeoPositionInfoSourceWinRT::singleUpdateTimeOut); + + setPreferredPositioningMethods(QGeoPositionInfoSource::AllPositioningMethods); + + connect(this, &QGeoPositionInfoSourceWinRT::nativePositionUpdate, this, &QGeoPositionInfoSourceWinRT::updateSynchronized); +} + +QGeoPositionInfoSourceWinRT::~QGeoPositionInfoSourceWinRT() +{ +} + +QGeoPositionInfo QGeoPositionInfoSourceWinRT::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const +{ + Q_D(const QGeoPositionInfoSourceWinRT); + Q_UNUSED(fromSatellitePositioningMethodsOnly) + return d->lastPosition; +} + +QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSourceWinRT::supportedPositioningMethods() const +{ + Q_D(const QGeoPositionInfoSourceWinRT); + + PositionStatus status; + HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([d, &status]() { + HRESULT hr = d->locator->get_LocationStatus(&status); + return hr; + }); + if (FAILED(hr)) + return QGeoPositionInfoSource::NoPositioningMethods; + + switch (status) { + case PositionStatus::PositionStatus_NoData: + case PositionStatus::PositionStatus_Disabled: + case PositionStatus::PositionStatus_NotAvailable: + return QGeoPositionInfoSource::NoPositioningMethods; + } + + return QGeoPositionInfoSource::AllPositioningMethods; +} + +void QGeoPositionInfoSourceWinRT::setPreferredPositioningMethods(QGeoPositionInfoSource::PositioningMethods methods) +{ + Q_D(QGeoPositionInfoSourceWinRT); + + PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods(); + QGeoPositionInfoSource::setPreferredPositioningMethods(methods); + if (previousPreferredPositioningMethods == preferredPositioningMethods()) + return; + + bool needsRestart = d->positionToken.value != 0; + + if (needsRestart) + stopHandler(); + + PositionAccuracy acc = methods & PositioningMethod::SatellitePositioningMethods ? + PositionAccuracy::PositionAccuracy_High : + PositionAccuracy::PositionAccuracy_Default; + HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([d, acc]() { + HRESULT hr = d->locator->put_DesiredAccuracy(acc); + return hr; + }); + RETURN_VOID_IF_FAILED("Could not set positioning accuracy."); + + if (needsRestart) + startHandler(); +} + +void QGeoPositionInfoSourceWinRT::setUpdateInterval(int msec) +{ + Q_D(QGeoPositionInfoSourceWinRT); + // Windows Phone 8.1 and Windows 10 do not support 0 interval +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) + if (msec == 0) + msec = minimumUpdateInterval(); +#endif + + // If msec is 0 we send updates as data becomes available, otherwise we force msec to be equal + // to or larger than the minimum update interval. + if (msec != 0 && msec < minimumUpdateInterval()) + msec = minimumUpdateInterval(); + + HRESULT hr = d->locator->put_ReportInterval(msec); + if (FAILED(hr)) { + setError(QGeoPositionInfoSource::UnknownSourceError); + qErrnoWarning(hr, "Failed to set update interval"); + return; + } + + d->periodicTimer.setInterval(qMax(msec, minimumUpdateInterval())); + + QGeoPositionInfoSource::setUpdateInterval(msec); +} + +int QGeoPositionInfoSourceWinRT::minimumUpdateInterval() const +{ + // We use one second to reduce potential timer events + // in case the platform itself stops reporting + return 1000; +} + +void QGeoPositionInfoSourceWinRT::startUpdates() +{ + Q_D(QGeoPositionInfoSourceWinRT); + + if (d->updatesOngoing) + return; + + if (!startHandler()) + return; + d->updatesOngoing = true; + d->periodicTimer.start(); +} + +void QGeoPositionInfoSourceWinRT::stopUpdates() +{ + Q_D(QGeoPositionInfoSourceWinRT); + + stopHandler(); + d->updatesOngoing = false; + d->periodicTimer.stop(); +} + +bool QGeoPositionInfoSourceWinRT::startHandler() +{ + Q_D(QGeoPositionInfoSourceWinRT); + + // Check if already attached + if (d->positionToken.value != 0) + return true; + + if (preferredPositioningMethods() == QGeoPositionInfoSource::NoPositioningMethods) { + setError(QGeoPositionInfoSource::UnknownSourceError); + return false; + } + + if (!requestAccess() || !checkNativeState()) + return false; + + HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([this, d]() { + HRESULT hr; + + // We need to call this at least once on Windows 10 Mobile. + // Unfortunately this operation does not have a completion handler + // registered. That could have helped in the single update case + ComPtr> op; + hr = d->locator->GetGeopositionAsync(&op); + + hr = d->locator->add_PositionChanged(Callback(this, + &QGeoPositionInfoSourceWinRT::onPositionChanged).Get(), + &d->positionToken); + return hr; + }); + if (FAILED(hr)) { + setError(QGeoPositionInfoSource::UnknownSourceError); + qErrnoWarning(hr, "Could not add position handler"); + return false; + } + + return true; +} + +void QGeoPositionInfoSourceWinRT::stopHandler() +{ + Q_D(QGeoPositionInfoSourceWinRT); + + if (!d->positionToken.value) + return; + QEventDispatcherWinRT::runOnXamlThread([d]() { + d->locator->remove_PositionChanged(d->positionToken); + return S_OK; + }); + d->positionToken.value = 0; +} + +void QGeoPositionInfoSourceWinRT::requestUpdate(int timeout) +{ + Q_D(QGeoPositionInfoSourceWinRT); + + if (timeout != 0 && timeout < minimumUpdateInterval()) { + emit updateTimeout(); + return; + } + + if (timeout == 0) + timeout = 2*60*1000; // Maximum time for cold start (see Android) + + startHandler(); + d->singleUpdateTimer.start(timeout); +} + +void QGeoPositionInfoSourceWinRT::virtualPositionUpdate() +{ + Q_D(QGeoPositionInfoSourceWinRT); + QMutexLocker locker(&d->mutex); + + // Need to check if services are still running and ok + if (!checkNativeState()) { + stopUpdates(); + return; + } + + // The operating system did not provide information in time + // Hence we send a virtual position update to keep same behavior + // between backends. + // This only applies to the periodic timer, not for single requests + // We can only do this if we received a valid position before + if (d->lastPosition.isValid()) { + QGeoPositionInfo sent = d->lastPosition; + sent.setTimestamp(QDateTime::currentDateTime()); + d->lastPosition = sent; + emit positionUpdated(sent); + } + d->periodicTimer.start(); +} + +void QGeoPositionInfoSourceWinRT::singleUpdateTimeOut() +{ + Q_D(QGeoPositionInfoSourceWinRT); + QMutexLocker locker(&d->mutex); + + if (d->singleUpdateTimer.isActive()) { + emit updateTimeout(); + if (!d->updatesOngoing) + stopHandler(); + } +} + +void QGeoPositionInfoSourceWinRT::updateSynchronized(QGeoPositionInfo currentInfo) +{ + Q_D(QGeoPositionInfoSourceWinRT); + QMutexLocker locker(&d->mutex); + + d->periodicTimer.stop(); + d->lastPosition = currentInfo; + + if (d->updatesOngoing) + d->periodicTimer.start(); + + if (d->singleUpdateTimer.isActive()) { + d->singleUpdateTimer.stop(); + if (!d->updatesOngoing) + stopHandler(); + } + + emit positionUpdated(currentInfo); +} + +QGeoPositionInfoSource::Error QGeoPositionInfoSourceWinRT::error() const +{ + Q_D(const QGeoPositionInfoSourceWinRT); + return d->positionError; +} + +void QGeoPositionInfoSourceWinRT::setError(QGeoPositionInfoSource::Error positionError) +{ + Q_D(QGeoPositionInfoSourceWinRT); + + if (positionError == d->positionError) + return; + d->positionError = positionError; + emit QGeoPositionInfoSource::error(positionError); +} + +bool QGeoPositionInfoSourceWinRT::checkNativeState() +{ + Q_D(QGeoPositionInfoSourceWinRT); + + PositionStatus status; + HRESULT hr = d->locator->get_LocationStatus(&status); + if (FAILED(hr)) { + setError(QGeoPositionInfoSource::UnknownSourceError); + qErrnoWarning(hr, "Could not query status"); + return false; + } + + bool result = false; + switch (status) { + case PositionStatus::PositionStatus_NotAvailable: + setError(QGeoPositionInfoSource::UnknownSourceError); + break; + case PositionStatus::PositionStatus_Disabled: + case PositionStatus::PositionStatus_NoData: + setError(QGeoPositionInfoSource::ClosedError); + break; + default: + setError(QGeoPositionInfoSource::NoError); + result = true; + break; + } + return result; +} + +HRESULT QGeoPositionInfoSourceWinRT::onPositionChanged(IGeolocator *locator, IPositionChangedEventArgs *args) +{ + Q_UNUSED(locator); + + HRESULT hr; + ComPtr pos; + hr = args->get_Position(&pos); + RETURN_HR_IF_FAILED("Could not access position object."); + + QGeoPositionInfo currentInfo; + + ComPtr coord; + hr = pos->get_Coordinate(&coord); + if (FAILED(hr)) + qErrnoWarning(hr, "Could not access coordinate"); + + DOUBLE lat; + hr = coord->get_Latitude(&lat); + if (FAILED(hr)) + qErrnoWarning(hr, "Could not access latitude"); + + DOUBLE lon; + hr = coord->get_Longitude(&lon); + if (FAILED(hr)) + qErrnoWarning(hr, "Could not access longitude"); + + // Depending on data source altitude can + // be identified or not + IReference *alt; + hr = coord->get_Altitude(&alt); + if (SUCCEEDED(hr) && alt) { + double altd; + hr = alt->get_Value(&altd); + currentInfo.setCoordinate(QGeoCoordinate(lat, lon, altd)); + } else { + currentInfo.setCoordinate(QGeoCoordinate(lat, lon)); + } + + DOUBLE accuracy; + hr = coord->get_Accuracy(&accuracy); + if (SUCCEEDED(hr)) + currentInfo.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy); + + IReference *altAccuracy; + hr = coord->get_AltitudeAccuracy(&altAccuracy); + if (SUCCEEDED(hr) && altAccuracy) { + double value; + hr = alt->get_Value(&value); + currentInfo.setAttribute(QGeoPositionInfo::VerticalAccuracy, value); + } + + IReference *speed; + hr = coord->get_Speed(&speed); + if (SUCCEEDED(hr) && speed) { + double value; + hr = speed->get_Value(&value); + currentInfo.setAttribute(QGeoPositionInfo::GroundSpeed, value); + } + + currentInfo.setTimestamp(QDateTime::currentDateTime()); + + emit nativePositionUpdate(currentInfo); + + return S_OK; +} + +HRESULT QGeoPositionInfoSourceWinRT::onStatusChanged(IGeolocator*, IStatusChangedEventArgs *args) +{ + PositionStatus st; + args->get_Status(&st); + return S_OK; +} + +bool QGeoPositionInfoSourceWinRT::requestAccess() const +{ +#if _MSC_VER >= 1900 && defined(Q_OS_WINRT) + static GeolocationAccessStatus accessStatus = GeolocationAccessStatus_Unspecified; + static ComPtr statics; + + if (accessStatus == GeolocationAccessStatus_Allowed) + return true; + else if (accessStatus == GeolocationAccessStatus_Denied) + return false; + + ComPtr> op; + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([&op]() { + HRESULT hr; + hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Geolocation_Geolocator).Get(), + IID_PPV_ARGS(&statics)); + RETURN_HR_IF_FAILED("Could not access Geolocation Statics."); + + hr = statics->RequestAccessAsync(&op); + return hr; + }); + Q_ASSERT_SUCCEEDED(hr); + + // We cannot wait inside the XamlThread as that would deadlock + QWinRTFunctions::await(op, &accessStatus); + return accessStatus == GeolocationAccessStatus_Allowed; +#else // _MSC_VER < 1900 + return true; +#endif // _MSC_VER < 1900 +} + +QT_END_NAMESPACE diff --git a/src/plugins/position/winrt/qgeopositioninfosource_winrt_p.h b/src/plugins/position/winrt/qgeopositioninfosource_winrt_p.h new file mode 100644 index 0000000..b8820dd --- /dev/null +++ b/src/plugins/position/winrt/qgeopositioninfosource_winrt_p.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCEWINRT_H +#define QGEOPOSITIONINFOSOURCEWINRT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeopositioninfosource.h" +#include "qgeopositioninfo.h" + +#include + +#include +#include + +namespace ABI { + namespace Windows { + namespace Devices { + namespace Geolocation{ + struct IGeolocator; + struct IPositionChangedEventArgs; + struct IStatusChangedEventArgs; + } + } + } +} + +QT_BEGIN_NAMESPACE + +class QGeoPositionInfoSourceWinRTPrivate; + +class QGeoPositionInfoSourceWinRT : public QGeoPositionInfoSource +{ + Q_OBJECT +public: + QGeoPositionInfoSourceWinRT(QObject *parent = 0); + ~QGeoPositionInfoSourceWinRT(); + + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const; + PositioningMethods supportedPositioningMethods() const; + + void setPreferredPositioningMethods(PositioningMethods methods); + + void setUpdateInterval(int msec); + int minimumUpdateInterval() const; + Error error() const; + + HRESULT onPositionChanged(ABI::Windows::Devices::Geolocation::IGeolocator *locator, + ABI::Windows::Devices::Geolocation::IPositionChangedEventArgs *args); + + HRESULT onStatusChanged(ABI::Windows::Devices::Geolocation::IGeolocator*, + ABI::Windows::Devices::Geolocation::IStatusChangedEventArgs *args); + + bool requestAccess() const; +Q_SIGNALS: + void nativePositionUpdate(const QGeoPositionInfo); +public slots: + void startUpdates(); + void stopUpdates(); + + void requestUpdate(int timeout = 0); + +private slots: + void stopHandler(); + void virtualPositionUpdate(); + void singleUpdateTimeOut(); + void updateSynchronized(const QGeoPositionInfo info); +private: + bool startHandler(); + + Q_DISABLE_COPY(QGeoPositionInfoSourceWinRT) + void setError(QGeoPositionInfoSource::Error positionError); + bool checkNativeState(); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QGeoPositionInfoSourceWinRT) +}; + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFOSOURCEWINRT_H diff --git a/src/plugins/position/winrt/qgeopositioninfosourcefactory_winrt.cpp b/src/plugins/position/winrt/qgeopositioninfosourcefactory_winrt.cpp new file mode 100644 index 0000000..81656c2 --- /dev/null +++ b/src/plugins/position/winrt/qgeopositioninfosourcefactory_winrt.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosourcefactory_winrt.h" +#include "qgeopositioninfosource_winrt_p.h" + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactoryWinRT::positionInfoSource(QObject *parent) +{ + return new QGeoPositionInfoSourceWinRT(parent); +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactoryWinRT::satelliteInfoSource(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactoryWinRT::areaMonitor(QObject *parent) +{ + Q_UNUSED(parent); + return 0; +} diff --git a/src/plugins/position/winrt/qgeopositioninfosourcefactory_winrt.h b/src/plugins/position/winrt/qgeopositioninfosourcefactory_winrt.h new file mode 100644 index 0000000..46cd385 --- /dev/null +++ b/src/plugins/position/winrt/qgeopositioninfosourcefactory_winrt.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCEFACTORY_WINRT_H +#define QGEOPOSITIONINFOSOURCEFACTORY_WINRT_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoPositionInfoSourceFactoryWinRT : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + Q_INTERFACES(QGeoPositionInfoSourceFactory) +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent); + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent); + QGeoAreaMonitorSource *areaMonitor(QObject *parent); +}; + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFOSOURCEFACTORY_WINRT_H diff --git a/src/plugins/position/winrt/winrt.pro b/src/plugins/position/winrt/winrt.pro new file mode 100644 index 0000000..446cb34 --- /dev/null +++ b/src/plugins/position/winrt/winrt.pro @@ -0,0 +1,16 @@ +TARGET = qtposition_winrt + +QT = core core-private positioning + +SOURCES += qgeopositioninfosource_winrt.cpp \ + qgeopositioninfosourcefactory_winrt.cpp +HEADERS += qgeopositioninfosource_winrt_p.h \ + qgeopositioninfosourcefactory_winrt.h + +OTHER_FILES += \ + plugin.json + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = QGeoPositionInfoSourceFactoryWinRT +msvc:!winrt: LIBS += runtimeobject.lib +load(qt_plugin) diff --git a/src/positioning/doc/qtpositioning.qdocconf b/src/positioning/doc/qtpositioning.qdocconf new file mode 100644 index 0000000..608b30a --- /dev/null +++ b/src/positioning/doc/qtpositioning.qdocconf @@ -0,0 +1,55 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) + +project = QtPositioning +description = Qt Positioning Reference Documentation +version = $QT_VERSION + + + +qhp.projects = QtPositioning + +qhp.QtPositioning.file = qtpositioning.qhp +qhp.QtPositioning.namespace = org.qt-project.qtpositioning.$QT_VERSION_TAG +qhp.QtPositioning.virtualFolder = qtpositioning +qhp.QtPositioning.indexTitle = Qt Positioning +qhp.QtPositioning.indexRoot = + +qhp.QtPositioning.filterAttributes = qtpositioning $QT_VERSION qtrefdoc +qhp.QtPositioning.customFilters.Qt.name = QtPositioning $QT_VERSION +qhp.QtPositioning.customFilters.Qt.filterAttributes = qtpositioning $QT_VERSION +qhp.QtPositioning.subprojects = classes qml examples +qhp.QtPositioning.subprojects.classes.title = C++ Classes +qhp.QtPositioning.subprojects.classes.indexTitle = Qt Positioning C++ Classes +qhp.QtPositioning.subprojects.classes.selectors = class fake:headerfile +qhp.QtPositioning.subprojects.classes.sortPages = true +qhp.QtPositioning.subprojects.qml.title = QML Types +qhp.QtPositioning.subprojects.qml.indexTitle = Qt Positioning QML Types +qhp.QtPositioning.subprojects.qml.selectors = qmlclass +qhp.QtPositioning.subprojects.qml.sortPages = true +qhp.QtPositioning.subprojects.examples.title = Qt Positioning Examples +qhp.QtPositioning.subprojects.examples.indexTitle = Qt Positioning Examples +qhp.QtPositioning.subprojects.examples.selectors = fake:example + +tagfile = ../../../doc/qtpositioning/qtpositioning.tags + +depends += qtcore qtdoc qtquick qtqml qtnetwork qtlocation + +headerdirs += .. \ + ../../imports/positioning + +sourcedirs += .. \ + ../../imports/positioning + +examplesinstallpath = positioning + +exampledirs += ../../../examples/positioning \ + snippets/ + + +imagedirs += images + +navigation.landingpage = "Qt Positioning" +navigation.cppclassespage = "Qt Positioning C++ Classes" +navigation.qmltypespage = "Qt Positioning QML Types" + +manifestmeta.thumbnail.names += "QtPositioning/Log File*" diff --git a/src/positioning/doc/snippets/cpp/cpp.pro b/src/positioning/doc/snippets/cpp/cpp.pro new file mode 100644 index 0000000..47401e9 --- /dev/null +++ b/src/positioning/doc/snippets/cpp/cpp.pro @@ -0,0 +1,8 @@ +TEMPLATE = app +TARGET = positioning_cppsnippet +QT = core positioning + +SOURCES += \ + main.cpp \ + cppqml.cpp + diff --git a/src/positioning/doc/snippets/cpp/cppqml.cpp b/src/positioning/doc/snippets/cpp/cppqml.cpp new file mode 100644 index 0000000..5954ecf --- /dev/null +++ b/src/positioning/doc/snippets/cpp/cppqml.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +void cppQmlInterface(QObject *qmlObject) +{ + //! [Address get] + QGeoAddress geoAddress = qmlObject->property("address").value(); + //! [Address get] + + //! [Address set] + qmlObject->setProperty("address", QVariant::fromValue(geoAddress)); + //! [Address set] + + //! [Location get] + QGeoLocation geoLocation = qmlObject->property("location").value(); + //! [Location get] + + //! [Location set] + qmlObject->setProperty("location", QVariant::fromValue(geoLocation)); + //! [Location set] +} + +class MyClass : public QObject +{ + Q_OBJECT +//! [BigBen] +public: + MyClass() : QObject() + { + QGeoAreaMonitorSource *monitor = QGeoAreaMonitorSource::createDefaultSource(this); + if (monitor) { + connect(monitor, SIGNAL(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo)), + this, SLOT(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo))); + connect(monitor, SIGNAL(areaExited(QGeoAreaMonitorInfo,QGeoPositionInfo)), + this, SLOT(areaExited(QGeoAreaMonitorInfo,QGeoPositionInfo))); + + QGeoAreaMonitorInfo bigBen("Big Ben"); + QGeoCoordinate position(51.50104, -0.124632); + bigBen.setArea(QGeoCircle(position, 100)); + + monitor->startMonitoring(bigBen); + + } else { + qDebug() << "Could not create default area monitor"; + } + } + +public Q_SLOTS: + void areaEntered(const QGeoAreaMonitorInfo &mon, const QGeoPositionInfo &update) + { + Q_UNUSED(mon) + + qDebug() << "Now within 100 meters, current position is" << update.coordinate(); + } + + void areaExited(const QGeoAreaMonitorInfo &mon, const QGeoPositionInfo &update) + { + Q_UNUSED(mon) + + qDebug() << "No longer within 100 meters, current position is" << update.coordinate(); + } +//! [BigBen] +}; diff --git a/src/positioning/doc/snippets/cpp/main.cpp b/src/positioning/doc/snippets/cpp/main.cpp new file mode 100644 index 0000000..7e893f5 --- /dev/null +++ b/src/positioning/doc/snippets/cpp/main.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +int main(int /*argc*/, char ** /*argv*/) +{ + return 0; +} + diff --git a/src/positioning/doc/snippets/doc_src_qtpositioning.qml b/src/positioning/doc/snippets/doc_src_qtpositioning.qml new file mode 100644 index 0000000..1e5e4f9 --- /dev/null +++ b/src/positioning/doc/snippets/doc_src_qtpositioning.qml @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [import] +import QtPositioning 5.2 +//! [import] + +Item { +} + diff --git a/src/positioning/doc/snippets/snippets.pro b/src/positioning/doc/snippets/snippets.pro new file mode 100644 index 0000000..451d1c3 --- /dev/null +++ b/src/positioning/doc/snippets/snippets.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += cpp diff --git a/src/positioning/doc/src/cpp-position.qdoc b/src/positioning/doc/src/cpp-position.qdoc new file mode 100644 index 0000000..34f39bd --- /dev/null +++ b/src/positioning/doc/src/cpp-position.qdoc @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page location-positioning-cpp.html + +\title Positioning (C++) + +\brief The Location Positioning API enables location positioning by means of +GPS or an NMEA data source. + +\section1 Positioning + +The Positioning component of the Qt Location API is about the geographical position, size +and address of some place. Positioning contains a QGeoCoordinate class, containing latitude, longitude and altitude in meters. QGeoLocation contains a QGeoCoordinate plus address +and size information (a bounding box) so that positions can be more than mathematical points. +Movement into or out of the defined bounding box areas can be monitored. The API +also allows the developer to control the source of the positional information +as well. + +Location data involves a precisely specified position on the Earth's +surface \unicode {0x2014} as provided by a latitude-longitude coordinate +\unicode {0x2014} along with associated data, such as: + + \list + \li The date and time at which the position was reported + \li The velocity of the device that reported the position + \li The altitude of the reported position (height above sea level) + \li The bearing of the device in degrees, relative to true north + \endlist + +This data can be extracted through a variety of methods. One of the most +well known methods of positioning is GPS (Global Positioning System), a +publicly available system that uses radiowave signals received from +Earth-orbiting satellites to calculate the precise position and time of +the receiver. Another popular method is 'Cell Identifier Positioning', which uses +the cell identifier of the cell site that is currently serving the receiving +device to calculate its approximate location. These and other positioning +methods can all be used with the Location API; the only requirement for a +location data source within the API is that it provides a +latitude-longitude coordinate with a date/time value, with the option of +providing the other attributes listed above. + + +Location data sources are created by subclassing QGeoPositionInfoSource and +providing QGeoPositionInfo objects through the +QGeoPositionInfoSource::positionUpdated() signal. Clients that require +location data can connect to the +\l{QGeoPositionInfoSource::positionUpdated()}{positionUpdated()} signal and +call \l{QGeoPositionInfoSource::startUpdates()}{startUpdates()} or +\l{QGeoPositionInfoSource::requestUpdate()}{requestUpdate()} to trigger the +distribution of location data. The location data distribution can be stopped by +calling the \l {QGeoPositionInfoSource::stopUpdates()}{stopUpdates()} function. + +A default position source may be available on some platforms. Call +QGeoPositionInfoSource::createDefaultSource() to create an instance of the default +position source; the method returns 0 if no default source is available for +the platform. + +If a problem occurs with access to the information source then an +\l {QGeoPositionInfoSource::error()}{error()} signal is emitted. + +The QGeoAreaMonitorSource class enables client applications to be notified when +the receiving device has moved in or out of a particular area, as specified +by a coordinate and radius. If the platform provides built-in support for +area monitoring, QGeoAreaMonitorSource::createDefaultMonitor() returns an instance of +the default area monitor. + +Satellite information can also be distributed through the +QGeoSatelliteInfoSource class. Call QGeoSatelliteInfoSource::createDefaultSource() to +create an instance of the default satellite data source for the platform, +if one is available. Alternatively, clients can subclass it to provide a +custom satellite data source. + + + +\section2 Requesting Location Data from Data Sources + +To receive data from a source, connect to its +\l{QGeoPositionInfoSource::positionUpdated()}{positionUpdated()} signal, +then call either \l{QGeoPositionInfoSource::startUpdates()}{startUpdates()} +or \l{QGeoPositionInfoSource::requestUpdate()}{requestUpdate()} to begin. + +Here is an example of a client that receives data from the default location +data source, as returned by QGeoPositionInfoSource::createDefaultSource(): + +\code +class MyClass : public QObject +{ + Q_OBJECT +public: + MyClass(QObject *parent = 0) + : QObject(parent) + { + QGeoPositionInfoSource *source = QGeoPositionInfoSource::createDefaultSource(this); + if (source) { + connect(source, SIGNAL(positionUpdated(QGeoPositionInfo)), + this, SLOT(positionUpdated(QGeoPositionInfo))); + source->startUpdates(); + } + } + +private slots: + void positionUpdated(const QGeoPositionInfo &info) + { + qDebug() << "Position updated:" << info; + } +}; + +\endcode + +\section2 Controlling Aspects of Data Sources + +The QGeoPositionInfoSource::setUpdateInterval() method can be used to +control the rate at which position updates are received. For example, if +the client application only requires updates once every 30 seconds, it can +call \c setUpdateInterval(30000). (If no update interval is set, or +\l {QGeoPositionInfoSource::}{setUpdateInterval()} is called with a value of 0, the source uses a default +interval or some other internal logic to determine when updates should be +provided.) + +QGeoPositionInfoSource::setPreferredPositioningMethods() enables client +applications to request that a certain type of positioning method be used. +For example, if the application prefers to use only satellite positioning, +which offers fairly precise outdoor positioning but can be a heavy user of +power resources, it can call this method with the +QGeoPositionInfoSource::SatellitePositioningMethods value. However, this +method should only be used in specialized client applications; in most +cases, the default positioning methods should not be changed, as a source +may internally use a variety of positioning methods that can be useful to +the application. + +\section2 NMEA Data + +\l {http://en.wikipedia.org/wiki/NMEA_0183}{NMEA} is a common text-based +protocol for specifying navigational data. For convenience, the +QNmeaPositionInfoSource is provided to enable client applications to read +and distribute NMEA data in either real-time mode (for example, when +streaming from a GPS device) or simulation mode (for example, when reading +from a NMEA log file). In simulation mode, the source will emit updates +according to the time stamp of each NMEA sentence to produce a "replay" +of the recorded data. + +Generally, the capabilities provided by the default position source as +returned by QGeoPositionInfoSource::createDefaultSource(), along with the +QNmeaPositionInfoSource class, are sufficient for retrieving location +data. However, in some cases developers may wish to write their own custom +location data source. + +The \l {Log File Position Source (C++)} example demonstrates how to subclass QGeoPositionInfoSource +to create a custom positioning source. + + +\section1 Examples + +\section3 \b{Flickr Example} + +The \l{GeoFlickr QML}{Flickr Example} uses the current location to download thumbnail +images from Flickr relevant to the current location. + + + +\section1 Positioning Classes + +\annotatedlist QtPositioning-positioning + +*/ diff --git a/src/positioning/doc/src/cpp-qml-positioning.qdoc b/src/positioning/doc/src/cpp-qml-positioning.qdoc new file mode 100644 index 0000000..9245c17 --- /dev/null +++ b/src/positioning/doc/src/cpp-qml-positioning.qdoc @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page positioning-cpp-qml.html +\title Interfaces between C++ and QML Code in Qt Positioning + +\brief Some of the providing QtPositioning QML types providing interfaces to access and modify properties in C++. + +\section1 Overview + +Depending on the type of C++ class QtPositioning utilizes two methods to simplify +exchange of position data between C++ and QML code. + +\target Cpp_value_integration_positioning +\section1 Direct C++ Value Integration in QtPositioning + +Starting with Qt 5.5, it has become much easier to integrate non-QObject based +data types into QML. This is achieved by adding \l Q_GADGET support to QtQml. +The macro converts classes into a light-weight +version of a QObject without the required \l QObject inheritance. At the same time +it retains the reflection capabilities of \l QMetaObject. As a result they can be directly +exposed to QML and do not require any further wrapper classes. + +A significant number of Position and Location +related data types were converted to Q_GADGETs. They retain their API and value +type character but have become introspectable via \l QMetaObject. This conversion +was done to the following classes: + +\list +\li \l QGeoCircle +\li \l QGeoCoordinate +\li \l QGeoRectangle +\li \l QGeoShape +\endlist + +Using \l QGeoCoordinate as an example, the C++ types are directly exposed to the +QML environment via its meta type: + +\code + qRegisterMetaType(); + QMetaType::registerEqualsComparator(); +\endcode + +The above registration of \l QGeoCoordinate is automatically done once by the +QtPositioning QML plugin. The \l{Plane Spotter (QML)}{Plane Spotter} example demonstrates +this feature. + +\section1 QVariant Based integration + +This section provides information on how to integrate QGeoAddress and QGeoLocation. + +\section2 Address - QGeoAddress +The \l {QtPositioning::Address::address} {Address.address} property is used to provide an interface between C++ and QML code. First a pointer to a +Address object must be obtained from C++, then use the \l {QObject::property()}{property()} and +\l {QObject::setProperty()}{setProperty()} functions to get and set the \c address property. +The following gets the QGeoAddress representing this object from C++: +\snippet cpp/cppqml.cpp Address get +The following sets the properties of this object based on a QGeoAddress object from C++: +\snippet cpp/cppqml.cpp Address set + + +\section2 Location - QGeoLocation +The \l {Location::location} {Location.location} property is used to provide an interface between C++ and QML code. First a pointer to a +Location object must be obtained from C++, then use the \l {QObject::property()}{property()} and +\l {QObject::setProperty()}{setProperty()} functions to get and set the \c location property. +The following gets the QGeoLocation representing this object from C++: +\snippet cpp/cppqml.cpp Location get +The following sets the properties of this object based on a QGeoLocation object from C++: +\snippet cpp/cppqml.cpp Location set + +*/ diff --git a/src/positioning/doc/src/qml-position.qdoc b/src/positioning/doc/src/qml-position.qdoc new file mode 100644 index 0000000..4b338fd --- /dev/null +++ b/src/positioning/doc/src/qml-position.qdoc @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page location-positioning-qml.html + +\title Positioning (QML) + +\brief The Location Positioning API enables location positioning by means of +GPS or an NMEA data source. + +\section1 Location Positioning + +Location data involves a precisely specified position on the Earth's +surface \unicode {0x2014} as provided by a latitude-longitude coordinate +\unicode {0x2014} along with associated data, such as: + + \list + \li The date and time at which the position was reported + \li The velocity of the device that reported the position + \li The altitude of the reported position (height above sea level) + \li The bearing of the device in degrees, relative to true north + \endlist + +For more information see +\l {http://en.wikipedia.org/wiki/Geographic_coordinate}{Geographic Coordinate}. + +This data can be extracted through a variety of methods. One of the most +well known methods of positioning is GPS (Global Positioning System), a +publicly available system that uses radiowave signals received from +Earth-orbiting satellites to calculate the precise position and time of +the receiver. Another popular method is 'Cell Identifier Positioning', which uses +the cell identifier of the cell site that is currently serving the receiving +device to calculate its approximate location. These and other positioning +methods can all be used with the Location API; the only requirement for a +location data source within the API is that it provides a +latitude-longitude coordinate with a date/time value, with the option of +providing the other attributes listed above. + +\section2 Coordinate + +The \l {coordinate} is a basic unit of geographical information. The +\l {coordinate} type has attributes to hold the \c {latitude}, +\c longitude and \c altitude. + +\section2 Position + +The three dimensional position of an object such as a mobile device can be specified by giving +the latitude, longitude and altitude. That is the values held in the +l\ {coordinate} type. Additionally for computation of future +positions we would like to know if the object is moving, what \l {Position::speed}{speed} it is +doing and what is the \l {Position::timestamp}{timestamp} of the last position data. Position +therefore includes values for the \l {Position::coordinate}{coordinate}, +\l {Position::speed}{speed} and a \l {Position::timestamp}{timestamp}. \l Position also takes +responsibility for validation of sensible values for these properties. These are exposed as +the \l {Position::latitudeValid}{latitudeValid}, \l {Position::longitudeValid}{longitudeValid}, +\l {Position::altitudeValid}{altitudeValid}, \l {Position::speedValid}{speedValid}, +\l {Position::horizontalAccuracyValid}{horizontalAccuracyValid}, and +\l {Position::verticalAccuracyValid}{verticalAccuracyValid} properties. + + +\section2 PositionSource + +We have a Position type, a \l {coordinate} type but where does the data come from? +Also it is a good idea to be able to indicate alternative sources. +Perhaps instead of directly picking up GPS satellites it might be desirable to do +some testing using a datafile. + +The \l PositionSource type provides the developer with control, +within the limits allowed by the platform, of the source of the +geographical data. Apart from tradtional sources such as GPS and cell data the positional data can be +sourced from a logfile which is in NMEA format. + +\l {http://en.wikipedia.org/wiki/NMEA}{NMEA} is a common text-based +protocol for specifying navigational data. For convenience, the \l +{PositionSource::nmeaSource}{nmeaSource} property is provided to enable +QML applications to read NMEA data from a log file or a TCP socket, the +source will emit updates according to the time stamp of each NMEA sentence +to produce a "replay" of the recorded data. To use a TCP socket set the +"socket" uri scheme. + +\code +PositionSource { + nmeaSource: "socket://127.0.0.1:12345" +} +\endcode + + + +\section2 \b{GeoFlickr Example} + +The \l{GeoFlickr (QML)}{GeoFlickr Example} uses the Location to download thumbnail +images from Flickr relevant to the current location. + +*/ diff --git a/src/positioning/doc/src/qtpositioning-examples.qdoc b/src/positioning/doc/src/qtpositioning-examples.qdoc new file mode 100644 index 0000000..57d129a --- /dev/null +++ b/src/positioning/doc/src/qtpositioning-examples.qdoc @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \group qtpositioning-examples + \title Qt Positioning Examples + \brief Examples for the Qt Positioning module + \ingroup all-examples + \ingroup qtpositioning + + These are the \l{Qt Positioning} examples. + +*/ diff --git a/src/positioning/doc/src/qtpositioning-plugins.qdoc b/src/positioning/doc/src/qtpositioning-plugins.qdoc new file mode 100644 index 0000000..4a56b65 --- /dev/null +++ b/src/positioning/doc/src/qtpositioning-plugins.qdoc @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page qtpositioning-plugins.html +\title Qt Positioning service plugins +\brief Implementing Qt Positioning plugins + +Qt Positioning provides the majority of its functionality through plugins. This +document outlines how to develop a new position plugin. + +\section1 Plugin Description + +Each plugin is described by a json file. The json describes the plugins capabilities and +version. Below is an example of a json file used by the postionpoll plugin: + +\quotefile ../../../plugins/position/positionpoll/plugin.json + +The entries have the following meaning: + +\table + \header + \li Key + \li Description + \row + \li Keys + \li The unique name/key of the plugin. Each position plugin must have a unique name. + \row + \li Provider + \li The provider name of the services. Multiple plugins may have the same name. + In such cases the Version string will be used to further distinguish the plugins. + \row + \li Position + \li Set to \c true if the plugin implements a \l QGeoPositionInfoSource. + \row + \li Satellite + \li Set to \c true if the plugin implements a \l QGeoSatelliteInfoSource. + \row + \li Monitor + \li Set to \c true if the plugin implements a \l QGeoAreaMonitorSource. + \row + \li Priority + \li The plugin priority. If multiple plugins have the same provider name, the plugin + with the higest priority will be used. +\endtable + +\section1 Implementing Plugins + +A plugin implementer needs to subclass \l QGeoPositionInfoSourceFactory and override one or more of +its functions. If a plugin does not support a specific feature the function should return 0 or +utilize the default implementation. + +\list + \li \l QGeoPositionInfoSourceFactory::areaMonitor() + \li \l QGeoPositionInfoSourceFactory::positionInfoSource() + \li \l QGeoPositionInfoSourceFactory::satelliteInfoSource() +\endlist +*/ diff --git a/src/positioning/doc/src/qtpositioning-qml.qdoc b/src/positioning/doc/src/qtpositioning-qml.qdoc new file mode 100644 index 0000000..0c94777 --- /dev/null +++ b/src/positioning/doc/src/qtpositioning-qml.qdoc @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \qmlmodule QtPositioning 5.2 + \title Qt Positioning QML Types + \ingroup qmlmodules + \brief Provides QML types for position information + +\section1 Overview + +The identifying string for this module is \e QtPositioning. To use include the following import +statement in the QML file. + +\snippet doc_src_qtpositioning.qml import + +\section2 Positioning QML Concepts + +Position information can come from a variety of sources including satellites, +wifi, text files and so on. The position is described by the latitude, +the longitude, and the altitude in meters. For more information see +\l {http://en.wikipedia.org/wiki/Geographic_coordinate}{Geographic Coordinate}. + +The QML position is stored in a \l {coordinate} which contains the +latitude, longitude and altitude of the device. The \l {QtPositioning::Location}{Location} contains +this \l {coordinate} and adds an address, it also has a bounding box which +defines the recommended viewing region when displaying the location. + +Now that the device has a position, with regular updates the API can determine +the speed and heading of the device. It can also define a box or a circle that can +produce a notification when the device either leaves or enters that region. + +More detailed information retrieving the current position can be found under +\l {Positioning (QML)}{Location Positioning via QML} + +\section1 Basic Types + +\annotatedlist qml-QtPositioning5-basictypes + +\section1 Alphabetical Listing of All QML Types +*/ diff --git a/src/positioning/doc/src/qtpositioning.qdoc b/src/positioning/doc/src/qtpositioning.qdoc new file mode 100644 index 0000000..d5b0eb3 --- /dev/null +++ b/src/positioning/doc/src/qtpositioning.qdoc @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \module QtPositioning + \title Qt Positioning C++ Classes + \ingroup modules + \qtvariable positioning + + \brief The Positioning module provides positioning information via QML and C++ interfaces. + + To load the Qt Positioning module, add the following statement to your .qml files + + \snippet doc_src_qtpositioning.qml import + + For C++ projects include the header appropriate for the current use case, + for example applications using routes may use + + \code #include \endcode + + The .pro file should have the \e positioning keyword added + + \code QT += positioning \endcode + + + See more in the \l{Qt Positioning}{Qt Positioning Overview}. + +*/ + + + +/*! +\page qtpositioning-index.html +\title Qt Positioning +\brief The Qt Positioning API provides positioning information via QML and C++ interfaces. +\ingroup technology-apis + +The Qt Positioning API provides positioning information via QML and C++ interfaces. + +Currently the API is supported on \l {Qt for Android}{Android}, \l {Qt for iOS}{iOS}, +\l {Qt for macOS}{\macos}, +\l {Qt for Linux/X11}{Linux} (using +\l {http://www.freedesktop.org/wiki/Software/GeoClue}{GeoClue version 0.12.99}), +\l {Qt for Windows}{Windows} (with GPS receivers exposed as a serial port providing NMEA sentences), +and \l {Qt for WinRT}{WinRT} (using Windows.Devices.Geolocation). + +\section1 Overview + +The Qt Positioning API gives developers the ability to determine a position by +using a variety of possible sources, including satellite, or wifi, or text file, +and so on. That information can then be used to for example determine a position +on a map. In addition satellite information can be retrieved and area based monitoring +can be performed. + +\section1 Getting Started + +To load the Qt Positioning module, add the following statement to your .qml files + +\snippet doc_src_qtpositioning.qml import + +For C++ projects include the header appropriate for the current use case, +for example applications using routes may use + +\code #include \endcode + +The .pro file should have the \e positioning keyword added + +\code QT += positioning \endcode + +\section1 Related Information +\section2 Overview + +Positioning includes all the functionality necessary to find and work with geographic +coordinates. It can use a variety of external sources of information, including GPS. This +provides us with a coordinate and altitude for the device with additional features +such as speed and direction. This provides the fundamental location information used in the API. + +\section2 References +\table +\row + \li Positioning introduction: + \li \l{Positioning (QML)}{for QML} + \li \l{Positioning (C++)}{for C++} +\row + \li API references: + \li \l {Qt Positioning QML Types}{for QML} + \li \l {Qt Positioning C++ Classes}{for C++} +\row + \li Position plugins: + \li \l {Qt Positioning service plugins} +\endtable + +\section2 Examples + +\list + \li \l {GeoFlickr (QML)} + \li \l {Log File Position Source (C++)} + \li \l {SatelliteInfo (C++/QML)} + \li \l {Weather Info (C++/QML)} +\endlist +*/ diff --git a/src/positioning/positioning.pro b/src/positioning/positioning.pro new file mode 100644 index 0000000..3cc059c --- /dev/null +++ b/src/positioning/positioning.pro @@ -0,0 +1,81 @@ +TARGET = QtPositioning +QT = core-private + +QMAKE_DOCS = $$PWD/doc/qtpositioning.qdocconf +OTHER_FILES += doc/src/*.qdoc # show .qdoc files in Qt Creator + +ANDROID_BUNDLED_JAR_DEPENDENCIES = \ + jar/QtPositioning-bundled.jar:org.qtproject.qt5.android.positioning.QtPositioning +ANDROID_JAR_DEPENDENCIES = \ + jar/QtPositioning.jar:org.qtproject.qt5.android.positioning.QtPositioning +ANDROID_PERMISSIONS = \ + android.permission.ACCESS_FINE_LOCATION +ANDROID_LIB_DEPENDENCIES = \ + plugins/position/libqtposition_android.so +MODULE_WINRT_CAPABILITIES_DEVICE += \ + location +MODULE_PLUGIN_TYPES = \ + position + +PUBLIC_HEADERS += \ + qgeoaddress.h \ + qgeoareamonitorinfo.h \ + qgeoareamonitorsource.h \ + qgeoshape.h \ + qgeorectangle.h \ + qgeocircle.h \ + qgeocoordinate.h \ + qgeolocation.h \ + qgeopositioninfo.h \ + qgeopositioninfosource.h \ + qgeosatelliteinfo.h \ + qgeosatelliteinfosource.h \ + qnmeapositioninfosource.h \ + qgeopositioninfosourcefactory.h \ + qpositioningglobal.h + +PRIVATE_HEADERS += \ + qgeoaddress_p.h \ + qgeoshape_p.h \ + qgeorectangle_p.h \ + qgeocircle_p.h \ + qgeolocation_p.h \ + qlocationutils_p.h \ + qnmeapositioninfosource_p.h \ + qgeocoordinate_p.h \ + qgeopositioninfosource_p.h \ + qdeclarativegeoaddress_p.h \ + qdeclarativegeolocation_p.h \ + qdoublevector2d_p.h \ + qdoublevector3d_p.h \ + qgeoprojection_p.h \ + qpositioningglobal_p.h \ + qlocationdata_simulator_p.h + +SOURCES += \ + qgeoaddress.cpp \ + qgeoareamonitorsource.cpp \ + qgeoareamonitorinfo.cpp \ + qgeoshape.cpp \ + qgeorectangle.cpp \ + qgeocircle.cpp \ + qgeocoordinate.cpp \ + qgeolocation.cpp \ + qgeopositioninfo.cpp \ + qgeopositioninfosource.cpp \ + qgeosatelliteinfo.cpp \ + qgeosatelliteinfosource.cpp \ + qlocationutils.cpp \ + qnmeapositioninfosource.cpp \ + qgeopositioninfosourcefactory.cpp \ + qdeclarativegeoaddress.cpp \ + qdeclarativegeolocation.cpp \ + qdoublevector2d.cpp \ + qdoublevector3d.cpp \ + qgeoprojection.cpp \ + qlocationdata_simulator.cpp + +HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS + + +load(qt_module) diff --git a/src/positioning/qdeclarativegeoaddress.cpp b/src/positioning/qdeclarativegeoaddress.cpp new file mode 100644 index 0000000..476fcac --- /dev/null +++ b/src/positioning/qdeclarativegeoaddress.cpp @@ -0,0 +1,355 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +***************************************************************************/ + +#include "qdeclarativegeoaddress_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Address + \instantiates QDeclarativeGeoAddress + \inqmlmodule QtPositioning + \since 5.2 + + \brief The Address QML type represents a specific location as a street address. + + An Address is used as a unit of data for queries such as (Reverse) Geocoding + or Places searches -- many of these operations either accept an Address + or return one. + + Not all properties of an Address are necessarily available or relevant + in all parts of the world and all locales. The \l district, \l state and + \l county properties are particularly area-specific for many data sources, + and often only one or two of these are available or useful. + + The Address has a \l text property which holds a formatted string. It + is the recommended way to display an address to the user and typically + takes the format of an address as found on an envelope, but this is not always + the case. The \l text may be automatically generated from constituent + address properties such as \l street, \l city and and so on, but can also + be explicitly assigned. See \l text for details. + + \section2 Example Usage + + The following code snippet shows the declaration of an Address object. + + \code + Address { + id: address + street: "53 Brandl St" + city: "Eight Mile Plains" + country: "Australia" + countryCode: "AUS" + } + \endcode + + This could then be used, for example, as the value of a geocoding query, + to get an exact longitude and latitude for the address. + + \sa {QGeoAddress} +*/ + +QDeclarativeGeoAddress::QDeclarativeGeoAddress(QObject *parent) : + QObject(parent) +{ +} + +QDeclarativeGeoAddress::QDeclarativeGeoAddress(const QGeoAddress &address, QObject *parent) : + QObject(parent), m_address(address) +{ +} + +/*! + \qmlproperty QGeoAddress QtPositioning::Address::address + + For details on how to use this property to interface between C++ and QML see + "\l {Address - QGeoAddress} {Interfaces between C++ and QML Code}". +*/ +QGeoAddress QDeclarativeGeoAddress::address() const +{ + return m_address; +} + +void QDeclarativeGeoAddress::setAddress(const QGeoAddress &address) +{ + // Elaborate but takes care of emiting needed signals + setText(address.text()); + setCountry(address.country()); + setCountryCode(address.countryCode()); + setState(address.state()); + setCounty(address.county()); + setCity(address.city()); + setDistrict(address.district()); + setStreet(address.street()); + setPostalCode(address.postalCode()); + m_address = address; +} + +/*! + \qmlproperty string QtPositioning::Address::text + + This property holds the address as a single formatted string. It is the recommended + string to use to display the address to the user. It typically takes the format of + an address as found on an envelope, but this is not always necessarily the case. + + The address \c text is either automatically generated or explicitly assigned, + this can be determined by checking \l isTextGenerated. + + If an empty string is assigned to \c text, then \l isTextGenerated will be set + to true and \c text will return a string which is locally formatted according to + \l countryCode and based on the properties of the address. Modifying the address + properties such as \l street, \l city and so on may cause the contents of \c text to + change. + + If a non-empty string is assigned to \c text, then \l isTextGenerated will be + set to false and \c text will always return the explicitly assigned string. + Modifying address properties will not affect the \c text property. +*/ +QString QDeclarativeGeoAddress::text() const +{ + return m_address.text(); +} + +void QDeclarativeGeoAddress::setText(const QString &address) +{ + QString oldText = m_address.text(); + bool oldIsTextGenerated = m_address.isTextGenerated(); + m_address.setText(address); + + if (oldText != m_address.text()) + emit textChanged(); + if (oldIsTextGenerated != m_address.isTextGenerated()) + emit isTextGeneratedChanged(); +} + +/*! + \qmlproperty string QtPositioning::Address::country + + This property holds the country of the address as a single formatted string. +*/ +QString QDeclarativeGeoAddress::country() const +{ + return m_address.country(); +} + +void QDeclarativeGeoAddress::setCountry(const QString &country) +{ + if (m_address.country() == country) + return; + QString oldText = m_address.text(); + m_address.setCountry(country); + emit countryChanged(); + + if (m_address.isTextGenerated() && oldText != m_address.text()) + emit textChanged(); +} + +/*! + \qmlproperty string QtPositioning::Address::countryCode + + This property holds the country code of the address as a single formatted string. +*/ +QString QDeclarativeGeoAddress::countryCode() const +{ + return m_address.countryCode(); +} + +void QDeclarativeGeoAddress::setCountryCode(const QString &countryCode) +{ + if (m_address.countryCode() == countryCode) + return; + QString oldText = m_address.text(); + m_address.setCountryCode(countryCode); + emit countryCodeChanged(); + + if (m_address.isTextGenerated() && oldText != m_address.text()) + emit textChanged(); +} + +/*! + \qmlproperty string QtPositioning::Address::state + + This property holds the state of the address as a single formatted string. +*/ +QString QDeclarativeGeoAddress::state() const +{ + return m_address.state(); +} + +void QDeclarativeGeoAddress::setState(const QString &state) +{ + if (m_address.state() == state) + return; + QString oldText = m_address.text(); + m_address.setState(state); + emit stateChanged(); + + if (m_address.isTextGenerated() && oldText != m_address.text()) + emit textChanged(); +} + +/*! + \qmlproperty string QtPositioning::Address::county + + This property holds the county of the address as a single formatted string. +*/ +QString QDeclarativeGeoAddress::county() const +{ + return m_address.county(); +} + +void QDeclarativeGeoAddress::setCounty(const QString &county) +{ + if (m_address.county() == county) + return; + QString oldText = m_address.text(); + m_address.setCounty(county); + emit countyChanged(); + + if (m_address.isTextGenerated() && oldText != m_address.text()) + emit textChanged(); +} + +/*! + \qmlproperty string QtPositioning::Address::city + + This property holds the city of the address as a single formatted string. +*/ +QString QDeclarativeGeoAddress::city() const +{ + return m_address.city(); +} + +void QDeclarativeGeoAddress::setCity(const QString &city) +{ + if (m_address.city() == city) + return; + QString oldText = m_address.text(); + m_address.setCity(city); + emit cityChanged(); + + if (m_address.isTextGenerated() && oldText != m_address.text()) + emit textChanged(); +} + +/*! + \qmlproperty string QtPositioning::Address::district + + This property holds the district of the address as a single formatted string. +*/ +QString QDeclarativeGeoAddress::district() const +{ + return m_address.district(); +} + +void QDeclarativeGeoAddress::setDistrict(const QString &district) +{ + if (m_address.district() == district) + return; + QString oldText = m_address.text(); + m_address.setDistrict(district); + emit districtChanged(); + + if (m_address.isTextGenerated() && oldText != m_address.text()) + emit textChanged(); +} + +/*! + \qmlproperty string QtPositioning::Address::street + + This property holds the street of the address but + may also contain things like a unit number, a building + name, or anything else that might be used to + distinguish one address from another. +*/ +QString QDeclarativeGeoAddress::street() const +{ + return m_address.street(); +} + +void QDeclarativeGeoAddress::setStreet(const QString &street) +{ + if (m_address.street() == street) + return; + QString oldText = m_address.text(); + m_address.setStreet(street); + emit streetChanged(); + + if (m_address.isTextGenerated() && oldText != m_address.text()) + emit textChanged(); +} + +/*! + \qmlproperty string QtPositioning::Address::postalCode + + This property holds the postal code of the address as a single formatted string. +*/ +QString QDeclarativeGeoAddress::postalCode() const +{ + return m_address.postalCode(); +} + +void QDeclarativeGeoAddress::setPostalCode(const QString &postalCode) +{ + if (m_address.postalCode() == postalCode) + return; + QString oldText = m_address.text(); + m_address.setPostalCode(postalCode); + emit postalCodeChanged(); + + if (m_address.isTextGenerated() && oldText != m_address.text()) + emit textChanged(); +} + +/*! + \qmlproperty bool QtPositioning::Address::isTextGenerated + + This property holds a boolean that if true, indicates that \l text is automatically + generated from address properties. If false, it indicates that the \l text has been + explicitly assigned. + +*/ +bool QDeclarativeGeoAddress::isTextGenerated() const +{ + return m_address.isTextGenerated(); +} + +#include "moc_qdeclarativegeoaddress_p.cpp" + +QT_END_NAMESPACE diff --git a/src/positioning/qdeclarativegeoaddress_p.h b/src/positioning/qdeclarativegeoaddress_p.h new file mode 100644 index 0000000..3285cea --- /dev/null +++ b/src/positioning/qdeclarativegeoaddress_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +***************************************************************************/ + +#ifndef QDECLARATIVEGEOADDRESS_P_H +#define QDECLARATIVEGEOADDRESS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_POSITIONING_EXPORT QDeclarativeGeoAddress : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QGeoAddress address READ address WRITE setAddress) + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(QString country READ country WRITE setCountry NOTIFY countryChanged) + Q_PROPERTY(QString countryCode READ countryCode WRITE setCountryCode NOTIFY countryCodeChanged) + Q_PROPERTY(QString state READ state WRITE setState NOTIFY stateChanged) + Q_PROPERTY(QString county READ county WRITE setCounty NOTIFY countyChanged) + Q_PROPERTY(QString city READ city WRITE setCity NOTIFY cityChanged) + Q_PROPERTY(QString district READ district WRITE setDistrict NOTIFY districtChanged) + Q_PROPERTY(QString street READ street WRITE setStreet NOTIFY streetChanged) + Q_PROPERTY(QString postalCode READ postalCode WRITE setPostalCode NOTIFY postalCodeChanged) + Q_PROPERTY(bool isTextGenerated READ isTextGenerated NOTIFY isTextGeneratedChanged) + +public: + explicit QDeclarativeGeoAddress(QObject *parent = 0); + QDeclarativeGeoAddress(const QGeoAddress &address, QObject *parent = 0); + QGeoAddress address() const; + void setAddress(const QGeoAddress &address); + + QString text() const; + void setText(const QString &address); + + QString country() const; + void setCountry(const QString &country); + QString countryCode() const; + void setCountryCode(const QString &countryCode); + QString state() const; + void setState(const QString &state); + QString county() const; + void setCounty(const QString &county); + QString city() const; + void setCity(const QString &city); + QString district() const; + void setDistrict(const QString &district); + QString street() const; + void setStreet(const QString &street); + QString postalCode() const; + void setPostalCode(const QString &postalCode); + bool isTextGenerated() const; + +Q_SIGNALS: + void textChanged(); + void countryChanged(); + void countryCodeChanged(); + void stateChanged(); + void countyChanged(); + void cityChanged(); + void districtChanged(); + void streetChanged(); + void postalCodeChanged(); + void isTextGeneratedChanged(); + +private: + QGeoAddress m_address; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVEGEOADDRESS_P_H diff --git a/src/positioning/qdeclarativegeolocation.cpp b/src/positioning/qdeclarativegeolocation.cpp new file mode 100644 index 0000000..9e3b71e --- /dev/null +++ b/src/positioning/qdeclarativegeolocation.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeolocation_p.h" + +QT_USE_NAMESPACE + +/*! + \qmltype Location + \instantiates QDeclarativeGeoLocation + \inqmlmodule QtPositioning + \since 5.2 + + \brief The Location type holds location data. + + Location types represent a geographic "location", in a human sense. This + consists of a specific \l {coordinate}, an \l {address} and a \l {boundingBox}{bounding box}. + The \l {boundingBox}{bounding box} represents the recommended region + to display when viewing this location. + + The Location type is most commonly seen as the contents of a search + model such as the GeocodeModel. When a GeocodeModel returns the list of + locations found for a given query, it represents these as Location objects. + + \section2 Example Usage + + The following example shows a simple Location object being declared: + + \code + Location { + coordinate { + latitude: -27.3 + longitude: 153.1 + } + address: Address { + ... + } + } + \endcode +*/ + +QDeclarativeGeoLocation::QDeclarativeGeoLocation(QObject *parent) +: QObject(parent), m_address(0) + +{ + setLocation(QGeoLocation()); +} + +QDeclarativeGeoLocation::QDeclarativeGeoLocation(const QGeoLocation &src, QObject *parent) +: QObject(parent), m_address(0) +{ + setLocation(src); +} + +QDeclarativeGeoLocation::~QDeclarativeGeoLocation() +{ +} + +/*! + \qmlproperty QGeoLocation QtPositioning::Location::location + + For details on how to use this property to interface between C++ and QML see + "\l {Location - QGeoLocation} {Interfaces between C++ and QML Code}". +*/ +void QDeclarativeGeoLocation::setLocation(const QGeoLocation &src) +{ + if (m_address && m_address->parent() == this) { + m_address->setAddress(src.address()); + } else if (!m_address || m_address->parent() != this) { + m_address = new QDeclarativeGeoAddress(src.address(), this); + emit addressChanged(); + } + + setCoordinate(src.coordinate()); + setBoundingBox(src.boundingBox()); +} + +QGeoLocation QDeclarativeGeoLocation::location() const +{ + QGeoLocation retValue; + retValue.setAddress(m_address ? m_address->address() : QGeoAddress()); + retValue.setCoordinate(m_coordinate); + retValue.setBoundingBox(m_boundingBox); + return retValue; +} + +/*! + \qmlproperty Address QtPositioning::Location::address + + This property holds the address of the location which can be use to retrieve address details of the location. +*/ +void QDeclarativeGeoLocation::setAddress(QDeclarativeGeoAddress *address) +{ + if (m_address == address) + return; + + if (m_address && m_address->parent() == this) + delete m_address; + + m_address = address; + emit addressChanged(); +} + +QDeclarativeGeoAddress *QDeclarativeGeoLocation::address() const +{ + return m_address; +} + +/*! + \qmlproperty coordinate QtPositioning::Location::coordinate + + This property holds the exact geographical coordinate of the location which can be used to retrieve the latitude, longitude and altitude of the location. + + \note this property's changed() signal is currently emitted only if the + whole object changes, not if only the contents of the object change. +*/ +void QDeclarativeGeoLocation::setCoordinate(const QGeoCoordinate coordinate) +{ + if (m_coordinate == coordinate) + return; + + m_coordinate = coordinate; + emit coordinateChanged(); +} + +QGeoCoordinate QDeclarativeGeoLocation::coordinate() const +{ + return m_coordinate; +} + +/*! + \qmlproperty georectangle QtPositioning::Location::boundingBox + + This property holds the recommended region to use when displaying the location. + For example, a building's location may have a region centered around the building, + but the region is large enough to show it's immediate surrounding geographical + context. + + Note: this property's changed() signal is currently emitted only if the + whole object changes, not if only the contents of the object change. +*/ +void QDeclarativeGeoLocation::setBoundingBox(const QGeoRectangle &boundingBox) +{ + if (m_boundingBox == boundingBox) + return; + + m_boundingBox = boundingBox; + emit boundingBoxChanged(); +} + +QGeoRectangle QDeclarativeGeoLocation::boundingBox() const +{ + return m_boundingBox; +} diff --git a/src/positioning/qdeclarativegeolocation_p.h b/src/positioning/qdeclarativegeolocation_p.h new file mode 100644 index 0000000..b208e50 --- /dev/null +++ b/src/positioning/qdeclarativegeolocation_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOLOCATION_P_H +#define QDECLARATIVEGEOLOCATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_POSITIONING_EXPORT QDeclarativeGeoLocation : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QGeoLocation location READ location WRITE setLocation) + Q_PROPERTY(QDeclarativeGeoAddress *address READ address WRITE setAddress NOTIFY addressChanged) + Q_PROPERTY(QGeoCoordinate coordinate READ coordinate WRITE setCoordinate NOTIFY coordinateChanged) + Q_PROPERTY(QGeoRectangle boundingBox READ boundingBox WRITE setBoundingBox NOTIFY boundingBoxChanged) + +public: + explicit QDeclarativeGeoLocation(QObject *parent = 0); + explicit QDeclarativeGeoLocation(const QGeoLocation &src, QObject *parent = 0); + ~QDeclarativeGeoLocation(); + + QGeoLocation location() const; + void setLocation(const QGeoLocation &src); + + QDeclarativeGeoAddress *address() const; + void setAddress(QDeclarativeGeoAddress *address); + QGeoCoordinate coordinate() const; + void setCoordinate(const QGeoCoordinate coordinate); + + QGeoRectangle boundingBox() const; + void setBoundingBox(const QGeoRectangle &boundingBox); + +Q_SIGNALS: + void addressChanged(); + void coordinateChanged(); + void boundingBoxChanged(); + +private: + QDeclarativeGeoAddress *m_address; + QGeoRectangle m_boundingBox; + QGeoCoordinate m_coordinate; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVELOCATION_P_H diff --git a/src/positioning/qdoublevector2d.cpp b/src/positioning/qdoublevector2d.cpp new file mode 100644 index 0000000..c18f236 --- /dev/null +++ b/src/positioning/qdoublevector2d.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdoublevector2d_p.h" +#include "qdoublevector3d_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QDoubleVector2D::QDoubleVector2D(const QDoubleVector3D &vector) : + xp(vector.xp), yp(vector.yp) +{ +} + +double QDoubleVector2D::length() const +{ + return qSqrt(xp * xp + yp * yp); +} + +QDoubleVector2D QDoubleVector2D::normalized() const +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp); + if (qFuzzyIsNull(len - 1.0)) + return *this; + else if (!qFuzzyIsNull(len)) + return *this / (double)qSqrt(len); + else + return QDoubleVector2D(); +} + +void QDoubleVector2D::normalize() +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp); + if (qFuzzyIsNull(len - 1.0) || qFuzzyIsNull(len)) + return; + + len = qSqrt(len); + + xp /= len; + yp /= len; +} + +QDoubleVector3D QDoubleVector2D::toVector3D() const +{ + return QDoubleVector3D(xp, yp, 0.0); +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug dbg, const QDoubleVector2D &vector) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QDoubleVector2D(" << vector.x() << ", " << vector.y() << ')'; + return dbg; +} + +#endif + +#ifndef QT_NO_DATASTREAM + +QDataStream &operator<<(QDataStream &stream, const QDoubleVector2D &vector) +{ + stream << double(vector.x()) << double(vector.y()); + return stream; +} + +QDataStream &operator>>(QDataStream &stream, QDoubleVector2D &vector) +{ + double x, y; + stream >> x; + stream >> y; + vector.setX(double(x)); + vector.setY(double(y)); + return stream; +} + +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE diff --git a/src/positioning/qdoublevector2d_p.h b/src/positioning/qdoublevector2d_p.h new file mode 100644 index 0000000..c35899e --- /dev/null +++ b/src/positioning/qdoublevector2d_p.h @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDOUBLEVECTOR2D_P_H +#define QDOUBLEVECTOR2D_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifdef QT_BUILD_LOCATION_LIB +#include +#endif + +#include "qpositioningglobal.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QDoubleVector3D; + +class Q_POSITIONING_EXPORT QDoubleVector2D +{ +public: + Q_DECL_CONSTEXPR inline QDoubleVector2D(); + Q_DECL_CONSTEXPR inline QDoubleVector2D(double xpos, double ypos); + Q_DECL_CONSTEXPR explicit inline QDoubleVector2D(const QPointF &p); + explicit QDoubleVector2D(const QDoubleVector3D &vector); + + Q_DECL_CONSTEXPR inline double manhattanLength() const; + inline bool isNull() const; + + Q_DECL_CONSTEXPR inline double x() const; + Q_DECL_CONSTEXPR inline double y() const; + + inline void setX(double x); + inline void setY(double y); + + double length() const; + Q_DECL_CONSTEXPR inline double lengthSquared() const; + + QDoubleVector2D normalized() const; + void normalize(); + + inline QDoubleVector2D &operator+=(const QDoubleVector2D &vector); + inline QDoubleVector2D &operator-=(const QDoubleVector2D &vector); + inline QDoubleVector2D &operator*=(double factor); + inline QDoubleVector2D &operator*=(const QDoubleVector2D &vector); + inline QDoubleVector2D &operator/=(double divisor); + + Q_DECL_CONSTEXPR static inline double dotProduct(const QDoubleVector2D &v1, const QDoubleVector2D &v2) + { return v1.xp * v2.xp + v1.yp * v2.yp; } + + + friend Q_DECL_CONSTEXPR inline bool operator==(const QDoubleVector2D &v1, const QDoubleVector2D &v2); + friend Q_DECL_CONSTEXPR inline bool operator!=(const QDoubleVector2D &v1, const QDoubleVector2D &v2); + friend Q_DECL_CONSTEXPR inline const QDoubleVector2D operator+(const QDoubleVector2D &v1, const QDoubleVector2D &v2); + friend Q_DECL_CONSTEXPR inline const QDoubleVector2D operator-(const QDoubleVector2D &v1, const QDoubleVector2D &v2); + friend Q_DECL_CONSTEXPR inline const QDoubleVector2D operator*(double factor, const QDoubleVector2D &vector); + friend Q_DECL_CONSTEXPR inline const QDoubleVector2D operator*(const QDoubleVector2D &vector, double factor); + friend Q_DECL_CONSTEXPR inline const QDoubleVector2D operator*(const QDoubleVector2D &v1, const QDoubleVector2D &v2); + friend Q_DECL_CONSTEXPR inline const QDoubleVector2D operator-(const QDoubleVector2D &vector); + friend Q_DECL_CONSTEXPR inline const QDoubleVector2D operator/(const QDoubleVector2D &vector, double divisor); + + friend Q_DECL_CONSTEXPR inline bool qFuzzyCompare(const QDoubleVector2D &v1, const QDoubleVector2D &v2); + + QDoubleVector3D toVector3D() const; + Q_DECL_CONSTEXPR inline QPointF toPointF() const; + +private: + double xp, yp; + + friend class QDoubleVector3D; +}; + +Q_DECLARE_TYPEINFO(QDoubleVector2D, Q_MOVABLE_TYPE); + +Q_DECL_CONSTEXPR inline QDoubleVector2D::QDoubleVector2D() : xp(0.0), yp(0.0) {} + +Q_DECL_CONSTEXPR inline QDoubleVector2D::QDoubleVector2D(double xpos, double ypos) : xp(xpos), yp(ypos) {} + +Q_DECL_CONSTEXPR inline QDoubleVector2D::QDoubleVector2D(const QPointF &p) : xp(p.x()), yp(p.y()) { } + +Q_DECL_CONSTEXPR inline double QDoubleVector2D::manhattanLength() const +{ + return qAbs(x())+qAbs(y()); +} + +inline bool QDoubleVector2D::isNull() const +{ + return qIsNull(xp) && qIsNull(yp); +} + +Q_DECL_CONSTEXPR inline double QDoubleVector2D::x() const { return xp; } +Q_DECL_CONSTEXPR inline double QDoubleVector2D::y() const { return yp; } + +inline void QDoubleVector2D::setX(double aX) { xp = aX; } +inline void QDoubleVector2D::setY(double aY) { yp = aY; } + +Q_DECL_CONSTEXPR inline double QDoubleVector2D::lengthSquared() const +{ return xp * xp + yp * yp; } + +inline QDoubleVector2D &QDoubleVector2D::operator+=(const QDoubleVector2D &vector) +{ + xp += vector.xp; + yp += vector.yp; + return *this; +} + +inline QDoubleVector2D &QDoubleVector2D::operator-=(const QDoubleVector2D &vector) +{ + xp -= vector.xp; + yp -= vector.yp; + return *this; +} + +inline QDoubleVector2D &QDoubleVector2D::operator*=(double factor) +{ + xp *= factor; + yp *= factor; + return *this; +} + +inline QDoubleVector2D &QDoubleVector2D::operator*=(const QDoubleVector2D &vector) +{ + xp *= vector.xp; + yp *= vector.yp; + return *this; +} + +inline QDoubleVector2D &QDoubleVector2D::operator/=(double divisor) +{ + xp /= divisor; + yp /= divisor; + return *this; +} + +Q_DECL_CONSTEXPR inline bool operator==(const QDoubleVector2D &v1, const QDoubleVector2D &v2) +{ + return v1.xp == v2.xp && v1.yp == v2.yp; +} + +Q_DECL_CONSTEXPR inline bool operator!=(const QDoubleVector2D &v1, const QDoubleVector2D &v2) +{ + return v1.xp != v2.xp || v1.yp != v2.yp; +} + +Q_DECL_CONSTEXPR inline const QDoubleVector2D operator+(const QDoubleVector2D &v1, const QDoubleVector2D &v2) +{ + return QDoubleVector2D(v1.xp + v2.xp, v1.yp + v2.yp); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector2D operator-(const QDoubleVector2D &v1, const QDoubleVector2D &v2) +{ + return QDoubleVector2D(v1.xp - v2.xp, v1.yp - v2.yp); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector2D operator*(double factor, const QDoubleVector2D &vector) +{ + return QDoubleVector2D(vector.xp * factor, vector.yp * factor); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector2D operator*(const QDoubleVector2D &vector, double factor) +{ + return QDoubleVector2D(vector.xp * factor, vector.yp * factor); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector2D operator*(const QDoubleVector2D &v1, const QDoubleVector2D &v2) +{ + return QDoubleVector2D(v1.xp * v2.xp, v1.yp * v2.yp); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector2D operator-(const QDoubleVector2D &vector) +{ + return QDoubleVector2D(-vector.xp, -vector.yp); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector2D operator/(const QDoubleVector2D &vector, double divisor) +{ + return QDoubleVector2D(vector.xp / divisor, vector.yp / divisor); +} + +Q_DECL_CONSTEXPR inline bool qFuzzyCompare(const QDoubleVector2D &v1, const QDoubleVector2D &v2) +{ + return qFuzzyCompare(v1.xp, v2.xp) && qFuzzyCompare(v1.yp, v2.yp); +} + +Q_DECL_CONSTEXPR inline QPointF QDoubleVector2D::toPointF() const +{ + return QPointF(qreal(xp), qreal(yp)); +} + +#ifndef QT_NO_DEBUG_STREAM +Q_POSITIONING_EXPORT QDebug operator<<(QDebug dbg, const QDoubleVector2D &vector); +#endif + +#ifndef QT_NO_DATASTREAM +Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &, const QDoubleVector2D &); +Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &, QDoubleVector2D &); +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qdoublevector3d.cpp b/src/positioning/qdoublevector3d.cpp new file mode 100644 index 0000000..b308084 --- /dev/null +++ b/src/positioning/qdoublevector3d.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdoublevector3d_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QDoubleVector3D QDoubleVector3D::normalized() const +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp) + + double(zp) * double(zp); + if (qFuzzyIsNull(len - 1.0)) + return *this; + else if (!qFuzzyIsNull(len)) + return *this / (double)qSqrt(len); + else + return QDoubleVector3D(); +} + +void QDoubleVector3D::normalize() +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp) + + double(zp) * double(zp); + if (qFuzzyIsNull(len - 1.0) || qFuzzyIsNull(len)) + return; + + len = qSqrt(len); + + xp /= len; + yp /= len; + zp /= len; +} + +QDoubleVector3D QDoubleVector3D::normal(const QDoubleVector3D &v1, const QDoubleVector3D &v2) +{ + return crossProduct(v1, v2).normalized(); +} + +QDoubleVector3D QDoubleVector3D::normal + (const QDoubleVector3D &v1, const QDoubleVector3D &v2, const QDoubleVector3D &v3) +{ + return crossProduct((v2 - v1), (v3 - v1)).normalized(); +} + +double QDoubleVector3D::distanceToPlane + (const QDoubleVector3D &plane1, const QDoubleVector3D &plane2, const QDoubleVector3D &plane3) const +{ + QDoubleVector3D n = normal(plane2 - plane1, plane3 - plane1); + return dotProduct(*this - plane1, n); +} + +double QDoubleVector3D::distanceToLine + (const QDoubleVector3D &point, const QDoubleVector3D &direction) const +{ + if (direction.isNull()) + return (*this - point).length(); + QDoubleVector3D p = point + dotProduct(*this - point, direction) * direction; + return (*this - p).length(); +} + +double QDoubleVector3D::length() const +{ + return qSqrt(xp * xp + yp * yp + zp * zp); +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug dbg, const QDoubleVector3D &vector) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QDoubleVector3D(" + << vector.x() << ", " << vector.y() << ", " << vector.z() << ')'; + return dbg; +} + +#endif + +#ifndef QT_NO_DATASTREAM + +QDataStream &operator<<(QDataStream &stream, const QDoubleVector3D &vector) +{ + stream << double(vector.x()) << double(vector.y()) + << double(vector.z()); + return stream; +} + +QDataStream &operator>>(QDataStream &stream, QDoubleVector3D &vector) +{ + double x, y, z; + stream >> x; + stream >> y; + stream >> z; + vector.setX(double(x)); + vector.setY(double(y)); + vector.setZ(double(z)); + return stream; +} + +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE diff --git a/src/positioning/qdoublevector3d_p.h b/src/positioning/qdoublevector3d_p.h new file mode 100644 index 0000000..d500fbe --- /dev/null +++ b/src/positioning/qdoublevector3d_p.h @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDOUBLEVECTOR3D_P_H +#define QDOUBLEVECTOR3D_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifdef QT_BUILD_LOCATION_LIB +#include +#endif + +#include "qpositioningglobal.h" +#include "qdoublevector2d_p.h" +#include + +QT_BEGIN_NAMESPACE + +class Q_POSITIONING_EXPORT QDoubleVector3D +{ +public: + Q_DECL_CONSTEXPR inline QDoubleVector3D(); + Q_DECL_CONSTEXPR inline QDoubleVector3D(double xpos, double ypos, double zpos); + Q_DECL_CONSTEXPR inline QDoubleVector3D(const QDoubleVector2D &vector); + Q_DECL_CONSTEXPR inline QDoubleVector3D(const QDoubleVector2D &vector, double zpos); + + inline bool isNull() const; + + Q_DECL_CONSTEXPR inline double x() const; + Q_DECL_CONSTEXPR inline double y() const; + Q_DECL_CONSTEXPR inline double z() const; + + inline void setX(double x); + inline void setY(double y); + inline void setZ(double z); + + inline double get(int i) const; + inline void set(int i, double value); + + double length() const; + Q_DECL_CONSTEXPR inline double lengthSquared() const; + + QDoubleVector3D normalized() const; + void normalize(); + + inline QDoubleVector3D &operator+=(const QDoubleVector3D &vector); + inline QDoubleVector3D &operator-=(const QDoubleVector3D &vector); + inline QDoubleVector3D &operator*=(double factor); + inline QDoubleVector3D &operator*=(const QDoubleVector3D &vector); + inline QDoubleVector3D &operator/=(double divisor); + + Q_DECL_CONSTEXPR static inline double dotProduct(const QDoubleVector3D &v1, const QDoubleVector3D &v2) + { return v1.xp * v2.xp + v1.yp * v2.yp + v1.zp * v2.zp; } + + Q_DECL_CONSTEXPR static inline QDoubleVector3D crossProduct(const QDoubleVector3D &v1, const QDoubleVector3D &v2) + { return QDoubleVector3D(v1.yp * v2.zp - v1.zp * v2.yp, + v1.zp * v2.xp - v1.xp * v2.zp, + v1.xp * v2.yp - v1.yp * v2.xp); } + + static QDoubleVector3D normal(const QDoubleVector3D &v1, const QDoubleVector3D &v2); + static QDoubleVector3D normal + (const QDoubleVector3D &v1, const QDoubleVector3D &v2, const QDoubleVector3D &v3); + + double distanceToPlane(const QDoubleVector3D &plane, const QDoubleVector3D &normal) const; + double distanceToPlane(const QDoubleVector3D &plane1, const QDoubleVector3D &plane2, const QDoubleVector3D &plane3) const; + double distanceToLine(const QDoubleVector3D &point, const QDoubleVector3D &direction) const; + + friend Q_DECL_CONSTEXPR inline bool operator==(const QDoubleVector3D &v1, const QDoubleVector3D &v2); + friend Q_DECL_CONSTEXPR inline bool operator!=(const QDoubleVector3D &v1, const QDoubleVector3D &v2); + friend Q_DECL_CONSTEXPR inline const QDoubleVector3D operator+(const QDoubleVector3D &v1, const QDoubleVector3D &v2); + friend Q_DECL_CONSTEXPR inline const QDoubleVector3D operator-(const QDoubleVector3D &v1, const QDoubleVector3D &v2); + friend Q_DECL_CONSTEXPR inline const QDoubleVector3D operator*(double factor, const QDoubleVector3D &vector); + friend Q_DECL_CONSTEXPR inline const QDoubleVector3D operator*(const QDoubleVector3D &vector, double factor); + friend Q_DECL_CONSTEXPR inline const QDoubleVector3D operator*(const QDoubleVector3D &v1, const QDoubleVector3D &v2); + friend Q_DECL_CONSTEXPR inline const QDoubleVector3D operator-(const QDoubleVector3D &vector); + friend Q_DECL_CONSTEXPR inline const QDoubleVector3D operator/(const QDoubleVector3D &vector, double divisor); + + friend Q_DECL_CONSTEXPR inline bool qFuzzyCompare(const QDoubleVector3D &v1, const QDoubleVector3D &v2); + + Q_DECL_CONSTEXPR inline QDoubleVector2D toVector2D() const; + +private: + double xp, yp, zp; + + friend class QDoubleVector2D; +}; + +Q_DECLARE_TYPEINFO(QDoubleVector3D, Q_MOVABLE_TYPE); + +Q_DECL_CONSTEXPR inline QDoubleVector3D::QDoubleVector3D() : xp(0.0), yp(0.0), zp(0.0) {} + +Q_DECL_CONSTEXPR inline QDoubleVector3D::QDoubleVector3D(double xpos, double ypos, double zpos) : xp(xpos), yp(ypos), zp(zpos) {} + +Q_DECL_CONSTEXPR inline QDoubleVector3D::QDoubleVector3D(const QDoubleVector2D &v) + : xp(v.xp), yp(v.yp), zp(0.0) {} + +Q_DECL_CONSTEXPR inline QDoubleVector3D::QDoubleVector3D(const QDoubleVector2D &v, double zpos) + : xp(v.xp), yp(v.yp), zp(zpos) {} + +inline bool QDoubleVector3D::isNull() const +{ + return qIsNull(xp) && qIsNull(yp) && qIsNull(zp); +} + +Q_DECL_CONSTEXPR inline double QDoubleVector3D::x() const { return xp; } +Q_DECL_CONSTEXPR inline double QDoubleVector3D::y() const { return yp; } +Q_DECL_CONSTEXPR inline double QDoubleVector3D::z() const { return zp; } + +Q_DECL_CONSTEXPR inline double QDoubleVector3D::lengthSquared() const +{ return xp * xp + yp * yp + zp * zp; } + + +inline void QDoubleVector3D::setX(double aX) { xp = aX; } +inline void QDoubleVector3D::setY(double aY) { yp = aY; } +inline void QDoubleVector3D::setZ(double aZ) { zp = aZ; } + +inline double QDoubleVector3D::get(int i) const +{ + switch (i) { + case 0: + return xp; + case 1: + return yp; + case 2: + return zp; + default: + return 0.0; + } +} + +inline void QDoubleVector3D::set(int i, double value) +{ + switch (i) { + case 0: + xp = value; + break; + case 1: + yp = value; + break; + case 2: + zp = value; + break; + default: + break; + } +} + +inline QDoubleVector3D &QDoubleVector3D::operator+=(const QDoubleVector3D &vector) +{ + xp += vector.xp; + yp += vector.yp; + zp += vector.zp; + return *this; +} + +inline QDoubleVector3D &QDoubleVector3D::operator-=(const QDoubleVector3D &vector) +{ + xp -= vector.xp; + yp -= vector.yp; + zp -= vector.zp; + return *this; +} + +inline QDoubleVector3D &QDoubleVector3D::operator*=(double factor) +{ + xp *= factor; + yp *= factor; + zp *= factor; + return *this; +} + +inline QDoubleVector3D &QDoubleVector3D::operator*=(const QDoubleVector3D &vector) +{ + xp *= vector.xp; + yp *= vector.yp; + zp *= vector.zp; + return *this; +} + +inline QDoubleVector3D &QDoubleVector3D::operator/=(double divisor) +{ + xp /= divisor; + yp /= divisor; + zp /= divisor; + return *this; +} + +Q_DECL_CONSTEXPR inline bool operator==(const QDoubleVector3D &v1, const QDoubleVector3D &v2) +{ + return v1.xp == v2.xp && v1.yp == v2.yp && v1.zp == v2.zp; +} + +Q_DECL_CONSTEXPR inline bool operator!=(const QDoubleVector3D &v1, const QDoubleVector3D &v2) +{ + return v1.xp != v2.xp || v1.yp != v2.yp || v1.zp != v2.zp; +} + +Q_DECL_CONSTEXPR inline const QDoubleVector3D operator+(const QDoubleVector3D &v1, const QDoubleVector3D &v2) +{ + return QDoubleVector3D(v1.xp + v2.xp, v1.yp + v2.yp, v1.zp + v2.zp); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector3D operator-(const QDoubleVector3D &v1, const QDoubleVector3D &v2) +{ + return QDoubleVector3D(v1.xp - v2.xp, v1.yp - v2.yp, v1.zp - v2.zp); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector3D operator*(double factor, const QDoubleVector3D &vector) +{ + return QDoubleVector3D(vector.xp * factor, vector.yp * factor, vector.zp * factor); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector3D operator*(const QDoubleVector3D &vector, double factor) +{ + return QDoubleVector3D(vector.xp * factor, vector.yp * factor, vector.zp * factor); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector3D operator*(const QDoubleVector3D &v1, const QDoubleVector3D &v2) +{ + return QDoubleVector3D(v1.xp * v2.xp, v1.yp * v2.yp, v1.zp * v2.zp); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector3D operator-(const QDoubleVector3D &vector) +{ + return QDoubleVector3D(-vector.xp, -vector.yp, -vector.zp); +} + +Q_DECL_CONSTEXPR inline const QDoubleVector3D operator/(const QDoubleVector3D &vector, double divisor) +{ + return QDoubleVector3D(vector.xp / divisor, vector.yp / divisor, vector.zp / divisor); +} + +Q_DECL_CONSTEXPR inline bool qFuzzyCompare(const QDoubleVector3D &v1, const QDoubleVector3D &v2) +{ + return qFuzzyCompare(v1.xp, v2.xp) && + qFuzzyCompare(v1.yp, v2.yp) && + qFuzzyCompare(v1.zp, v2.zp); +} + +Q_DECL_CONSTEXPR inline QDoubleVector2D QDoubleVector3D::toVector2D() const +{ + return QDoubleVector2D(xp, yp); +} + + +#ifndef QT_NO_DEBUG_STREAM +Q_POSITIONING_EXPORT QDebug operator<<(QDebug dbg, const QDoubleVector3D &vector); +#endif + +#ifndef QT_NO_DATASTREAM +Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &, const QDoubleVector3D &); +Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &, QDoubleVector3D &); +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeoaddress.cpp b/src/positioning/qgeoaddress.cpp new file mode 100644 index 0000000..6f08f2d --- /dev/null +++ b/src/positioning/qgeoaddress.cpp @@ -0,0 +1,635 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoaddress.h" +#include "qgeoaddress_p.h" + +#include + +#ifdef QGEOADDRESS_DEBUG +#include +#endif + +QT_BEGIN_NAMESPACE + +/* + Combines a list of address parts into a single line. + + The parts parameter contains both address elements such as city, state and so on + as well as separators such as spaces and commas. + + It is expected that an element is always followed by a separator and the last + sepator is usually a new line delimeter. + + For example: Springfield, 8900 + would have four parts + ["Springfield", ", ", "8900", "
    "] + + The addressLine takes care of putting in separators appropriately or leaving + them out depending on whether the adjacent elements are present or not. + For example if city were empty in the above scenario the returned string is "8900
    " + If the postal code was empty, returned string is "Springfield
    " + If both city and postal code were empty, the returned string is "". +*/ +static QString addressLine(const QStringList &parts) +{ + QString line; + Q_ASSERT(parts.count() % 2 == 0); + + //iterate until just before the last pair + QString penultimateSeparator; + for (int i = 0; i < parts.count() - 2; i += 2) { + if (!parts.at(i).isEmpty()) { + line.append(parts.at(i) + parts.at(i + 1)); + penultimateSeparator = parts.at(i + 1); + } + } + + if (parts.at(parts.count() - 2).isEmpty()) { + line.chop(penultimateSeparator.length()); + + if (!line.isEmpty()) + line.append(parts.at(parts.count() - 1)); + } else { + line.append(parts.at(parts.count() - 2)); + line.append(parts.at(parts.count() - 1)); + } + + return line; +} + +/* + Returns a single formatted string representing the \a address. Lines of the address + are delimited by \a newLine. By default lines are delimited by
    . The \l + {QGeoAddress::countryCode} {countryCode} of the \a address determines the format of + the resultant string. +*/ +static QString formattedAddress(const QGeoAddress &address, + const QString &newLine = QLatin1String("
    ")) +{ + const QString Comma(QStringLiteral(", ")); + const QString Dash(QStringLiteral("-")); + const QString Space(QStringLiteral(" ")); + + QString text; + + if (address.countryCode() == QLatin1String("ALB") + || address.countryCode() == QLatin1String("MTQ")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.postalCode() << Comma + << address.city() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("AND") + || address.countryCode() == QLatin1String("AUT") + || address.countryCode() == QLatin1String("FRA") + || address.countryCode() == QLatin1String("GLP") + || address.countryCode() == QLatin1String("GUF") + || address.countryCode() == QLatin1String("ITA") + || address.countryCode() == QLatin1String("LUX") + || address.countryCode() == QLatin1String("MCO") + || address.countryCode() == QLatin1String("REU") + || address.countryCode() == QLatin1String("RUS") + || address.countryCode() == QLatin1String("SMR") + || address.countryCode() == QLatin1String("VAT")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.postalCode() << Space + << address.city() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("ARE") + || address.countryCode() == QLatin1String("BHS")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Space + << address.city() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("AUS")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << (address.district().isEmpty() ? address.city() : address.district()) + << Space << address.state() << Space << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("BHR")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Comma + << address.city() << Comma << address.state() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("BRA")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Space + << address.city() << Dash << address.state() << Space << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("BRN") + || address.countryCode() == QLatin1String("JOR") + || address.countryCode() == QLatin1String("LBN") + || address.countryCode() == QLatin1String("NZL")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Space + << address.city() << Space << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("CAN") + || address.countryCode() == QLatin1String("USA") + || address.countryCode() == QLatin1String("VIR")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.city() << Comma << address.state() << Space + << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("CHN")) { + text += addressLine(QStringList() << address.street() << Comma << address.city() << newLine); + text += addressLine(QStringList() << address.postalCode() << Space << address.state() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("CHL")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.postalCode() << Space + << address.district() << Comma << address.city() << Comma + << address.state() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("CYM")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.state() << Space + << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("GBR")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Comma + << address.city() << Comma << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("GIB")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.city() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("HKG")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << newLine); + text += addressLine(QStringList() << address.city() << newLine); + } else if (address.countryCode() == QLatin1String("IND")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.city() << Space << address.postalCode() << Space + << address.state() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("IDN") + || address.countryCode() == QLatin1String("JEY") + || address.countryCode() == QLatin1String("LVA")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.city() << Comma << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("IRL")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Comma << address.state() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("KWT")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.postalCode() << Comma + << address.district() << Comma << address.city() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("MLT") + || address.countryCode() == QLatin1String("SGP") + || address.countryCode() == QLatin1String("UKR")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.city() << Space << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("MEX")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << newLine); + text += addressLine(QStringList() << address.postalCode() << Space << address.city() << Comma + << address.state() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("MYS")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.postalCode() << Space << address.city() << newLine); + text += addressLine(QStringList() << address.state() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("OMN")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Comma + << address.postalCode() << Comma + << address.city() << Comma + << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("PRI")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Comma << address.city() << Comma + << address.state() << Comma << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("QAT")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Space << address.city() << Comma + << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("SAU")) { + text += addressLine(QStringList() << address.street() << Space << address.district() << newLine); + text += addressLine(QStringList() << address.city() << Space << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("TWN")) { + text += addressLine(QStringList() << address.street() << Comma + << address.district() << Comma << address.city() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("THA")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Comma << address.city() << Space + << address.postalCode() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("TUR")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.postalCode() << Space << address.district() << Comma + << address.city() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("VEN")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.city() << Space << address.postalCode() << Comma + << address.state() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else if (address.countryCode() == QLatin1String("ZAF")) { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.district() << Comma << address.city() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } else { + text += addressLine(QStringList() << address.street() << newLine); + text += addressLine(QStringList() << address.postalCode() << Space << address.city() << newLine); + text += addressLine(QStringList() << address.country() << newLine); + } + + text.chop(newLine.length()); + return text; +} + +QGeoAddressPrivate::QGeoAddressPrivate() + : QSharedData(), + m_autoGeneratedText(false) +{ +} + +QGeoAddressPrivate::QGeoAddressPrivate(const QGeoAddressPrivate &other) + : QSharedData(other), + sCountry(other.sCountry), + sCountryCode(other.sCountryCode), + sState(other.sState), + sCounty(other.sCounty), + sCity(other.sCity), + sDistrict(other.sDistrict), + sStreet(other.sStreet), + sPostalCode(other.sPostalCode), + sText(other.sText), + m_autoGeneratedText(false) +{ +} + +QGeoAddressPrivate::~QGeoAddressPrivate() +{ +} + +/*! + \class QGeoAddress + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \ingroup QtLocation-places-data + \ingroup QtLocation-places + \since 5.2 + + \brief The QGeoAddress class represents an address of a \l QGeoLocation. + + The address' attributes are normalized to US feature names and can be mapped + to the local feature levels (for example State matches "Bundesland" in Germany). + + The address contains a \l text() for displaying purposes and additional + properties to access the components of an address: + + \list + \li QGeoAddress::country() + \li QGeoAddress::countryCode() + \li QGeoAddress::state() + \li QGeoAddress::city() + \li QGeoAddress::district() + \li QGeoAddress::street() + \li QGeoAddress::postalCode() + \endlist +*/ + +/*! + Default constructor. +*/ +QGeoAddress::QGeoAddress() + : d(new QGeoAddressPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QGeoAddress::QGeoAddress(const QGeoAddress &other) + : d(other.d) +{ +} + +/*! + Destroys this address. +*/ +QGeoAddress::~QGeoAddress() +{ +} + +/*! + Assigns the given \a address to this address and + returns a reference to this address. +*/ +QGeoAddress &QGeoAddress::operator=(const QGeoAddress & address) +{ + if (this == &address) + return *this; + + d = address.d; + return *this; +} + +/*! + Returns true if this address is equal to \a other, + otherwise returns false. +*/ +bool QGeoAddress::operator==(const QGeoAddress &other) const +{ +#ifdef QGEOADDRESS_DEBUG + qDebug() << "country" << (d->sCountry == other.country()); + qDebug() << "countryCode" << (d->sCountryCode == other.countryCode()); + qDebug() << "state:" << (d->sState == other.state()); + qDebug() << "county:" << (d->sCounty == other.county()); + qDebug() << "city:" << (d->sCity == other.city()); + qDebug() << "district:" << (d->sDistrict == other.district()); + qDebug() << "street:" << (d->sStreet == other.street()); + qDebug() << "postalCode:" << (d->sPostalCode == other.postalCode()); + qDebug() << "text:" << (text() == other.text()); +#endif + + return d->sCountry == other.country() && + d->sCountryCode == other.countryCode() && + d->sState == other.state() && + d->sCounty == other.county() && + d->sCity == other.city() && + d->sDistrict == other.district() && + d->sStreet == other.street() && + d->sPostalCode == other.postalCode() && + this->text() == other.text(); +} + +/*! + \fn bool QGeoAddress::operator!=(const QGeoAddress &other) const + + Returns true if this address is not equal to \a other, + otherwise returns false. +*/ + +/*! + Returns the address as a single formatted string. It is the recommended string + to use to display the address to the user. It typically takes the format of + an address as found on an envelope, but this is not always necessarily the case. + + The address text is either automatically generated or explicitly assigned. + This can be determined by checking \l {QGeoAddress::isTextGenerated()} {isTextGenerated}. + + If an empty string is provided to setText(), then isTextGenerated() will be set + to true and text() will return a string which is locally formatted according to + countryCode() and based on the elements of the address such as street, city and so on. + Because the text string is generated from the address elements, a sequence + of calls such as text(), setStreet(), text() may return different strings for each + invocation of text(). + + If a non-empty string is provided to setText(), then isTextGenerated() will be + set to false and text() will always return the explicitly assigned string. + Calls to modify other elements such as setStreet(), setCity() and so on will not + affect the resultant string from text(). +*/ +QString QGeoAddress::text() const +{ + if (d->sText.isEmpty()) + return formattedAddress(*this); + else + return d->sText; +} + +/*! + If \a text is not empty, explicitly assigns \a text as the string to be returned by + text(). isTextGenerated() will return false. + + If \a text is empty, indicates that text() should be automatically generated + from the address elements. isTextGenerated() will return true. +*/ +void QGeoAddress::setText(const QString &text) +{ + d->sText = text; +} + +/*! + Returns the country name. +*/ +QString QGeoAddress::country() const +{ + return d->sCountry; +} + +/*! + Sets the \a country name. +*/ +void QGeoAddress::setCountry(const QString &country) +{ + d->sCountry = country; +} + +/*! + Returns the country code according to ISO 3166-1 alpha-3 +*/ +QString QGeoAddress::countryCode() const +{ + return d->sCountryCode; +} + +/*! + Sets the \a countryCode according to ISO 3166-1 alpha-3 +*/ +void QGeoAddress::setCountryCode(const QString &countryCode) +{ + d->sCountryCode = countryCode; +} + +/*! + Returns the state. The state is considered the first subdivision below country. +*/ +QString QGeoAddress::state() const +{ + return d->sState; +} + +/*! + Sets the \a state. +*/ +void QGeoAddress::setState(const QString &state) +{ + d->sState = state; +} + +/*! + Returns the county. The county is considered the second subdivision below country. +*/ +QString QGeoAddress::county() const +{ + return d->sCounty; +} + +/*! + Sets the \a county. +*/ +void QGeoAddress::setCounty(const QString &county) +{ + d->sCounty = county; +} + +/*! + Returns the city. +*/ +QString QGeoAddress::city() const +{ + return d->sCity; +} + +/*! + Sets the \a city. +*/ +void QGeoAddress::setCity(const QString &city) +{ + d->sCity = city; +} + +/*! + Returns the district. The district is considered the subdivison below city. +*/ +QString QGeoAddress::district() const +{ + return d->sDistrict; +} + +/*! + Sets the \a district. +*/ +void QGeoAddress::setDistrict(const QString &district) +{ + d->sDistrict = district; +} + +/*! + Returns the street-level component of the address. + + This typically includes a street number and street name + but may also contain things like a unit number, a building + name, or anything else that might be used to + distinguish one address from another. +*/ +QString QGeoAddress::street() const +{ + return d->sStreet; +} + +/*! + Sets the street-level component of the address to \a street. + + This typically includes a street number and street name + but may also contain things like a unit number, a building + name, or anything else that might be used to + distinguish one address from another. +*/ +void QGeoAddress::setStreet(const QString &street) +{ + d->sStreet = street; +} + +/*! + Returns the postal code. +*/ +QString QGeoAddress::postalCode() const +{ + return d->sPostalCode; +} + +/*! + Sets the \a postalCode. +*/ +void QGeoAddress::setPostalCode(const QString &postalCode) +{ + d->sPostalCode = postalCode; +} + +/*! + Returns whether this address is empty. An address is considered empty + if \e all of its fields are empty. +*/ +bool QGeoAddress::isEmpty() const +{ + return d->sCountry.isEmpty() && + d->sCountryCode.isEmpty() && + d->sState.isEmpty() && + d->sCounty.isEmpty() && + d->sCity.isEmpty() && + d->sDistrict.isEmpty() && + d->sStreet.isEmpty() && + d->sPostalCode.isEmpty() && + d->sText.isEmpty(); + +} + +/*! + Clears all of the address' data fields. +*/ +void QGeoAddress::clear() +{ + d->sCountry.clear(); + d->sCountryCode.clear(); + d->sState.clear(); + d->sCounty.clear(); + d->sCity.clear(); + d->sDistrict.clear(); + d->sStreet.clear(); + d->sPostalCode.clear(); + d->sText.clear(); +} + +/*! + Returns true if QGeoAddress::text() is automatically generated from address elements, + otherwise returns false if text() has been explicitly assigned. + + \sa text(), setText() +*/ +bool QGeoAddress::isTextGenerated() const +{ + return d->sText.isEmpty(); +} + +QT_END_NAMESPACE + diff --git a/src/positioning/qgeoaddress.h b/src/positioning/qgeoaddress.h new file mode 100644 index 0000000..6231274 --- /dev/null +++ b/src/positioning/qgeoaddress.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOADDRESS_H +#define QGEOADDRESS_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QString; +class QGeoAddressPrivate; +class Q_POSITIONING_EXPORT QGeoAddress +{ +public: + QGeoAddress(); + QGeoAddress(const QGeoAddress &other); + ~QGeoAddress(); + + QGeoAddress &operator=(const QGeoAddress &other); + bool operator==(const QGeoAddress &other) const; + bool operator!=(const QGeoAddress &other) const { + return !(other == *this); + } + + QString text() const; + void setText(const QString &text); + + QString country() const; + void setCountry(const QString &country); + + QString countryCode() const; + void setCountryCode(const QString &countryCode); + + QString state() const; + void setState(const QString &state); + + QString county() const; + void setCounty(const QString &county); + + QString city() const; + void setCity(const QString &city); + + QString district() const; + void setDistrict(const QString &district); + + QString postalCode() const; + void setPostalCode(const QString &postalCode); + + QString street() const; + void setStreet(const QString &street); + + bool isEmpty() const; + void clear(); + + bool isTextGenerated() const; + +private: + QSharedDataPointer d; +}; + +Q_DECLARE_TYPEINFO(QGeoAddress, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoAddress) + +#endif diff --git a/src/positioning/qgeoaddress_p.h b/src/positioning/qgeoaddress_p.h new file mode 100644 index 0000000..ca0897e --- /dev/null +++ b/src/positioning/qgeoaddress_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCATION_GEOADDRESS_P_H +#define QLOCATION_GEOADDRESS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoAddressPrivate : public QSharedData +{ +public: + QGeoAddressPrivate(); + QGeoAddressPrivate(const QGeoAddressPrivate &other); + ~QGeoAddressPrivate(); + + QString sCountry; //!< country field + QString sCountryCode; //!< country code field + QString sState; //!< state field + QString sCounty; //!< county field + QString sCity; //!< city field + QString sDistrict; //!< district field + QString sStreet; //!< street name field + QString sPostalCode; //!< postal code field + QString sText; + bool m_autoGeneratedText; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeoareamonitorinfo.cpp b/src/positioning/qgeoareamonitorinfo.cpp new file mode 100644 index 0000000..5d39dee --- /dev/null +++ b/src/positioning/qgeoareamonitorinfo.cpp @@ -0,0 +1,379 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +#ifndef QT_NO_DEBUG_STREAM +#include +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoAreaMonitorInfo + \inmodule QtPositioning + \since 5.2 + \ingroup QtPositioning-positioning + + \brief The QGeoAreaMonitorInfo class describes the parameters of an area or region + to be monitored for proximity. + + The purpose of area monitoring is to inform a user when he/she comes close to an area of + interest. In general such an area is described by a \l QGeoCircle. The circle's center + represents the place of interest and the area around it identifies the geographical region + within which notifications are sent. + + A QGeoAreaMonitorInfo object is valid if it has a non-empty name and a valid \l area(). + Such objects must be registered with a \l QGeoAreaMonitorSource to start and stop the + monitoring process. Note that extensive monitoring can be very resource consuming + because the positioning engine must remain active and has to match the current position + with each QGeoAreaMonitorInfo instance. + + To further reduce the burden on the system there are optional attributes which can + set. Each monitored area can have an expiry date which automatically removes the + to-be-monitored area from the monitoring source once the expiry date has been reached. + Another option is to adjust the persistence of a monitored area. A QGeoAreaMonitorInfo + that \l isPersistent() will remain active beyond + the current applications lifetime. If an area is entered while the monitoring + application is not running the application will be started. Note that this feature is + not available on all platforms. Its availability can be checked via + \l QGeoAreaMonitorSource::supportedAreaMonitorFeatures(). + + \sa QGeoAreaMonitorSource + + */ + +class QGeoAreaMonitorInfoPrivate : public QSharedData +{ +public: + QGeoAreaMonitorInfoPrivate() : QSharedData(), persistent(false) {} + QGeoAreaMonitorInfoPrivate(const QGeoAreaMonitorInfoPrivate &other) + : QSharedData(other) + { + uid = other.uid; + name = other.name; + shape = other.shape; + persistent = other.persistent; + notificationParameters = other.notificationParameters; + expiry = other.expiry; + } + ~QGeoAreaMonitorInfoPrivate() {} + + QUuid uid; + QString name; + QGeoShape shape; + bool persistent; + QVariantMap notificationParameters; + QDateTime expiry; +}; + +/*! + Constructs a QGeoAreaMonitorInfo object with the specified \a name. + + \sa name() + */ +QGeoAreaMonitorInfo::QGeoAreaMonitorInfo(const QString &name) +{ + d = new QGeoAreaMonitorInfoPrivate; + d->name = name; + d->uid = QUuid::createUuid(); +} + +/*! + Constructs a QGeoAreaMonitorInfo object as a copy of \a other. + */ +QGeoAreaMonitorInfo::QGeoAreaMonitorInfo(const QGeoAreaMonitorInfo &other) + : d(other.d) +{ +} + +/*! + Destructor + */ +QGeoAreaMonitorInfo::~QGeoAreaMonitorInfo() +{ +} + +/*! + Assigns \a other to this QGeoAreaMonitorInfo object and returns a reference + to this QGeoAreaMonitorInfo object. + */ +QGeoAreaMonitorInfo &QGeoAreaMonitorInfo::operator=(const QGeoAreaMonitorInfo &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true if all of this object's values are the same as those of + \a other. +*/ +bool QGeoAreaMonitorInfo::operator==(const QGeoAreaMonitorInfo &other) const +{ + return (d->name == other.d->name && + d->uid == other.d->uid && + d->shape == other.d->shape && + d->persistent == other.d->persistent && + d->expiry == other.d->expiry && + d->notificationParameters == other.d->notificationParameters); +} + +/*! + Returns true if any of this object's values are not the same as those of + \a other. +*/ +bool QGeoAreaMonitorInfo::operator!=(const QGeoAreaMonitorInfo &other) const +{ + return !QGeoAreaMonitorInfo::operator ==(other); +} + +/*! + Returns the name of the QGeoAreaMonitorInfo object. The name should be used to + for user-visibility purposes. + */ +QString QGeoAreaMonitorInfo::name() const +{ + return d->name; +} + +/*! + Sets the user visibile \a name. + */ +void QGeoAreaMonitorInfo::setName(const QString &name) +{ + if (d->name != name) + d->name = name; +} + +/*! + Returns the identifier of the QGeoAreaMonitorInfo object. + The identifier is automatically generated upon construction of a new + QGeoAreaMonitorInfo object. +*/ + +QString QGeoAreaMonitorInfo::identifier() const +{ + return d->uid.toString(); +} + +/*! + Returns true, if the monitor is valid. A valid QGeoAreaMonitorInfo has a non-empty name() + and the monitored area is not \l {QGeoShape::isEmpty()}{empty()}. + Otherwise this function returns false. + */ +bool QGeoAreaMonitorInfo::isValid() const +{ + return (!d->name.isEmpty() && !d->shape.isEmpty()); +} + +/*! + Returns the boundaries of the to-be-monitored area. This area must not be empty. + + \sa setArea() + */ +QGeoShape QGeoAreaMonitorInfo::area() const +{ + return d->shape; +} + +/*! + Sets the to-be-monitored area to \a newShape. + + \sa area() + */ +void QGeoAreaMonitorInfo::setArea(const QGeoShape &newShape) +{ + d->shape = newShape; +} + +/*! + Returns the expiry date. + + After an active QGeoAreaMonitorInfo has expired the region is no longer monitored + and the QGeoAreaMonitorInfo object is removed from the list of + \l {QGeoAreaMonitorSource::activeMonitors()}{active monitors}. + + If the expiry \l QDateTime is invalid the QGeoAreaMonitorInfo object is treated as not having + an expiry date. This implies an indefinite monitoring period if the object is persistent or + until the current application closes if the object is non-persistent. + + \sa QGeoAreaMonitorSource::activeMonitors() + */ +QDateTime QGeoAreaMonitorInfo::expiration() const +{ + return d->expiry; +} + +/*! + Sets the expiry date and time to \a expiry. + */ +void QGeoAreaMonitorInfo::setExpiration(const QDateTime &expiry) +{ + d->expiry = expiry; +} + +/*! + Returns true if the QGeoAreaMonitorInfo is persistent. + The default value for this property is false. + + A non-persistent QGeoAreaMonitorInfo will be removed by the system once + the application owning the monitor object stops. Persistent objects remain + active and can be retrieved once the application restarts. + + If the system triggers an event associated to a persistent QGeoAreaMonitorInfo + the relevant application will be re-started and the appropriate signal emitted. + + \sa setPersistent() + */ +bool QGeoAreaMonitorInfo::isPersistent() const +{ + return d->persistent; +} + +/*! + Sets the QGeoAreaMonitorInfo objects persistence to \a isPersistent. + + Note that setting this flag does not imply that QGeoAreaMonitorInfoSource supports persistent + monitoring. \l QGeoAreaMonitorSource::supportedAreaMonitorFeatures() can be used to + check for this feature's availability. + + \sa isPersistent() + */ +void QGeoAreaMonitorInfo::setPersistent(bool isPersistent) +{ + d->persistent = isPersistent; +} + + +/*! + Returns the set of platform specific paraemters used by this QGeoAreaMonitorInfo. + + \sa setNotificationParameters() + */ +QVariantMap QGeoAreaMonitorInfo::notificationParameters() const +{ + return d->notificationParameters; +} + +/*! + Sets the set of platform specific \a parameters used by QGeoAreaMonitorInfo. + + \sa notificationParameters() + */ +void QGeoAreaMonitorInfo::setNotificationParameters(const QVariantMap ¶meters) +{ + d->notificationParameters = parameters; +} + +#ifndef QT_NO_DATASTREAM + +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QGeoAreaMonitorInfo &monitor) + \relates QGeoAreaMonitorInfo + + Writes the given \a monitor to the specified \a stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &ds, const QGeoAreaMonitorInfo &monitor) +{ + ds << monitor.name() << monitor.d->uid << monitor.area() + << monitor.isPersistent() << monitor.notificationParameters() << monitor.expiration(); + return ds; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QGeoAreaMonitorInfo &monitor) + \relates QGeoAreaMonitorInfo + + Reads a area monitoring data from the specified \a stream into the given + \a monitor. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &ds, QGeoAreaMonitorInfo &monitor) +{ + QString s; + ds >> s; + monitor = QGeoAreaMonitorInfo(s); + + QUuid id; + ds >> id; + monitor.d->uid = id; + + QGeoShape shape; + ds >> shape; + monitor.setArea(shape); + + bool persistent; + ds >> persistent; + monitor.setPersistent(persistent); + + QVariantMap map; + ds >> map; + monitor.setNotificationParameters(map); + + QDateTime dt; + ds >> dt; + monitor.setExpiration(dt); + + return ds; +} + +#endif + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QGeoAreaMonitorInfo &monitor) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QGeoAreaMonitorInfo(\"" << qPrintable(monitor.name()) + << "\", " << monitor.area() + << ", persistent: " << monitor.isPersistent() + << ", expiry: " << monitor.expiration() << ")"; + return dbg; +} + +#endif + +QT_END_NAMESPACE diff --git a/src/positioning/qgeoareamonitorinfo.h b/src/positioning/qgeoareamonitorinfo.h new file mode 100644 index 0000000..ae65d2c --- /dev/null +++ b/src/positioning/qgeoareamonitorinfo.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOAREAMONITORINFO_H +#define QGEOAREAMONITORINFO_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDataStream; +class QGeoAreaMonitorInfo; + +#ifndef QT_NO_DATASTREAM +Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &, const QGeoAreaMonitorInfo &); +Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &, QGeoAreaMonitorInfo &); +#endif + +class QGeoAreaMonitorInfoPrivate; +class Q_POSITIONING_EXPORT QGeoAreaMonitorInfo +{ +public: + explicit QGeoAreaMonitorInfo(const QString &name = QString()); + QGeoAreaMonitorInfo(const QGeoAreaMonitorInfo &other); + ~QGeoAreaMonitorInfo(); + + QGeoAreaMonitorInfo &operator=(const QGeoAreaMonitorInfo &other); + + bool operator==(const QGeoAreaMonitorInfo &other) const; + bool operator!=(const QGeoAreaMonitorInfo &other) const; + + QString name() const; + void setName(const QString &name); + + QString identifier() const; + bool isValid() const; + + QGeoShape area() const; + void setArea(const QGeoShape &newShape); + + QDateTime expiration() const; + void setExpiration(const QDateTime &expiry); + + bool isPersistent() const; + void setPersistent(bool isPersistent); + + QVariantMap notificationParameters() const; + void setNotificationParameters(const QVariantMap ¶meters); +private: + QSharedDataPointer d; + +#ifndef QT_NO_DATASTREAM + friend Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &, const QGeoAreaMonitorInfo &); + friend Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &, QGeoAreaMonitorInfo &); +#endif +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_POSITIONING_EXPORT QDebug operator<<(QDebug, const QGeoAreaMonitorInfo &); +#endif + +QT_END_NAMESPACE + +#endif // QGEOAREAMONITORINFO_H diff --git a/src/positioning/qgeoareamonitorsource.cpp b/src/positioning/qgeoareamonitorsource.cpp new file mode 100644 index 0000000..1db9c74 --- /dev/null +++ b/src/positioning/qgeoareamonitorsource.cpp @@ -0,0 +1,389 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qgeopositioninfosourcefactory.h" +#include "qgeopositioninfosource_p.h" + +/*! + \class QGeoAreaMonitorSource + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QGeoAreaMonitorSource class enables the detection of proximity + changes for a specified set of coordinates. + + A QGeoAreaMonitorSource emits signals when the current position is in + range, or has moved out of range, of a specified area. + Each area is specified by a \l QGeoAreaMonitorInfo object. + For example: + + \snippet cpp/cppqml.cpp BigBen + + \c QGeoAreaMonitorSource follows a singleton pattern. Each instance of + the class with the same \l sourceName() shares the same area monitoring backend. + If a new \l QGeoAreaMonitorInfo object is added via \l startMonitoring() + or \l requestUpdate() it can be retrieved by another instance of this class + (provided that they are sourced from the same area monitor provider plug-in). + The same singleton pattern applies to the \l QGeoPositionInfoSource instance + used by this class. The following code snippet emphasizes the behavior: + + \code + QGeoAreaMonitorSource *s1 = QGeoAreaMonitorSource::createSource("blah", this); + QGeoAreaMonitorSource *s2 = QGeoAreaMonitorSource::createSource("blah", this); + QVERIFY(s1->positionInfoSource() == s2->positionInfoSource); + \endcode +*/ + +QT_BEGIN_NAMESPACE + + + +class QGeoAreaMonitorSourcePrivate +{ +public: + QGeoPositionInfoSource *source; + QString providerName; +}; + +/*! + \enum QGeoAreaMonitorSource::Error + Defines the types of positioning methods. + + The Error enumeration represents the errors which can occur. + + \value AccessError The connection setup to the remote area monitoring backend failed because the + application lacked the required privileges. + \value InsufficientPositionInfo The area monitoring source could not retrieve a location fix or + the accuracy of the fix is not high enough to provide an effective area monitoring. + \value NoError No error has occurred. + \value UnknownSourceError An unidentified error occurred. +*/ + +/*! + \enum QGeoAreaMonitorSource::AreaMonitorFeature + Defines the types of area monitoring capabilities. + + \value PersistentAreaMonitorFeature QGeoAreaMonitorInfo instances can be made persistent. + A persistent monitor continues to be active even when the application managing the monitor is + not running. + \value AnyAreaMonitorFeature Matches all possible area monitoring features. +*/ + +/*! + \fn virtual AreaMonitoringFeatures QGeoAreaMonitorSource::supportedAreaMonitorFeatures() const = 0; + + Returns the area monitoring features available to this source. +*/ + +/*! + \fn virtual QGeoAreaMonitorSource::Error QGeoAreaMonitorSource::error() const + + Returns the type of error that last occurred. +*/ + +/*! + Creates a monitor with the given \a parent. +*/ +QGeoAreaMonitorSource::QGeoAreaMonitorSource(QObject *parent) + : QObject(parent), + d(new QGeoAreaMonitorSourcePrivate) +{ + d->source = 0; +} + +/*! + Destroys the monitor. +*/ +QGeoAreaMonitorSource::~QGeoAreaMonitorSource() +{ + delete d; +} + +/*! + Creates and returns a monitor with the given \a parent that + monitors areas using resources on the underlying system. + + Returns 0 if the system has no support for position monitoring. +*/ +QGeoAreaMonitorSource *QGeoAreaMonitorSource::createDefaultSource(QObject *parent) +{ + QList plugins = QGeoPositionInfoSourcePrivate::pluginsSorted(); + foreach (const QJsonObject &obj, plugins) { + if (obj.value(QStringLiteral("Monitor")).isBool() + && obj.value(QStringLiteral("Monitor")).toBool()) + { + QGeoPositionInfoSourcePrivate d; + d.metaData = obj; + d.loadPlugin(); + QGeoAreaMonitorSource *s = 0; + if (d.factory) + s = d.factory->areaMonitor(parent); + if (s) + s->d->providerName = d.metaData.value(QStringLiteral("Provider")).toString(); + return s; + } + } + + return 0; +} + +/*! + Creates and returns a monitor with the given \a parent, + by loading the plugin named \a sourceName. + + Returns 0 if the plugin cannot be found. +*/ +QGeoAreaMonitorSource *QGeoAreaMonitorSource::createSource(const QString &sourceName, QObject *parent) +{ + QHash plugins = QGeoPositionInfoSourcePrivate::plugins(); + if (plugins.contains(sourceName)) { + QGeoPositionInfoSourcePrivate d; + d.metaData = plugins.value(sourceName); + d.loadPlugin(); + QGeoAreaMonitorSource *s = 0; + if (d.factory) + s = d.factory->areaMonitor(parent); + if (s) + s->d->providerName = d.metaData.value(QStringLiteral("Provider")).toString(); + return s; + } + + return 0; +} + +/*! + Returns a list of available monitor plugins, including the default system + backend if one is available. +*/ +QStringList QGeoAreaMonitorSource::availableSources() +{ + QStringList plugins; + const QHash meta = QGeoPositionInfoSourcePrivate::plugins(); + for (auto it = meta.cbegin(), end = meta.cend(); it != end; ++it) { + if (it.value().value(QStringLiteral("Monitor")).isBool() + && it.value().value(QStringLiteral("Monitor")).toBool()) { + plugins << it.key(); + } + } + + return plugins; +} + +/*! + Returns the unique name of the area monitor source implementation in use. + + This is the same name that can be passed to createSource() in order to + create a new instance of a particular area monitor source implementation. +*/ +QString QGeoAreaMonitorSource::sourceName() const +{ + return d->providerName; +} + +/*! + Returns the current QGeoPositionInfoSource used by this QGeoAreaMonitorSource + object. The function will return \l QGeoPositionInfoSource::createDefaultSource() + if no other object has been set. + + The function returns 0 if not even a default QGeoPositionInfoSource exists. + + Any usage of the returned \l QGeoPositionInfoSource instance should account + for the fact that it may reside in a different thread. + + \sa QGeoPositionInfoSource, setPositionInfoSource() +*/ +QGeoPositionInfoSource* QGeoAreaMonitorSource::positionInfoSource() const +{ + return d->source; +} + +/*! + Sets the new \l QGeoPositionInfoSource to be used by this QGeoAreaMonitorSource object. + The area monitoring backend becomes the new QObject parent for \a newSource. + The previous \l QGeoPositionInfoSource object will be deleted. All QGeoAreaMonitorSource + instances based on the same \l sourceName() share the same QGeoPositionInfoSource + instance. + + This may be useful when it is desirable to manipulate the positioning system + used by the area monitoring engine. + + Note that ownership must be taken care of by subclasses of QGeoAreaMonitorSource. + Due to the singleton pattern behind this class \a newSource may be moved to a + new thread. + + \sa positionInfoSource() + */ +void QGeoAreaMonitorSource::setPositionInfoSource(QGeoPositionInfoSource *newSource) +{ + d->source = newSource; +} + + +/*! + \fn virtual bool QGeoAreaMonitorSource::startMonitoring(const QGeoAreaMonitorInfo &monitor) + + Returns \c true if the monitoring of \a monitor could be successfully started; otherwise + returns false. A reason for not being able to start monitoring could be the unavailability + of an appropriate default position info source while no alternative QGeoPositionInfoSource + has been set via \l setPositionInfoSource(). + + If \a monitor is already active the existing monitor object will be replaced by the new \a monitor reference. + The identification of QGeoAreaMonitorInfo instances happens via \l QGeoAreaMonitorInfo::identifier(). + Therefore this function can also be used to update active monitors. + + If \a monitor has an expiry date that has been passed this function returns false. Calling + this function for an already via \l requestUpdate() registered single shot monitor + switches the monitor to a permanent monitoring mode. + + Requesting persistent monitoring on a QGeoAreaMonitorSource instance fails if the area monitoring + backend doesn't support \l QGeoAreaMonitorSource::PersistentAreaMonitorFeature. + + \sa stopMonitoring() +*/ + +/*! + \fn virtual bool QGeoAreaMonitorSource::requestUpdate(const QGeoAreaMonitorInfo &monitor, const char *signal) + + Enables single shot area monitoring. Area monitoring for \a monitor will be performed + until this QGeoAreaMonitorSource instance emits \a signal for the first time. Once + the signal was emitted, \a monitor is automatically removed from the list of \l activeMonitors(). + If \a monitor is invalid or has an expiry date that has been passed this function returns false. + + \code + QGeoAreaMonitor singleShotMonitor; + QGeoAreaMonitorSource * source = QGeoAreaMonitorSource::createDefaultSource(this); + //... + bool ret = source->requestUpdate(singleShotMonitor, + SIGNAL(areaExited(QGeoAreaMonitor,QGeoPositionInfo))); + \endcode + + The above \c singleShotMonitor object will cease to send updates once the \l areaExited() signal + was emitted for the first time. Until this point in time any other signal may be emitted + zero or more times depending on the area context. + + It is not possible to simultanously request updates for more than one signal of the same monitor object. + The last call to this function determines the signal upon which the updates cease to continue. + At this stage only the \l areaEntered() and \l areaExited() signals can be used to + terminate the monitoring process. + + Requesting persistent monitoring on a QGeoAreaMonitorSource instance fails if the area monitoring + backend doesn't support \l QGeoAreaMonitorSource::PersistentAreaMonitorFeature. + + If \a monitor was already registered via \l startMonitoring() it is converted to a single + shot behavior. + + \sa startMonitoring(), stopMonitoring() + */ + +/*! + \fn virtual bool QGeoAreaMonitorSource::stopMonitoring(const QGeoAreaMonitorInfo &monitor) + + Returns true if \a monitor was successfully removed from the list of \l activeMonitors(); + otherwise returns false. This behavior is independent on whether \a monitor was registered + via \l startMonitoring() or \l requestUpdate(). +*/ + +/*! + \fn virtual QList QGeoAreaMonitorSource::activeMonitors() const + + Returns the list of all active monitors known to the QGeoAreaMonitorSource object. + + An active monitor was started via startMonitoring() the source object will emit + the required signals such as areaEntered() or areaExited(). Multiple \l QGeoAreaMonitorSource + instances within the same application share the same active monitor objects. + + Unless an active QGeoAreaMonitorInfo \l {QGeoAreaMonitorInfo::isPersistent()}{isPersistent()} an active QGeoAreaMonitorInfo + will be stopped once the current application terminates. +*/ + +/*! + \fn virtual QList QGeoAreaMonitorSource::activeMonitors(const QGeoShape &lookupArea) const + + Returns the list of all active monitors known to the QGeoAreaMonitorSource object whose + center lies within \a lookupArea. If \a lookupArea is empty the returned list will be empty. + + An active monitor was started via startMonitoring() and the source object will emit + the required signals such as areaEntered() or areaExited(). Multiple QGeoAreaMonitorSource + instances within the same application share the same monitor objects. + + Unless an active QGeoAreaMonitorInfo \l {QGeoAreaMonitorInfo::isPersistent()}{isPersistent()} an active QGeoAreaMonitorInfo + will be stopped once the current application terminates. + + \sa QGeoShape +*/ + + +/*! + \fn void QGeoAreaMonitorSource::monitorExpired(const QGeoAreaMonitorInfo &monitor) + + Emitted when \a monitor has expired. An expired area monitor is automatically + removed from the list of \l activeMonitors(). + + \sa activeMonitors() +*/ + +/*! + \fn void QGeoAreaMonitorSource::areaEntered(const QGeoAreaMonitorInfo &monitor, const QGeoPositionInfo &update) + + Emitted when the current position has moved from a position outside of the active \a monitor + to a position within the monitored area. + + The \a update holds the new position. +*/ + +/*! + \fn void QGeoAreaMonitorSource::areaExited(const QGeoAreaMonitorInfo &monitor, const QGeoPositionInfo &update) + + Emitted when the current position has moved from a position within the active \a monitor + to a position outside the monitored area. + + The \a update holds the new position. +*/ + +/*! + \fn void QGeoAreaMonitorSource::error(QGeoAreaMonitorSource::Error areaMonitoringError) + + This signal is emitted after an error occurred. The \a areaMonitoringError + parameter describes the type of error that occurred. + +*/ + +QT_END_NAMESPACE diff --git a/src/positioning/qgeoareamonitorsource.h b/src/positioning/qgeoareamonitorsource.h new file mode 100644 index 0000000..519f313 --- /dev/null +++ b/src/positioning/qgeoareamonitorsource.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOAREAMONITORSOURCE_H +#define QGEOAREAMONITORSOURCE_H + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoPositionInfo; +class QGeoAreaMonitorSourcePrivate; +class Q_POSITIONING_EXPORT QGeoAreaMonitorSource : public QObject +{ + Q_OBJECT + +public: + enum Error { + AccessError = 0, + InsufficientPositionInfo = 1, + UnknownSourceError = 2, + NoError = 3 + }; + Q_ENUMS(Error) + + enum AreaMonitorFeature { + PersistentAreaMonitorFeature = 0x00000001, + AnyAreaMonitorFeature = 0xffffffff + }; + Q_DECLARE_FLAGS(AreaMonitorFeatures, AreaMonitorFeature) + + explicit QGeoAreaMonitorSource(QObject *parent); + virtual ~QGeoAreaMonitorSource(); + + static QGeoAreaMonitorSource *createDefaultSource(QObject *parent); + static QGeoAreaMonitorSource *createSource(const QString& sourceName, QObject *parent); + static QStringList availableSources(); + + virtual void setPositionInfoSource(QGeoPositionInfoSource *source); + virtual QGeoPositionInfoSource* positionInfoSource() const; + + QString sourceName() const; + + virtual Error error() const = 0; + virtual AreaMonitorFeatures supportedAreaMonitorFeatures() const = 0; + + virtual bool startMonitoring(const QGeoAreaMonitorInfo &monitor) = 0; + virtual bool stopMonitoring(const QGeoAreaMonitorInfo &monitor) = 0; + virtual bool requestUpdate(const QGeoAreaMonitorInfo &monitor, const char *signal) = 0; + + virtual QList activeMonitors() const = 0; + virtual QList activeMonitors(const QGeoShape &lookupArea) const = 0; + +Q_SIGNALS: + void areaEntered(const QGeoAreaMonitorInfo &monitor, const QGeoPositionInfo &update); + void areaExited(const QGeoAreaMonitorInfo &monitor, const QGeoPositionInfo &update); + void monitorExpired(const QGeoAreaMonitorInfo &monitor); + void error(QGeoAreaMonitorSource::Error error); + +private: + Q_DISABLE_COPY(QGeoAreaMonitorSource) + QGeoAreaMonitorSourcePrivate *d; +}; + + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeocircle.cpp b/src/positioning/qgeocircle.cpp new file mode 100644 index 0000000..3f2707a --- /dev/null +++ b/src/positioning/qgeocircle.cpp @@ -0,0 +1,385 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeocircle.h" +#include "qgeocircle_p.h" + +#include "qgeocoordinate.h" +#include "qnumeric.h" + +#include "qdoublevector2d_p.h" +#include "qdoublevector3d_p.h" +QT_BEGIN_NAMESPACE + +/*! + \class QGeoCircle + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QGeoCircle class defines a circular geographic area. + + The circle is defined in terms of a QGeoCoordinate which specifies the + center of the circle and a qreal which specifies the radius of the circle + in meters. + + The circle is considered invalid if the center coordinate is invalid + or if the radius is less than zero. + + This class is a \l Q_GADGET since Qt 5.5. It can be + \l{Cpp_value_integration_positioning}{directly used from C++ and QML}. +*/ + +/*! + \property QGeoCircle::center + \brief This property holds the center coordinate for the geo circle. + + The circle is considered invalid if this property contains an invalid + coordinate. + + A default constructed QGeoCircle uses an invalid \l QGeoCoordinate + as center. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoCircle::radius + \brief This property holds the circle radius in meters. + + The circle is considered invalid if this property is negative. + + By default, the radius is initialized with \c -1. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +inline QGeoCirclePrivate *QGeoCircle::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QGeoCirclePrivate *QGeoCircle::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +struct CircleVariantConversions +{ + CircleVariantConversions() + { + QMetaType::registerConverter(); + QMetaType::registerConverter(); + } +}; + +Q_GLOBAL_STATIC(CircleVariantConversions, initCircleConversions) + +/*! + Constructs a new, invalid geo circle. +*/ +QGeoCircle::QGeoCircle() +: QGeoShape(new QGeoCirclePrivate) +{ + initCircleConversions(); +} + +/*! + Constructs a new geo circle centered at \a center and with a radius of \a radius meters. +*/ +QGeoCircle::QGeoCircle(const QGeoCoordinate ¢er, qreal radius) +{ + initCircleConversions(); + d_ptr = new QGeoCirclePrivate(center, radius); +} + +/*! + Constructs a new geo circle from the contents of \a other. +*/ +QGeoCircle::QGeoCircle(const QGeoCircle &other) +: QGeoShape(other) +{ + initCircleConversions(); +} + +/*! + Constructs a new geo circle from the contents of \a other. +*/ +QGeoCircle::QGeoCircle(const QGeoShape &other) +: QGeoShape(other) +{ + initCircleConversions(); + if (type() != QGeoShape::CircleType) + d_ptr = new QGeoCirclePrivate; +} + +/*! + Destroys this geo circle. +*/ +QGeoCircle::~QGeoCircle() {} + +/*! + Assigns \a other to this geo circle and returns a reference to this geo circle. +*/ +QGeoCircle &QGeoCircle::operator=(const QGeoCircle &other) +{ + QGeoShape::operator=(other); + return *this; +} + +/*! + Returns whether this geo circle is equal to \a other. +*/ +bool QGeoCircle::operator==(const QGeoCircle &other) const +{ + Q_D(const QGeoCircle); + + return *d == *other.d_func(); +} + +/*! + Returns whether this geo circle is not equal to \a other. +*/ +bool QGeoCircle::operator!=(const QGeoCircle &other) const +{ + Q_D(const QGeoCircle); + + return !(*d == *other.d_func()); +} + +bool QGeoCirclePrivate::isValid() const +{ + return m_center.isValid() && !qIsNaN(radius) && radius >= -1e-7; +} + +bool QGeoCirclePrivate::isEmpty() const +{ + return !isValid() || radius <= 1e-7; +} + +/*! + Sets the center coordinate of this geo circle to \a center. +*/ +void QGeoCircle::setCenter(const QGeoCoordinate ¢er) +{ + Q_D(QGeoCircle); + + d->m_center = center; +} + +/*! + Returns the center coordinate of this geo circle. Equivalent to QGeoShape::center(). +*/ +QGeoCoordinate QGeoCircle::center() const +{ + Q_D(const QGeoCircle); + + return d->center(); +} + +/*! + Sets the radius in meters of this geo circle to \a radius. +*/ +void QGeoCircle::setRadius(qreal radius) +{ + Q_D(QGeoCircle); + + d->radius = radius; +} + +/*! + Returns the radius in meters of this geo circle. +*/ +qreal QGeoCircle::radius() const +{ + Q_D(const QGeoCircle); + + return d->radius; +} + +bool QGeoCirclePrivate::contains(const QGeoCoordinate &coordinate) const +{ + if (!isValid() || !coordinate.isValid()) + return false; + + // see QTBUG-41447 for details + qreal distance = m_center.distanceTo(coordinate); + if (qFuzzyCompare(distance, radius) || distance <= radius) + return true; + + return false; +} + +QGeoCoordinate QGeoCirclePrivate::center() const +{ + return m_center; +} + +/*! + Extends the circle to include \a coordinate +*/ +void QGeoCirclePrivate::extendShape(const QGeoCoordinate &coordinate) +{ + if (!isValid() || !coordinate.isValid() || contains(coordinate)) + return; + + radius = m_center.distanceTo(coordinate); +} + +/*! + Translates this geo circle by \a degreesLatitude northwards and \a degreesLongitude eastwards. + + Negative values of \a degreesLatitude and \a degreesLongitude correspond to + southward and westward translation respectively. +*/ +void QGeoCircle::translate(double degreesLatitude, double degreesLongitude) +{ + // TODO handle dlat, dlon larger than 360 degrees + + Q_D(QGeoCircle); + + double lat = d->m_center.latitude(); + double lon = d->m_center.longitude(); + + lat += degreesLatitude; + lon += degreesLongitude; + + if (lon < -180.0) + lon += 360.0; + if (lon > 180.0) + lon -= 360.0; + + if (lat > 90.0) { + lat = 180.0 - lat; + if (lon < 0.0) + lon = 180.0; + else + lon -= 180; + } + + if (lat < -90.0) { + lat = 180.0 + lat; + if (lon < 0.0) + lon = 180.0; + else + lon -= 180; + } + + d->m_center = QGeoCoordinate(lat, lon); +} + +/*! + Returns a copy of this geo circle translated by \a degreesLatitude northwards and + \a degreesLongitude eastwards. + + Negative values of \a degreesLatitude and \a degreesLongitude correspond to + southward and westward translation respectively. + + \sa translate() +*/ +QGeoCircle QGeoCircle::translated(double degreesLatitude, double degreesLongitude) const +{ + QGeoCircle result(*this); + result.translate(degreesLatitude, degreesLongitude); + return result; +} + +/*! + Returns the geo circle properties as a string. + + \since 5.5 +*/ + +QString QGeoCircle::toString() const +{ + if (type() != QGeoShape::CircleType) { + qWarning("Not a circle"); + return QStringLiteral("QGeoCircle(not a circle)"); + } + + return QStringLiteral("QGeoCircle({%1, %2}, %3)") + .arg(center().latitude()) + .arg(center().longitude()) + .arg(radius()); +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoCirclePrivate::QGeoCirclePrivate() +: QGeoShapePrivate(QGeoShape::CircleType), radius(-1.0) +{ +} + +QGeoCirclePrivate::QGeoCirclePrivate(const QGeoCoordinate ¢er, qreal radius) +: QGeoShapePrivate(QGeoShape::CircleType), m_center(center), radius(radius) +{ +} + +QGeoCirclePrivate::QGeoCirclePrivate(const QGeoCirclePrivate &other) +: QGeoShapePrivate(QGeoShape::CircleType), m_center(other.m_center), + radius(other.radius) +{ +} + +QGeoCirclePrivate::~QGeoCirclePrivate() {} + +QGeoShapePrivate *QGeoCirclePrivate::clone() const +{ + return new QGeoCirclePrivate(*this); +} + +bool QGeoCirclePrivate::operator==(const QGeoShapePrivate &other) const +{ + if (!QGeoShapePrivate::operator==(other)) + return false; + + const QGeoCirclePrivate &otherCircle = static_cast(other); + + return radius == otherCircle.radius && m_center == otherCircle.m_center; +} + +QT_END_NAMESPACE + diff --git a/src/positioning/qgeocircle.h b/src/positioning/qgeocircle.h new file mode 100644 index 0000000..b4b6c45 --- /dev/null +++ b/src/positioning/qgeocircle.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCIRCLE_H +#define QGEOCIRCLE_H + +#include + +QT_BEGIN_NAMESPACE + +class QGeoCoordinate; +class QGeoCirclePrivate; + +class Q_POSITIONING_EXPORT QGeoCircle : public QGeoShape +{ + Q_GADGET + Q_PROPERTY(QGeoCoordinate center READ center WRITE setCenter) + Q_PROPERTY(qreal radius READ radius WRITE setRadius) + +public: + QGeoCircle(); + QGeoCircle(const QGeoCoordinate ¢er, qreal radius = -1.0); + QGeoCircle(const QGeoCircle &other); + QGeoCircle(const QGeoShape &other); + + ~QGeoCircle(); + + QGeoCircle &operator=(const QGeoCircle &other); + + using QGeoShape::operator==; + bool operator==(const QGeoCircle &other) const; + + using QGeoShape::operator!=; + bool operator!=(const QGeoCircle &other) const; + + void setCenter(const QGeoCoordinate ¢er); + QGeoCoordinate center() const; + + void setRadius(qreal radius); + qreal radius() const; + + void translate(double degreesLatitude, double degreesLongitude); + QGeoCircle translated(double degreesLatitude, double degreesLongitude) const; + + Q_INVOKABLE QString toString() const; + +private: + inline QGeoCirclePrivate *d_func(); + inline const QGeoCirclePrivate *d_func() const; +}; + +Q_DECLARE_TYPEINFO(QGeoCircle, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoCircle) + +#endif + diff --git a/src/positioning/qgeocircle_p.h b/src/positioning/qgeocircle_p.h new file mode 100644 index 0000000..311aba8 --- /dev/null +++ b/src/positioning/qgeocircle_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCIRCLE_P_H +#define QGEOCIRCLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeoshape_p.h" +#include "qgeocoordinate.h" + +QT_BEGIN_NAMESPACE + +class QGeoCirclePrivate : public QGeoShapePrivate +{ +public: + QGeoCirclePrivate(); + QGeoCirclePrivate(const QGeoCoordinate ¢er, qreal radius); + QGeoCirclePrivate(const QGeoCirclePrivate &other); + ~QGeoCirclePrivate(); + + bool isValid() const Q_DECL_OVERRIDE; + bool isEmpty() const Q_DECL_OVERRIDE; + bool contains(const QGeoCoordinate &coordinate) const Q_DECL_OVERRIDE; + + QGeoCoordinate center() const Q_DECL_OVERRIDE; + + void extendShape(const QGeoCoordinate &coordinate) Q_DECL_OVERRIDE; + + QGeoShapePrivate *clone() const Q_DECL_OVERRIDE; + + bool operator==(const QGeoShapePrivate &other) const Q_DECL_OVERRIDE; + + QGeoCoordinate m_center; + qreal radius; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeocoordinate.cpp b/src/positioning/qgeocoordinate.cpp new file mode 100644 index 0000000..0386e85 --- /dev/null +++ b/src/positioning/qgeocoordinate.cpp @@ -0,0 +1,783 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeocoordinate.h" +#include "qgeocoordinate_p.h" +#include "qlocationutils_p.h" + +#include +#include +#include +#include +#include + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +QT_BEGIN_NAMESPACE + +static const double qgeocoordinate_EARTH_MEAN_RADIUS = 6371.0072; + +inline static double qgeocoordinate_degToRad(double deg) +{ + return deg * M_PI / 180; +} +inline static double qgeocoordinate_radToDeg(double rad) +{ + return rad * 180 / M_PI; +} + + +QGeoCoordinatePrivate::QGeoCoordinatePrivate(): + lat(qQNaN()), + lng(qQNaN()), + alt(qQNaN()) +{} + +QGeoCoordinatePrivate::QGeoCoordinatePrivate(const QGeoCoordinatePrivate &other) + : QSharedData(other), + lat(other.lat), + lng(other.lng), + alt(other.alt) +{} + +QGeoCoordinatePrivate::~QGeoCoordinatePrivate() +{} + + +QGeoMercatorCoordinatePrivate::QGeoMercatorCoordinatePrivate(): + QGeoCoordinatePrivate(), + m_mercatorX(qQNaN()), + m_mercatorY(qQNaN()) +{} + +QGeoMercatorCoordinatePrivate::QGeoMercatorCoordinatePrivate(const QGeoMercatorCoordinatePrivate &other) + : QGeoCoordinatePrivate(other), + m_mercatorX(other.m_mercatorX), + m_mercatorY(other.m_mercatorY) +{} + +QGeoMercatorCoordinatePrivate::~QGeoMercatorCoordinatePrivate() +{} + +/*! + \class QGeoCoordinate + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QGeoCoordinate class defines a geographical position on the surface of the Earth. + + A QGeoCoordinate is defined by latitude, longitude, and optionally, altitude. + + Use type() to determine whether a coordinate is a 2D coordinate (has + latitude and longitude only) or 3D coordinate (has latitude, longitude + and altitude). Use distanceTo() and azimuthTo() to calculate the distance + and bearing between coordinates. + + The coordinate values should be specified using the WGS84 datum. For more information + on geographical terms see this article on \l {http://en.wikipedia.org/wiki/Geographic_coordinate_system}{coordinates} and + another on \l {http://en.wikipedia.org/wiki/Geodetic_system}{geodetic systems} + including WGS84. + + Azimuth in this context is equivalent to a compass bearing based on true north. + + This class is a \l Q_GADGET since Qt 5.5. It can be + \l{Cpp_value_integration_positioning}{directly used from C++ and QML}. +*/ + +/*! + \enum QGeoCoordinate::CoordinateType + Defines the types of a coordinate. + + \value InvalidCoordinate An invalid coordinate. A coordinate is invalid if its latitude or longitude values are invalid. + \value Coordinate2D A coordinate with valid latitude and longitude values. + \value Coordinate3D A coordinate with valid latitude and longitude values, and also an altitude value. +*/ + +/*! + \enum QGeoCoordinate::CoordinateFormat + Defines the possible formatting options for toString(). + + \value Degrees Returns a string representation of the coordinates in decimal degrees format. + \value DegreesWithHemisphere Returns a string representation of the coordinates in decimal degrees format, using 'N', 'S', 'E' or 'W' to indicate the hemispheres of the coordinates. + \value DegreesMinutes Returns a string representation of the coordinates in degrees-minutes format. + \value DegreesMinutesWithHemisphere Returns a string representation of the coordinates in degrees-minutes format, using 'N', 'S', 'E' or 'W' to indicate the hemispheres of the coordinates. + \value DegreesMinutesSeconds Returns a string representation of the coordinates in degrees-minutes-seconds format. + \value DegreesMinutesSecondsWithHemisphere Returns a string representation of the coordinates in degrees-minutes-seconds format, using 'N', 'S', 'E' or 'W' to indicate the hemispheres of the coordinates. + + \sa toString() +*/ + +/*! + \property QGeoCoordinate::latitude + \brief This property holds the latitude in decimal degrees. + + The property is undefined (\l {qQNaN()}) if the latitude has not been set. + A positive latitude indicates the Northern Hemisphere, and a negative + latitude indicates the Southern Hemisphere. When setting the latitude the + new value should be in the + \l {http://en.wikipedia.org/wiki/World_Geodetic_System}{WGS84} datum format. + + To be valid, the latitude must be between -90 to 90 inclusive. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoCoordinate::longitude + \brief This property holds the longitude in decimal degrees. + + The property is undefined (\l {qQNaN()}) if the longitude has not been set. + A positive longitude indicates the Eastern Hemisphere, and a negative + longitude indicates the Western Hemisphere. When setting the longitude the + new value should be in the + \l {http://en.wikipedia.org/wiki/World_Geodetic_System}{WGS84} datum format. + + To be valid, the longitude must be between -180 to 180 inclusive. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoCoordinate::altitude + \brief This property holds the altitude in meters above sea level. + + The property is undefined (\l {qQNaN()}) if the altitude has not been set. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoCoordinate::isValid + \brief This property holds the validity of this geo coordinate. + + The geo coordinate is valid if the \l [CPP]{longitude} and \l [CPP]{latitude} + properties have been set to valid values. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + Constructs a coordinate. The coordinate will be invalid until + setLatitude() and setLongitude() have been called. +*/ +QGeoCoordinate::QGeoCoordinate() + : d(new QGeoCoordinatePrivate) +{ +} + +/*! + Constructs a coordinate with the given \a latitude and \a longitude. + + If the latitude is not between -90 to 90 inclusive, or the longitude + is not between -180 to 180 inclusive, none of the values are set and + the type() will be QGeoCoordinate::InvalidCoordinate. + + \sa isValid() +*/ +QGeoCoordinate::QGeoCoordinate(double latitude, double longitude) + : d(new QGeoCoordinatePrivate) +{ + if (QLocationUtils::isValidLat(latitude) && QLocationUtils::isValidLong(longitude)) { + d->lat = latitude; + d->lng = longitude; + } +} + +/*! + Constructs a coordinate with the given \a latitude, \a longitude + and \a altitude. + + If the latitude is not between -90 to 90 inclusive, or the longitude + is not between -180 to 180 inclusive, none of the values are set and + the type() will be QGeoCoordinate::InvalidCoordinate. + + Note that \a altitude specifies the meters above sea level. + + \sa isValid() +*/ +QGeoCoordinate::QGeoCoordinate(double latitude, double longitude, double altitude) + : d(new QGeoCoordinatePrivate) +{ + if (QLocationUtils::isValidLat(latitude) && QLocationUtils::isValidLong(longitude)) { + d->lat = latitude; + d->lng = longitude; + d->alt = altitude; + } +} + +/*! + Constructs a coordinate from the contents of \a other. +*/ +QGeoCoordinate::QGeoCoordinate(const QGeoCoordinate &other) + : d(other.d) +{} + +/*! + Assigns \a other to this coordinate and returns a reference to this coordinate. +*/ +QGeoCoordinate &QGeoCoordinate::operator=(const QGeoCoordinate &other) +{ + if (this == &other) + return *this; + + d = other.d; + return (*this); +} + +/*! + Destroys the coordinate object. +*/ +QGeoCoordinate::~QGeoCoordinate() +{ +} + +/*! + Returns true if the latitude, longitude and altitude of this + coordinate are the same as those of \a other. + + The longitude will be ignored if the latitude is +/- 90 degrees. +*/ +bool QGeoCoordinate::operator==(const QGeoCoordinate &other) const +{ + bool latEqual = (qIsNaN(d->lat) && qIsNaN(other.d->lat)) + || qFuzzyCompare(d->lat, other.d->lat); + bool lngEqual = (qIsNaN(d->lng) && qIsNaN(other.d->lng)) + || qFuzzyCompare(d->lng, other.d->lng); + bool altEqual = (qIsNaN(d->alt) && qIsNaN(other.d->alt)) + || qFuzzyCompare(d->alt, other.d->alt); + + if (!qIsNaN(d->lat) && ((d->lat == 90.0) || (d->lat == -90.0))) + lngEqual = true; + + return (latEqual && lngEqual && altEqual); +} + +/*! + \fn bool QGeoCoordinate::operator!=(const QGeoCoordinate &other) const; + + Returns true if the latitude, longitude or altitude of this + coordinate are not the same as those of \a other. +*/ + +/*! + Returns true if the \l longitude and \l latitude are valid. +*/ +bool QGeoCoordinate::isValid() const +{ + CoordinateType t = type(); + return t == Coordinate2D || t == Coordinate3D; +} + +/*! + Returns the type of this coordinate. +*/ +QGeoCoordinate::CoordinateType QGeoCoordinate::type() const +{ + if (QLocationUtils::isValidLat(d->lat) + && QLocationUtils::isValidLong(d->lng)) { + if (qIsNaN(d->alt)) + return Coordinate2D; + return Coordinate3D; + } + return InvalidCoordinate; +} + + +/*! + Returns the latitude, in decimal degrees. The return value is undefined + if the latitude has not been set. + + A positive latitude indicates the Northern Hemisphere, and a negative + latitude indicates the Southern Hemisphere. + + \sa setLatitude(), type() +*/ +double QGeoCoordinate::latitude() const +{ + return d->lat; +} + +/*! + Sets the latitude (in decimal degrees) to \a latitude. The value should + be in the WGS84 datum. + + To be valid, the latitude must be between -90 to 90 inclusive. + + \sa latitude() +*/ +void QGeoCoordinate::setLatitude(double latitude) +{ + d->lat = latitude; +} + +/*! + Returns the longitude, in decimal degrees. The return value is undefined + if the longitude has not been set. + + A positive longitude indicates the Eastern Hemisphere, and a negative + longitude indicates the Western Hemisphere. + + \sa setLongitude(), type() +*/ +double QGeoCoordinate::longitude() const +{ + return d->lng; +} + +/*! + Sets the longitude (in decimal degrees) to \a longitude. The value should + be in the WGS84 datum. + + To be valid, the longitude must be between -180 to 180 inclusive. + + \sa longitude() +*/ +void QGeoCoordinate::setLongitude(double longitude) +{ + d->lng = longitude; +} + +/*! + Returns the altitude (meters above sea level). + + The return value is undefined if the altitude has not been set. + + \sa setAltitude(), type() +*/ +double QGeoCoordinate::altitude() const +{ + return d->alt; +} + +/*! + Sets the altitude (meters above sea level) to \a altitude. + + \sa altitude() +*/ +void QGeoCoordinate::setAltitude(double altitude) +{ + d->alt = altitude; +} + +/*! + Returns the distance (in meters) from this coordinate to the coordinate + specified by \a other. Altitude is not used in the calculation. + + This calculation returns the great-circle distance between the two + coordinates, with an assumption that the Earth is spherical for the + purpose of this calculation. + + Returns 0 if the type of this coordinate or the type of \a other is + QGeoCoordinate::InvalidCoordinate. +*/ +qreal QGeoCoordinate::distanceTo(const QGeoCoordinate &other) const +{ + if (type() == QGeoCoordinate::InvalidCoordinate + || other.type() == QGeoCoordinate::InvalidCoordinate) { + return 0; + } + + // Haversine formula + double dlat = qgeocoordinate_degToRad(other.d->lat - d->lat); + double dlon = qgeocoordinate_degToRad(other.d->lng - d->lng); + double haversine_dlat = sin(dlat / 2.0); + haversine_dlat *= haversine_dlat; + double haversine_dlon = sin(dlon / 2.0); + haversine_dlon *= haversine_dlon; + double y = haversine_dlat + + cos(qgeocoordinate_degToRad(d->lat)) + * cos(qgeocoordinate_degToRad(other.d->lat)) + * haversine_dlon; + double x = 2 * asin(sqrt(y)); + return qreal(x * qgeocoordinate_EARTH_MEAN_RADIUS * 1000); +} + +/*! + Returns the azimuth (or bearing) in degrees from this coordinate to the + coordinate specified by \a other. Altitude is not used in the calculation. + + The bearing returned is the bearing from the origin to \a other along the + great-circle between the two coordinates. There is an assumption that the + Earth is spherical for the purpose of this calculation. + + Returns 0 if the type of this coordinate or the type of \a other is + QGeoCoordinate::InvalidCoordinate. +*/ +qreal QGeoCoordinate::azimuthTo(const QGeoCoordinate &other) const +{ + if (type() == QGeoCoordinate::InvalidCoordinate + || other.type() == QGeoCoordinate::InvalidCoordinate) { + return 0; + } + + double dlon = qgeocoordinate_degToRad(other.d->lng - d->lng); + double lat1Rad = qgeocoordinate_degToRad(d->lat); + double lat2Rad = qgeocoordinate_degToRad(other.d->lat); + + double y = sin(dlon) * cos(lat2Rad); + double x = cos(lat1Rad) * sin(lat2Rad) - sin(lat1Rad) * cos(lat2Rad) * cos(dlon); + + double azimuth = qgeocoordinate_radToDeg(atan2(y, x)) + 360.0; + double whole; + double fraction = modf(azimuth, &whole); + return qreal((int(whole + 360) % 360) + fraction); +} + +void QGeoCoordinatePrivate::atDistanceAndAzimuth(const QGeoCoordinate &coord, + qreal distance, qreal azimuth, + double *lon, double *lat) +{ + double latRad = qgeocoordinate_degToRad(coord.d->lat); + double lonRad = qgeocoordinate_degToRad(coord.d->lng); + double cosLatRad = cos(latRad); + double sinLatRad = sin(latRad); + + double azimuthRad = qgeocoordinate_degToRad(azimuth); + + double ratio = (distance / (qgeocoordinate_EARTH_MEAN_RADIUS * 1000.0)); + double cosRatio = cos(ratio); + double sinRatio = sin(ratio); + + double resultLatRad = asin(sinLatRad * cosRatio + + cosLatRad * sinRatio * cos(azimuthRad)); + double resultLonRad = lonRad + atan2(sin(azimuthRad) * sinRatio * cosLatRad, + cosRatio - sinLatRad * sin(resultLatRad)); + + *lat = qgeocoordinate_radToDeg(resultLatRad); + *lon = qgeocoordinate_radToDeg(resultLonRad); +} + +/*! + Returns the coordinate that is reached by traveling \a distance meters + from the current coordinate at \a azimuth (or bearing) along a great-circle. + There is an assumption that the Earth is spherical for the purpose of this + calculation. + + The altitude will have \a distanceUp added to it. + + Returns an invalid coordinate if this coordinate is invalid. +*/ +QGeoCoordinate QGeoCoordinate::atDistanceAndAzimuth(qreal distance, qreal azimuth, qreal distanceUp) const +{ + if (!isValid()) + return QGeoCoordinate(); + + double resultLon, resultLat; + QGeoCoordinatePrivate::atDistanceAndAzimuth(*this, distance, azimuth, + &resultLon, &resultLat); + + if (resultLon > 180.0) + resultLon -= 360.0; + else if (resultLon < -180.0) + resultLon += 360.0; + + double resultAlt = d->alt + distanceUp; + return QGeoCoordinate(resultLat, resultLon, resultAlt); +} + +/*! + Returns this coordinate as a string in the specified \a format. + + For example, if this coordinate has a latitude of -27.46758, a longitude + of 153.027892 and an altitude of 28.1, these are the strings + returned depending on \a format: + + \table + \header + \li \a format value + \li Returned string + \row + \li \l Degrees + \li -27.46758\unicode{0xB0}, 153.02789\unicode{0xB0}, 28.1m + \row + \li \l DegreesWithHemisphere + \li 27.46758\unicode{0xB0} S, 153.02789\unicode{0xB0} E, 28.1m + \row + \li \l DegreesMinutes + \li -27\unicode{0xB0} 28.054', 153\unicode{0xB0} 1.673', 28.1m + \row + \li \l DegreesMinutesWithHemisphere + \li 27\unicode{0xB0} 28.054 S', 153\unicode{0xB0} 1.673' E, 28.1m + \row + \li \l DegreesMinutesSeconds + \li -27\unicode{0xB0} 28' 3.2", 153\unicode{0xB0} 1' 40.4", 28.1m + \row + \li \l DegreesMinutesSecondsWithHemisphere + \li 27\unicode{0xB0} 28' 3.2" S, 153\unicode{0xB0} 1' 40.4" E, 28.1m + \endtable + + The altitude field is omitted if no altitude is set. + + If the coordinate is invalid, an empty string is returned. +*/ +QString QGeoCoordinate::toString(CoordinateFormat format) const +{ + if (type() == QGeoCoordinate::InvalidCoordinate) + return QString(); + + QString latStr; + QString longStr; + + double absLat = qAbs(d->lat); + double absLng = qAbs(d->lng); + QChar symbol(0x00B0); // degrees symbol + + switch (format) { + case Degrees: + case DegreesWithHemisphere: { + latStr = QString::number(absLat, 'f', 5) + symbol; + longStr = QString::number(absLng, 'f', 5) + symbol; + break; + } + case DegreesMinutes: + case DegreesMinutesWithHemisphere: { + double latMin = (absLat - int(absLat)) * 60; + double lngMin = (absLng - int(absLng)) * 60; + + if (qRound(latMin) >= 60) { + absLat++; + latMin = qAbs(latMin - 60.0f); + //avoid invalid latitude due to latMin rounding below + if (qRound(absLat) >= 90) + latMin = 0.0f; + } + if (qRound(lngMin) >= 60) { + absLng++; + lngMin = qAbs(lngMin - 60.0f); + // avoid invalid longitude due to lngMin rounding below + if (qRound(absLng) >= 180) + lngMin = 0.0f; + } + + latStr = QString::fromLatin1("%1%2 %3'") + .arg(QString::number(int(absLat))) + .arg(symbol) + .arg(QString::number(latMin, 'f', 3)); + longStr = QString::fromLatin1("%1%2 %3'") + .arg(QString::number(int(absLng))) + .arg(symbol) + .arg(QString::number(lngMin, 'f', 3)); + break; + } + case DegreesMinutesSeconds: + case DegreesMinutesSecondsWithHemisphere: { + double latMin = (absLat - int(absLat)) * 60; + double lngMin = (absLng - int(absLng)) * 60; + double latSec = (latMin - int(latMin)) * 60; + double lngSec = (lngMin - int(lngMin)) * 60; + + // overflow to full minutes + if (qRound(latSec) >= 60) { + latMin++; + latSec = qAbs(latSec - 60.0f); + // overflow to full degrees + if (qRound(latMin) >= 60) { + absLat++; + latMin = qAbs(latMin - 60.0f); + // avoid invalid latitude due to latSec rounding below + if (qRound(absLat) >= 90) + latSec = 0.0f; + } + } + if (qRound(lngSec) >= 60) { + lngMin++; + lngSec = qAbs(lngSec - 60.0f); + if (qRound(lngMin) >= 60) { + absLng++; + lngMin = qAbs(lngMin - 60.0f); + // avoid invalid longitude due to lngSec rounding below + if (qRound(absLng) >= 180) + lngSec = 0.0f; + } + } + + latStr = QString::fromLatin1("%1%2 %3' %4\"") + .arg(QString::number(int(absLat))) + .arg(symbol) + .arg(QString::number(int(latMin))) + .arg(QString::number(latSec, 'f', 1)); + longStr = QString::fromLatin1("%1%2 %3' %4\"") + .arg(QString::number(int(absLng))) + .arg(symbol) + .arg(QString::number(int(lngMin))) + .arg(QString::number(lngSec, 'f', 1)); + break; + } + } + + // now add the "-" to the start, or append the hemisphere char + switch (format) { + case Degrees: + case DegreesMinutes: + case DegreesMinutesSeconds: { + if (d->lat < 0) + latStr.insert(0, QStringLiteral("-")); + if (d->lng < 0) + longStr.insert(0, QStringLiteral("-")); + break; + } + case DegreesWithHemisphere: + case DegreesMinutesWithHemisphere: + case DegreesMinutesSecondsWithHemisphere: { + if (d->lat < 0) + latStr.append(QString::fromLatin1(" S")); + else if (d->lat > 0) + latStr.append(QString::fromLatin1(" N")); + if (d->lng < 0) + longStr.append(QString::fromLatin1(" W")); + else if (d->lng > 0) + longStr.append(QString::fromLatin1(" E")); + break; + } + } + + if (qIsNaN(d->alt)) + return QString::fromLatin1("%1, %2").arg(latStr, longStr); + return QString::fromLatin1("%1, %2, %3m").arg(latStr, longStr, QString::number(d->alt)); +} + +QGeoCoordinate::QGeoCoordinate(QGeoCoordinatePrivate &dd): + d(&dd) +{ +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QGeoCoordinate &coord) +{ + QDebugStateSaver saver(dbg); + double lat = coord.latitude(); + double lng = coord.longitude(); + + dbg.nospace() << "QGeoCoordinate("; + if (qIsNaN(lat)) + dbg << '?'; + else + dbg << lat; + dbg << ", "; + if (qIsNaN(lng)) + dbg << '?'; + else + dbg << lng; + if (coord.type() == QGeoCoordinate::Coordinate3D) { + dbg << ", "; + dbg << coord.altitude(); + } + dbg << ')'; + return dbg; +} +#endif + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QGeoCoordinate &coordinate) + + \relates QGeoCoordinate + + Writes the given \a coordinate to the specified \a stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &stream, const QGeoCoordinate &coordinate) +{ + stream << coordinate.latitude(); + stream << coordinate.longitude(); + stream << coordinate.altitude(); + return stream; +} +#endif + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator>>(QDataStream &stream, QGeoCoordinate &coordinate) + \relates QGeoCoordinate + + Reads a coordinate from the specified \a stream into the given + \a coordinate. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &stream, QGeoCoordinate &coordinate) +{ + double value; + stream >> value; + coordinate.setLatitude(value); + stream >> value; + coordinate.setLongitude(value); + stream >> value; + coordinate.setAltitude(value); + return stream; +} +#endif + +/*! \fn uint qHash(const QGeoCoordinate &coordinate, uint seed = 0) + \relates QHash + \since Qt 5.7 + + Returns a hash value for \a coordinate, using \a seed to seed the calculation. +*/ +uint qHash(const QGeoCoordinate &coordinate, uint seed) +{ + QtPrivate::QHashCombine hash; + // north and south pole are geographically equivalent (no matter the longitude) + if (coordinate.latitude() != 90.0 && coordinate.latitude() != -90.0) + seed = hash(seed, coordinate.longitude()); + seed = hash(seed, coordinate.latitude()); + seed = hash(seed, coordinate.altitude()); + return seed; +} + +QT_END_NAMESPACE diff --git a/src/positioning/qgeocoordinate.h b/src/positioning/qgeocoordinate.h new file mode 100644 index 0000000..ddb6274 --- /dev/null +++ b/src/positioning/qgeocoordinate.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCOORDINATE_H +#define QGEOCOORDINATE_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDebug; +class QDataStream; + +class QGeoCoordinatePrivate; +class Q_POSITIONING_EXPORT QGeoCoordinate +{ + Q_GADGET + + Q_PROPERTY(double latitude READ latitude WRITE setLatitude) + Q_PROPERTY(double longitude READ longitude WRITE setLongitude) + Q_PROPERTY(double altitude READ altitude WRITE setAltitude) + Q_PROPERTY(bool isValid READ isValid) + +public: + + enum CoordinateType { + InvalidCoordinate, + Coordinate2D, + Coordinate3D + }; + + enum CoordinateFormat { + Degrees, + DegreesWithHemisphere, + DegreesMinutes, + DegreesMinutesWithHemisphere, + DegreesMinutesSeconds, + DegreesMinutesSecondsWithHemisphere + }; + + QGeoCoordinate(); + QGeoCoordinate(double latitude, double longitude); + QGeoCoordinate(double latitude, double longitude, double altitude); + QGeoCoordinate(const QGeoCoordinate &other); + ~QGeoCoordinate(); + + QGeoCoordinate &operator=(const QGeoCoordinate &other); + + bool operator==(const QGeoCoordinate &other) const; + inline bool operator!=(const QGeoCoordinate &other) const { + return !operator==(other); + } + + bool isValid() const; + CoordinateType type() const; + + void setLatitude(double latitude); + double latitude() const; + + void setLongitude(double longitude); + double longitude() const; + + void setAltitude(double altitude); + double altitude() const; + + Q_INVOKABLE qreal distanceTo(const QGeoCoordinate &other) const; + Q_INVOKABLE qreal azimuthTo(const QGeoCoordinate &other) const; + + Q_INVOKABLE QGeoCoordinate atDistanceAndAzimuth(qreal distance, qreal azimuth, qreal distanceUp = 0.0) const; + + Q_INVOKABLE QString toString(CoordinateFormat format = DegreesMinutesSecondsWithHemisphere) const; + +private: + QGeoCoordinate(QGeoCoordinatePrivate &dd); + QSharedDataPointer d; + friend class QGeoCoordinatePrivate; + friend class QQuickGeoCoordinateAnimation; +}; + +Q_DECLARE_TYPEINFO(QGeoCoordinate, Q_MOVABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_POSITIONING_EXPORT QDebug operator<<(QDebug, const QGeoCoordinate &); +#endif + +Q_POSITIONING_EXPORT uint qHash(const QGeoCoordinate &coordinate, uint seed = 0); + +#ifndef QT_NO_DATASTREAM +Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &stream, const QGeoCoordinate &coordinate); +Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &stream, QGeoCoordinate &coordinate); +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoCoordinate) + +#endif diff --git a/src/positioning/qgeocoordinate_p.h b/src/positioning/qgeocoordinate_p.h new file mode 100644 index 0000000..e00cbfa --- /dev/null +++ b/src/positioning/qgeocoordinate_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCOORDINATE_P_H +#define QGEOCOORDINATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include "qgeocoordinate.h" + +QT_BEGIN_NAMESPACE + +class QGeoCoordinatePrivate : public QSharedData +{ +public: + QGeoCoordinatePrivate(); + QGeoCoordinatePrivate(const QGeoCoordinatePrivate &other); + ~QGeoCoordinatePrivate(); + + double lat; + double lng; + double alt; + + static void atDistanceAndAzimuth(const QGeoCoordinate &coord, + qreal distance, qreal azimuth, + double *lon, double *lat); + static const QGeoCoordinatePrivate *get(const QGeoCoordinate *c) { + return c->d.constData(); + } +}; + +class Q_POSITIONING_EXPORT QGeoMercatorCoordinatePrivate : public QGeoCoordinatePrivate +{ +public: + QGeoMercatorCoordinatePrivate(); + QGeoMercatorCoordinatePrivate(const QGeoMercatorCoordinatePrivate &other); + ~QGeoMercatorCoordinatePrivate(); + + double m_mercatorX; + double m_mercatorY; +}; + + +QT_END_NAMESPACE + +#endif // QGEOCOORDINATE_P_H diff --git a/src/positioning/qgeolocation.cpp b/src/positioning/qgeolocation.cpp new file mode 100644 index 0000000..62e1a6a --- /dev/null +++ b/src/positioning/qgeolocation.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeolocation.h" +#include "qgeolocation_p.h" + +QT_USE_NAMESPACE + +QGeoLocationPrivate::QGeoLocationPrivate() + : QSharedData() +{ +} + +QGeoLocationPrivate::QGeoLocationPrivate(const QGeoLocationPrivate &other) + : QSharedData() +{ + this->address = other.address; + this->coordinate = other.coordinate; + this->viewport = other.viewport; +} + +QGeoLocationPrivate::~QGeoLocationPrivate() +{ +} + +bool QGeoLocationPrivate::operator==(const QGeoLocationPrivate &other) const +{ + return (this->address == other.address + && this->coordinate == other.coordinate + && this->viewport == other.viewport); + +} + +bool QGeoLocationPrivate::isEmpty() const +{ + return (address.isEmpty() + && !coordinate.isValid() + && viewport.isEmpty() + ); +} + +/*! + \class QGeoLocation + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \ingroup QtLocation-places + \ingroup QtLocation-places-data + \since 5.2 + + \brief The QGeoLocation class represents basic information about a location. + + A QGeoLocation consists of a coordinate and corresponding address, along with an optional + bounding box which is the recommended region to be displayed when viewing the location. +*/ + +/*! + \fn bool QGeoLocation::operator!=(const QGeoLocation &other) const + + Returns true if this location is not equal to \a other, otherwise returns false. +*/ + +/*! + Constructs an new location object. +*/ +QGeoLocation::QGeoLocation() + : d(new QGeoLocationPrivate) +{ +} + +/*! + Constructs a copy of \a other +*/ +QGeoLocation::QGeoLocation(const QGeoLocation &other) + :d(other.d) +{ +} + +/*! + Destroys the location object. +*/ +QGeoLocation::~QGeoLocation() +{ +} + +/*! + Assigns \a other to this location and returns a reference to this location. +*/ +QGeoLocation &QGeoLocation::operator =(const QGeoLocation &other) +{ + if (this == &other) + return *this; + + d = other.d; + return *this; +} + +/*! + Returns true if this location is equal to \a other, + otherwise returns false. +*/ +bool QGeoLocation::operator==(const QGeoLocation &other) const +{ + return (*(d.constData()) == *(other.d.constData())); +} + +/*! + Returns the address of the location. +*/ +QGeoAddress QGeoLocation::address() const +{ + return d->address; +} + +/*! + Sets the \a address of the location. +*/ +void QGeoLocation::setAddress(const QGeoAddress &address) +{ + d->address = address; +} + +/*! + Returns the coordinate of the location. +*/ +QGeoCoordinate QGeoLocation::coordinate() const +{ + return d->coordinate; +} + +/*! + Sets the \a coordinate of the location. +*/ +void QGeoLocation::setCoordinate(const QGeoCoordinate &coordinate) +{ + d->coordinate = coordinate; +} + +/*! + Returns a bounding box which represents the recommended region + to display when viewing this location. + + For example, a building's location may have a region centered around the building, + but the region is large enough to show it's immediate surrounding geographical + context. +*/ +QGeoRectangle QGeoLocation::boundingBox() const +{ + return d->viewport; +} + +/*! + Sets the \a boundingBox of the location. +*/ +void QGeoLocation::setBoundingBox(const QGeoRectangle &boundingBox) +{ + d->viewport = boundingBox; +} + +/*! + Returns true if all fields of the location are 0; otherwise returns false. +*/ +bool QGeoLocation::isEmpty() const +{ + return d->isEmpty(); +} diff --git a/src/positioning/qgeolocation.h b/src/positioning/qgeolocation.h new file mode 100644 index 0000000..580b2fb --- /dev/null +++ b/src/positioning/qgeolocation.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOLOCATION_H +#define QGEOLOCATION_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoAddress; +class QGeoCoordinate; +class QGeoRectangle; +class QGeoLocationPrivate; + +class Q_POSITIONING_EXPORT QGeoLocation +{ +public: + QGeoLocation(); + QGeoLocation(const QGeoLocation &other); + + ~QGeoLocation(); + + QGeoLocation &operator=(const QGeoLocation &other); + + bool operator==(const QGeoLocation &other) const; + bool operator!=(const QGeoLocation &other) const { + return !(other == *this); + } + + QGeoAddress address() const; + void setAddress(const QGeoAddress &address); + QGeoCoordinate coordinate() const; + void setCoordinate(const QGeoCoordinate &position); + QGeoRectangle boundingBox() const; + void setBoundingBox(const QGeoRectangle &box); + + bool isEmpty() const; + +private: + QSharedDataPointer d; +}; + +Q_DECLARE_TYPEINFO(QGeoLocation, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoLocation) + +#endif diff --git a/src/positioning/qgeolocation_p.h b/src/positioning/qgeolocation_p.h new file mode 100644 index 0000000..a12e4cb --- /dev/null +++ b/src/positioning/qgeolocation_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOLOCATION_P_H +#define QGEOLOCATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoLocationPrivate : public QSharedData +{ +public: + QGeoLocationPrivate(); + QGeoLocationPrivate(const QGeoLocationPrivate &other); + + ~QGeoLocationPrivate(); + + bool operator==(const QGeoLocationPrivate &other) const; + + bool isEmpty() const; + + QGeoAddress address; + QGeoCoordinate coordinate; + QGeoRectangle viewport; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeopositioninfo.cpp b/src/positioning/qgeopositioninfo.cpp new file mode 100644 index 0000000..84b7fa1 --- /dev/null +++ b/src/positioning/qgeopositioninfo.cpp @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeopositioninfo.h" + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoPositionInfoPrivate +{ +public: + QDateTime timestamp; + QGeoCoordinate coord; + QHash doubleAttribs; +}; + +/*! + \class QGeoPositionInfo + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QGeoPositionInfo class contains information gathered on a global position, direction and velocity at a particular point in time. + + A QGeoPositionInfo contains, at a minimum, a geographical coordinate and + a timestamp. It may also have heading and speed measurements as well as + estimates of the accuracy of the provided data. + + \sa QGeoPositionInfoSource +*/ + +/*! + \enum QGeoPositionInfo::Attribute + Defines the attributes for positional information. + + \value Direction The bearing measured in degrees clockwise from true north to the direction of travel. + \value GroundSpeed The ground speed, in meters/sec. + \value VerticalSpeed The vertical speed, in meters/sec. + \value MagneticVariation The angle between the horizontal component of the magnetic field and true north, in degrees. Also known as magnetic declination. A positive value indicates a clockwise direction from true north and a negative value indicates a counter-clockwise direction. + \value HorizontalAccuracy The accuracy of the provided latitude-longitude value, in meters. + \value VerticalAccuracy The accuracy of the provided altitude value, in meters. +*/ + +/*! + Creates an invalid QGeoPositionInfo object. + + \sa isValid() +*/ +QGeoPositionInfo::QGeoPositionInfo() + : d(new QGeoPositionInfoPrivate) +{ +} + +/*! + Creates a QGeoPositionInfo for the given \a coordinate and \a timestamp. +*/ +QGeoPositionInfo::QGeoPositionInfo(const QGeoCoordinate &coordinate, const QDateTime ×tamp) + : d(new QGeoPositionInfoPrivate) +{ + d->timestamp = timestamp; + d->coord = coordinate; +} + +/*! + Creates a QGeoPositionInfo with the values of \a other. +*/ +QGeoPositionInfo::QGeoPositionInfo(const QGeoPositionInfo &other) + : d(new QGeoPositionInfoPrivate) +{ + operator=(other); +} + +/*! + Destroys a QGeoPositionInfo object. +*/ +QGeoPositionInfo::~QGeoPositionInfo() +{ + delete d; +} + +/*! + Assigns the values from \a other to this QGeoPositionInfo. +*/ +QGeoPositionInfo &QGeoPositionInfo::operator=(const QGeoPositionInfo & other) +{ + if (this == &other) + return *this; + + d->timestamp = other.d->timestamp; + d->coord = other.d->coord; + d->doubleAttribs = other.d->doubleAttribs; + + return *this; +} + +/*! + Returns true if all of this object's values are the same as those of + \a other. +*/ +bool QGeoPositionInfo::operator==(const QGeoPositionInfo &other) const +{ + return d->timestamp == other.d->timestamp + && d->coord == other.d->coord + && d->doubleAttribs == other.d->doubleAttribs; +} + +/*! + \fn bool QGeoPositionInfo::operator!=(const QGeoPositionInfo &other) const + + Returns true if any of this object's values are not the same as those of + \a other. +*/ + +/*! + Returns true if the timestamp() and coordinate() values are both valid. + + \sa QGeoCoordinate::isValid(), QDateTime::isValid() +*/ +bool QGeoPositionInfo::isValid() const +{ + return d->timestamp.isValid() && d->coord.isValid(); +} + +/*! + Sets the date and time at which this position was reported to \a timestamp. + + The \a timestamp must be in UTC time. + + \sa timestamp() +*/ +void QGeoPositionInfo::setTimestamp(const QDateTime ×tamp) +{ + d->timestamp = timestamp; +} + +/*! + Returns the date and time at which this position was reported, in UTC time. + + Returns an invalid QDateTime if no date/time value has been set. + + \sa setTimestamp() +*/ +QDateTime QGeoPositionInfo::timestamp() const +{ + return d->timestamp; +} + +/*! + Sets the coordinate for this position to \a coordinate. + + \sa coordinate() +*/ +void QGeoPositionInfo::setCoordinate(const QGeoCoordinate &coordinate) +{ + d->coord = coordinate; +} + +/*! + Returns the coordinate for this position. + + Returns an invalid coordinate if no coordinate has been set. + + \sa setCoordinate() +*/ +QGeoCoordinate QGeoPositionInfo::coordinate() const +{ + return d->coord; +} + +/*! + Sets the value for \a attribute to \a value. + + \sa attribute() +*/ +void QGeoPositionInfo::setAttribute(Attribute attribute, qreal value) +{ + d->doubleAttribs[attribute] = value; +} + +/*! + Returns the value of the specified \a attribute as a qreal value. + + Returns NaN if the value has not been set. + + The function hasAttribute() should be used to determine whether or + not a value has been set for an attribute. + + \sa hasAttribute(), setAttribute() +*/ +qreal QGeoPositionInfo::attribute(Attribute attribute) const +{ + if (d->doubleAttribs.contains(attribute)) + return d->doubleAttribs[attribute]; + return qQNaN(); +} + +/*! + Removes the specified \a attribute and its value. +*/ +void QGeoPositionInfo::removeAttribute(Attribute attribute) +{ + d->doubleAttribs.remove(attribute); +} + +/*! + Returns true if the specified \a attribute is present for this + QGeoPositionInfo object. +*/ +bool QGeoPositionInfo::hasAttribute(Attribute attribute) const +{ + return d->doubleAttribs.contains(attribute); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QGeoPositionInfo &info) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QGeoPositionInfo(" << info.d->timestamp; + dbg.nospace() << ", "; // timestamp force dbg.space() -> reverting here + dbg << info.d->coord; + + QList attribs = info.d->doubleAttribs.keys(); + std::stable_sort(attribs.begin(), attribs.end()); // Output a sorted list from an unsorted hash. + for (int i = 0; i < attribs.count(); ++i) { + dbg << ", "; + switch (attribs[i]) { + case QGeoPositionInfo::Direction: + dbg << "Direction="; + break; + case QGeoPositionInfo::GroundSpeed: + dbg << "GroundSpeed="; + break; + case QGeoPositionInfo::VerticalSpeed: + dbg << "VerticalSpeed="; + break; + case QGeoPositionInfo::MagneticVariation: + dbg << "MagneticVariation="; + break; + case QGeoPositionInfo::HorizontalAccuracy: + dbg << "HorizontalAccuracy="; + break; + case QGeoPositionInfo::VerticalAccuracy: + dbg << "VerticalAccuracy="; + break; + } + dbg << info.d->doubleAttribs[attribs[i]]; + } + dbg << ')'; + return dbg; +} +#endif + + +#ifndef QT_NO_DATASTREAM +/*! + \relates QGeoPositionInfo + + Writes the given \a attr enumeration to the specified \a stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &stream, QGeoPositionInfo::Attribute attr) +{ + return stream << int(attr); +} + +/*! + \relates QGeoPositionInfo + + Reads an attribute enumeration from the specified \a stream info the given \a attr. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator>>(QDataStream &stream, QGeoPositionInfo::Attribute &attr) +{ + int a; + stream >> a; + attr = static_cast(a); + return stream; +} + +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QGeoPositionInfo &info) + \relates QGeoPositionInfo + + Writes the given \a info to the specified \a stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &stream, const QGeoPositionInfo &info) +{ + stream << info.d->timestamp; + stream << info.d->coord; + stream << info.d->doubleAttribs; + return stream; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QGeoPositionInfo &info) + \relates QGeoPositionInfo + + Reads a coordinate from the specified \a stream into the given + \a info. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &stream, QGeoPositionInfo &info) +{ + stream >> info.d->timestamp; + stream >> info.d->coord; + stream >> info.d->doubleAttribs; + return stream; +} +#endif + +QT_END_NAMESPACE diff --git a/src/positioning/qgeopositioninfo.h b/src/positioning/qgeopositioninfo.h new file mode 100644 index 0000000..9f43fe8 --- /dev/null +++ b/src/positioning/qgeopositioninfo.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOPOSITIONINFO_H +#define QGEOPOSITIONINFO_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDebug; +class QDataStream; + +class QGeoPositionInfoPrivate; +class Q_POSITIONING_EXPORT QGeoPositionInfo +{ +public: + enum Attribute { + Direction, + GroundSpeed, + VerticalSpeed, + MagneticVariation, + HorizontalAccuracy, + VerticalAccuracy + }; + + QGeoPositionInfo(); + QGeoPositionInfo(const QGeoCoordinate &coordinate, const QDateTime &updateTime); + QGeoPositionInfo(const QGeoPositionInfo &other); + ~QGeoPositionInfo(); + + QGeoPositionInfo &operator=(const QGeoPositionInfo &other); + + bool operator==(const QGeoPositionInfo &other) const; + inline bool operator!=(const QGeoPositionInfo &other) const { + return !operator==(other); + } + + bool isValid() const; + + void setTimestamp(const QDateTime ×tamp); + QDateTime timestamp() const; + + void setCoordinate(const QGeoCoordinate &coordinate); + QGeoCoordinate coordinate() const; + + void setAttribute(Attribute attribute, qreal value); + qreal attribute(Attribute attribute) const; + void removeAttribute(Attribute attribute); + bool hasAttribute(Attribute attribute) const; + +private: +#ifndef QT_NO_DEBUG_STREAM + friend Q_POSITIONING_EXPORT QDebug operator<<(QDebug dbg, const QGeoPositionInfo &info); +#endif +#ifndef QT_NO_DATASTREAM + friend Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &stream, const QGeoPositionInfo &info); + friend Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &stream, QGeoPositionInfo &info); +#endif + QGeoPositionInfoPrivate *d; +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_POSITIONING_EXPORT QDebug operator<<(QDebug dbg, const QGeoPositionInfo &info); +#endif + +#ifndef QT_NO_DATASTREAM +Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &stream, QGeoPositionInfo::Attribute attr); +Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &stream, QGeoPositionInfo::Attribute &attr); +Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &stream, const QGeoPositionInfo &info); +Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &stream, QGeoPositionInfo &info); +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeopositioninfosource.cpp b/src/positioning/qgeopositioninfosource.cpp new file mode 100644 index 0000000..facace9 --- /dev/null +++ b/src/positioning/qgeopositioninfosource.cpp @@ -0,0 +1,483 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include +#include "qgeopositioninfosource_p.h" +#include "qgeopositioninfosourcefactory.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + ("org.qt-project.qt.position.sourcefactory/5.0", + QLatin1String("/position"))) +#endif + +/*! + \class QGeoPositionInfoSource + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QGeoPositionInfoSource class is an abstract base class for the distribution of positional updates. + + The static function QGeoPositionInfoSource::createDefaultSource() creates a default + position source that is appropriate for the platform, if one is available. + Otherwise, QGeoPositionInfoSource will check for available plugins that + implement the QGeoPositionInfoSourceFactory interface. + + Users of a QGeoPositionInfoSource subclass can request the current position using + requestUpdate(), or start and stop regular position updates using + startUpdates() and stopUpdates(). When an update is available, + positionUpdated() is emitted. The last known position can be accessed with + lastKnownPosition(). + + If regular position updates are required, setUpdateInterval() can be used + to specify how often these updates should be emitted. If no interval is + specified, updates are simply provided whenever they are available. + For example: + + \code + // Emit updates every 10 seconds if available + QGeoPositionInfoSource *source = QGeoPositionInfoSource::createDefaultSource(0); + if (source) + source->setUpdateInterval(10000); + \endcode + + To remove an update interval that was previously set, call + setUpdateInterval() with a value of 0. + + Note that the position source may have a minimum value requirement for + update intervals, as returned by minimumUpdateInterval(). +*/ + +/*! + \enum QGeoPositionInfoSource::PositioningMethod + Defines the types of positioning methods. + + \value NoPositioningMethods None of the positioning methods. + \value SatellitePositioningMethods Satellite-based positioning methods such as GPS or GLONASS. + \value NonSatellitePositioningMethods Other positioning methods such as 3GPP cell identifier or WiFi based positioning. + \value AllPositioningMethods Satellite-based positioning methods as soon as available. Otherwise non-satellite based methods. +*/ + +void QGeoPositionInfoSourcePrivate::loadMeta() +{ + metaData = plugins().value(providerName); +} + +void QGeoPositionInfoSourcePrivate::loadPlugin() +{ + int idx = int(metaData.value(QStringLiteral("index")).toDouble()); + if (idx < 0) + return; + factory = qobject_cast(loader()->instance(idx)); +} + +QHash QGeoPositionInfoSourcePrivate::plugins(bool reload) +{ + static QHash plugins; + static bool alreadyDiscovered = false; + + if (reload == true) + alreadyDiscovered = false; + + if (!alreadyDiscovered) { + loadPluginMetadata(plugins); + alreadyDiscovered = true; + } + return plugins; +} + +static bool pluginComparator(const QJsonObject &p1, const QJsonObject &p2) +{ + const QString prio = QStringLiteral("Priority"); + if (p1.contains(prio) && !p2.contains(prio)) + return true; + if (!p1.contains(prio) && p2.contains(prio)) + return false; + if (p1.value(prio).isDouble() && !p2.value(prio).isDouble()) + return true; + if (!p1.value(prio).isDouble() && p2.value(prio).isDouble()) + return false; + return (p1.value(prio).toDouble() > p2.value(prio).toDouble()); +} + +QList QGeoPositionInfoSourcePrivate::pluginsSorted() +{ + QList list = plugins().values(); + std::stable_sort(list.begin(), list.end(), pluginComparator); + return list; +} + +void QGeoPositionInfoSourcePrivate::loadPluginMetadata(QHash &plugins) +{ + QFactoryLoader *l = loader(); + QList meta = l->metaData(); + for (int i = 0; i < meta.size(); ++i) { + QJsonObject obj = meta.at(i).value(QStringLiteral("MetaData")).toObject(); + const QString testableKey = QStringLiteral("Testable"); + if (obj.contains(testableKey) && !obj.value(testableKey).toBool()) { + static bool inTest = qEnvironmentVariableIsSet("QT_QTESTLIB_RUNNING"); + if (inTest) + continue; + } + obj.insert(QStringLiteral("index"), i); + plugins.insertMulti(obj.value(QStringLiteral("Provider")).toString(), obj); + } +} + +/*! + Creates a position source with the specified \a parent. +*/ + +QGeoPositionInfoSource::QGeoPositionInfoSource(QObject *parent) + : QObject(parent), + d(new QGeoPositionInfoSourcePrivate) +{ + d->interval = 0; + d->methods = 0; +} + +/*! + Destroys the position source. +*/ +QGeoPositionInfoSource::~QGeoPositionInfoSource() +{ + delete d; +} + +/*! + \property QGeoPositionInfoSource::sourceName + \brief This property holds the unique name of the position source + implementation in use. + + This is the same name that can be passed to createSource() in order to + create a new instance of a particular position source implementation. +*/ +QString QGeoPositionInfoSource::sourceName() const +{ + return d->metaData.value(QStringLiteral("Provider")).toString(); +} + +/*! + \property QGeoPositionInfoSource::updateInterval + \brief This property holds the requested interval in milliseconds between each update. + + If the update interval is not set (or is set to 0) the + source will provide updates as often as necessary. + + If the update interval is set, the source will provide updates at an + interval as close to the requested interval as possible. If the requested + interval is less than the minimumUpdateInterval(), + the minimum interval is used instead. + + Changes to the update interval will happen as soon as is practical, however the + time the change takes may vary between implementations. Whether or not the elapsed + time from the previous interval is counted as part of the new interval is also + implementation dependent. + + The default value for this property is 0. + + Note: Subclass implementations must call the base implementation of + setUpdateInterval() so that updateInterval() returns the correct value. +*/ +void QGeoPositionInfoSource::setUpdateInterval(int msec) +{ + d->interval = msec; +} + +int QGeoPositionInfoSource::updateInterval() const +{ + return d->interval; +} + +/*! + Sets the preferred positioning methods for this source to \a methods. + + If \a methods includes a method that is not supported by the source, the + unsupported method will be ignored. + + If \a methods does not include any methods supported by the source, the + preferred methods will be set to the set of methods which the source supports. + + \b {Note:} When reimplementing this method, subclasses must call the + base method implementation to ensure preferredPositioningMethods() returns the correct value. + + \sa supportedPositioningMethods() +*/ +void QGeoPositionInfoSource::setPreferredPositioningMethods(PositioningMethods methods) +{ + d->methods = methods & supportedPositioningMethods(); + if (d->methods == 0) { + d->methods = supportedPositioningMethods(); + } +} + +/*! + Returns the positioning methods set by setPreferredPositioningMethods(). +*/ +QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSource::preferredPositioningMethods() const +{ + return d->methods; +} + +/*! + Creates and returns a position source with the given \a parent that + reads from the system's default sources of location data, or the plugin + with the highest available priority. + + Returns 0 if the system has no default position source, no valid plugins + could be found or the user does not have the permission to access the current position. +*/ +QGeoPositionInfoSource *QGeoPositionInfoSource::createDefaultSource(QObject *parent) +{ + QList plugins = QGeoPositionInfoSourcePrivate::pluginsSorted(); + foreach (const QJsonObject &obj, plugins) { + if (obj.value(QStringLiteral("Position")).isBool() + && obj.value(QStringLiteral("Position")).toBool()) + { + QGeoPositionInfoSourcePrivate d; + d.metaData = obj; + d.loadPlugin(); + QGeoPositionInfoSource *s = 0; + if (d.factory) + s = d.factory->positionInfoSource(parent); + if (s) { + s->d->metaData = d.metaData; + return s; + } + } + } + return 0; +} + + +/*! + Creates and returns a position source with the given \a parent, + by loading the plugin named \a sourceName. + + Returns 0 if the plugin cannot be found. +*/ +QGeoPositionInfoSource *QGeoPositionInfoSource::createSource(const QString &sourceName, QObject *parent) +{ + QHash plugins = QGeoPositionInfoSourcePrivate::plugins(); + if (plugins.contains(sourceName)) + { + QGeoPositionInfoSourcePrivate d; + d.metaData = plugins.value(sourceName); + d.loadPlugin(); + QGeoPositionInfoSource *src = 0; + if (d.factory) + src = d.factory->positionInfoSource(parent); + if (src) + { + src->d->metaData = d.metaData; + return src; + } + } + return 0; +} + + +/*! + Returns a list of available source plugins. This includes any default backend + plugin for the current platform. +*/ +QStringList QGeoPositionInfoSource::availableSources() +{ + QStringList plugins; + const QHash meta = QGeoPositionInfoSourcePrivate::plugins(); + for (auto it = meta.cbegin(), end = meta.cend(); it != end; ++it) { + if (it.value().value(QStringLiteral("Position")).isBool() + && it.value().value(QStringLiteral("Position")).toBool()) { + plugins << it.key(); + } + } + + return plugins; +} + +/*! + \fn QGeoPositionInfo QGeoPositionInfoSource::lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const = 0; + + Returns an update containing the last known position, or a null update + if none is available. + + If \a fromSatellitePositioningMethodsOnly is true, this returns the last + known position received from a satellite positioning method; if none + is available, a null update is returned. +*/ + +/*! + \fn virtual PositioningMethods QGeoPositionInfoSource::supportedPositioningMethods() const = 0; + + Returns the positioning methods available to this source. + + \sa setPreferredPositioningMethods() +*/ + + +/*! + \property QGeoPositionInfoSource::minimumUpdateInterval + \brief This property holds the minimum time (in milliseconds) required to retrieve a position update. + + This is the minimum value accepted by setUpdateInterval() and + requestUpdate(). +*/ + + +/*! + \fn virtual void QGeoPositionInfoSource::startUpdates() = 0; + + Starts emitting updates at regular intervals as specified by setUpdateInterval(). + + If setUpdateInterval() has not been called, the source will emit updates + as soon as they become available. + + An updateTimeout() signal will be emitted if this QGeoPositionInfoSource subclass determines + that it will not be able to provide regular updates. This could happen if a satellite fix is + lost or if a hardware error is detected. Position updates will recommence if the data becomes + available later on. The updateTimeout() signal will not be emitted again until after the + periodic updates resume. + + On iOS, starting from version 8, Core Location framework requires additional + entries in the application's Info.plist with keys NSLocationAlwaysUsageDescription or + NSLocationWhenInUseUsageDescription and a string to be displayed in the authorization prompt. + The key NSLocationWhenInUseUsageDescription is used when requesting permission + to use location services while the app is in the foreground. + The key NSLocationAlwaysUsageDescription is used when requesting permission + to use location services whenever the app is running (both the foreground and the background). + If both entries are defined, NSLocationWhenInUseUsageDescription has a priority in the + foreground mode. +*/ + +/*! + \fn virtual void QGeoPositionInfoSource::stopUpdates() = 0; + + Stops emitting updates at regular intervals. +*/ + +/*! + \fn virtual void QGeoPositionInfoSource::requestUpdate(int timeout = 0); + + Attempts to get the current position and emit positionUpdated() with + this information. If the current position cannot be found within the given \a timeout + (in milliseconds) or if \a timeout is less than the value returned by + minimumUpdateInterval(), updateTimeout() is emitted. + + If the timeout is zero, the timeout defaults to a reasonable timeout + period as appropriate for the source. + + This does nothing if another update request is in progress. However + it can be called even if startUpdates() has already been called and + regular updates are in progress. + + If the source uses multiple positioning methods, it tries to get the + current position from the most accurate positioning method within the + given timeout. +*/ + +/*! + \fn virtual QGeoPositionInfoSource::Error QGeoPositionInfoSource::error() const; + + Returns the type of error that last occurred. + +*/ + +/*! + \fn void QGeoPositionInfoSource::positionUpdated(const QGeoPositionInfo &update); + + If startUpdates() or requestUpdate() is called, this signal is emitted + when an update becomes available. + + The \a update value holds the value of the new update. +*/ + +/*! + \fn void QGeoPositionInfoSource::updateTimeout(); + + If requestUpdate() was called, this signal will be emitted if the current position could not + be retrieved within the specified timeout. + + If startUpdates() has been called, this signal will be emitted if this QGeoPositionInfoSource + subclass determines that it will not be able to provide further regular updates. This signal + will not be emitted again until after the regular updates resume. + + While the triggering of this signal may be considered an error condition, it does not + imply the emission of the \c error() signal. Only the emission of \c updateTimeout() is required + to indicate a timeout. +*/ + +/*! + \fn void QGeoPositionInfoSource::error(QGeoPositionInfoSource::Error positioningError) + + This signal is emitted after an error occurred. The \a positioningError + parameter describes the type of error that occurred. + + This signal is not emitted when an updateTimeout() has occurred. + +*/ + +/*! + \enum QGeoPositionInfoSource::Error + + The Error enumeration represents the errors which can occur. + + \value AccessError The connection setup to the remote positioning backend failed because the + application lacked the required privileges. + \value ClosedError The remote positioning backend closed the connection, which happens for example in case + the user is switching location services to off. As soon as the location service is re-enabled + regular updates will resume. + \value NoError No error has occurred. + \value UnknownSourceError An unidentified error occurred. + */ + +#include "moc_qgeopositioninfosource.cpp" + +QT_END_NAMESPACE diff --git a/src/positioning/qgeopositioninfosource.h b/src/positioning/qgeopositioninfosource.h new file mode 100644 index 0000000..2478446 --- /dev/null +++ b/src/positioning/qgeopositioninfosource.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOPOSITIONINFOSOURCE_H +#define QGEOPOSITIONINFOSOURCE_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QGeoPositionInfoSourcePrivate; +class Q_POSITIONING_EXPORT QGeoPositionInfoSource : public QObject +{ + Q_OBJECT + Q_PROPERTY(int updateInterval READ updateInterval WRITE setUpdateInterval) + Q_PROPERTY(int minimumUpdateInterval READ minimumUpdateInterval) + Q_PROPERTY(QString sourceName READ sourceName) + +public: + enum Error { + AccessError = 0, + ClosedError = 1, + UnknownSourceError = 2, + NoError = 3 + }; + Q_ENUMS(Error) + + enum PositioningMethod { + NoPositioningMethods = 0x00000000, + SatellitePositioningMethods = 0x000000ff, + NonSatellitePositioningMethods = 0xffffff00, + AllPositioningMethods = 0xffffffff + }; + Q_DECLARE_FLAGS(PositioningMethods, PositioningMethod) + + explicit QGeoPositionInfoSource(QObject *parent); + virtual ~QGeoPositionInfoSource(); + + virtual void setUpdateInterval(int msec); + int updateInterval() const; + + virtual void setPreferredPositioningMethods(PositioningMethods methods); + PositioningMethods preferredPositioningMethods() const; + + virtual QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const = 0; + + virtual PositioningMethods supportedPositioningMethods() const = 0; + virtual int minimumUpdateInterval() const = 0; + + QString sourceName() const; + + static QGeoPositionInfoSource *createDefaultSource(QObject *parent); + static QGeoPositionInfoSource *createSource(const QString &sourceName, QObject *parent); + static QStringList availableSources(); + virtual Error error() const = 0; + +public Q_SLOTS: + virtual void startUpdates() = 0; + virtual void stopUpdates() = 0; + + virtual void requestUpdate(int timeout = 0) = 0; + +Q_SIGNALS: + void positionUpdated(const QGeoPositionInfo &update); + void updateTimeout(); + void error(QGeoPositionInfoSource::Error); + +private: + Q_DISABLE_COPY(QGeoPositionInfoSource) + QGeoPositionInfoSourcePrivate *d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoPositionInfoSource::PositioningMethods) + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeopositioninfosource_p.h b/src/positioning/qgeopositioninfosource_p.h new file mode 100644 index 0000000..32fd23e --- /dev/null +++ b/src/positioning/qgeopositioninfosource_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCE_P_H +#define QGEOPOSITIONINFOSOURCE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeopositioninfosource.h" +#include "qgeopositioninfosourcefactory.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoPositionInfoSourcePrivate +{ +public: + int interval; + QGeoPositionInfoSource::PositioningMethods methods; + QJsonObject metaData; + QGeoPositionInfoSourceFactory *factory; + QString providerName; + + void loadMeta(); + void loadPlugin(); + + static QHash plugins(bool reload = false); + static void loadPluginMetadata(QHash &list); + static QList pluginsSorted(); +}; + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFOSOURCE_P_H diff --git a/src/positioning/qgeopositioninfosourcefactory.cpp b/src/positioning/qgeopositioninfosourcefactory.cpp new file mode 100644 index 0000000..6c6e9c7 --- /dev/null +++ b/src/positioning/qgeopositioninfosourcefactory.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosourcefactory.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoPositionInfoSourceFactory + \inmodule QtPositioning + \since 5.2 + + \brief The QGeoPositionInfoSourceFactory class is a factory class used + as the plugin interface for external providers of positioning data. + + The other functions must be overridden by all plugins, other than + sourcePriority() which defaults to returning 0. Higher values of + priority will be preferred to lower ones. +*/ + +/*! + \fn QGeoPositionInfoSource *QGeoPositionInfoSourceFactory::positionInfoSource(QObject *parent) + + Returns a new QGeoPositionInfoSource associated with this plugin + with parent \a parent. Can also return 0, in which case the plugin + loader will use the factory with the next highest priority. + */ + +/*! + \fn QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactory::satelliteInfoSource(QObject *parent) + + Returns a new QGeoSatelliteInfoSource associated with this plugin + with parent \a parent. Can also return 0, in which case the plugin + loader will use the factory with the next highest priority. + */ + +/*! + \fn QGeoAreaMonitorSource *QGeoPositionInfoSourceFactory::areaMonitor(QObject *parent); + + Returns a new QGeoAreaMonitorSource associated with this plugin with parent \a parent. + Can also return 0, in which case the plugin loader will use the factory with the + next highest priority. + */ + +/*! + Destroys the position info source factory. +*/ +QGeoPositionInfoSourceFactory::~QGeoPositionInfoSourceFactory() +{} + +QT_END_NAMESPACE diff --git a/src/positioning/qgeopositioninfosourcefactory.h b/src/positioning/qgeopositioninfosourcefactory.h new file mode 100644 index 0000000..b30aaf7 --- /dev/null +++ b/src/positioning/qgeopositioninfosourcefactory.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCEFACTORY_H +#define QGEOPOSITIONINFOSOURCEFACTORY_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_POSITIONING_EXPORT QGeoPositionInfoSourceFactory +{ +public: + virtual ~QGeoPositionInfoSourceFactory(); + + virtual QGeoPositionInfoSource *positionInfoSource(QObject *parent) = 0; + virtual QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent) = 0; + virtual QGeoAreaMonitorSource *areaMonitor(QObject *parent) = 0; +}; + +#define QT_POSITION_SOURCE_INTERFACE +Q_DECLARE_INTERFACE(QGeoPositionInfoSourceFactory, + "org.qt-project.qt.position.sourcefactory/5.0") + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFOSOURCEFACTORY_H diff --git a/src/positioning/qgeoprojection.cpp b/src/positioning/qgeoprojection.cpp new file mode 100644 index 0000000..507f080 --- /dev/null +++ b/src/positioning/qgeoprojection.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeoprojection_p.h" + +#include "qgeocoordinate.h" + +#include + +#include + +#include "qdoublevector2d_p.h" +#include "qdoublevector3d_p.h" + +QT_BEGIN_NAMESPACE + +QDoubleVector2D QGeoProjection::coordToMercator(const QGeoCoordinate &coord) +{ + const double pi = M_PI; + + double lon = coord.longitude() / 360.0 + 0.5; + + double lat = coord.latitude(); + lat = 0.5 - (std::log(std::tan((pi / 4.0) + (pi / 2.0) * lat / 180.0)) / pi) / 2.0; + lat = qBound(0.0, lat, 1.0); + + return QDoubleVector2D(lon, lat); +} + +double QGeoProjection::realmod(const double a, const double b) +{ + quint64 div = static_cast(a / b); + return a - static_cast(div) * b; +} + +QGeoCoordinate QGeoProjection::mercatorToCoord(const QDoubleVector2D &mercator) +{ + const double pi = M_PI; + + double fx = mercator.x(); + double fy = mercator.y(); + + if (fy < 0.0) + fy = 0.0; + else if (fy > 1.0) + fy = 1.0; + + double lat; + + if (fy == 0.0) + lat = 90.0; + else if (fy == 1.0) + lat = -90.0; + else + lat = (180.0 / pi) * (2.0 * std::atan(std::exp(pi * (1.0 - 2.0 * fy))) - (pi / 2.0)); + + double lng; + if (fx >= 0) { + lng = realmod(fx, 1.0); + } else { + lng = realmod(1.0 - realmod(-1.0 * fx, 1.0), 1.0); + } + + lng = lng * 360.0 - 180.0; + + return QGeoCoordinate(lat, lng, 0.0); +} + +QGeoCoordinate QGeoProjection::coordinateInterpolation(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress) +{ + QDoubleVector2D s = QGeoProjection::coordToMercator(from); + QDoubleVector2D e = QGeoProjection::coordToMercator(to); + + double x = s.x(); + + if (0.5 < qAbs(e.x() - s.x())) { + // handle dateline crossing + double ex = e.x(); + double sx = s.x(); + if (ex < sx) + sx -= 1.0; + else if (sx < ex) + ex -= 1.0; + + x = (1.0 - progress) * sx + progress * ex; + + if (!qFuzzyIsNull(x) && (x < 0.0)) + x += 1.0; + + } else { + x = (1.0 - progress) * s.x() + progress * e.x(); + } + + double y = (1.0 - progress) * s.y() + progress * e.y(); + + QGeoCoordinate result = QGeoProjection::mercatorToCoord(QDoubleVector2D(x, y)); + result.setAltitude((1.0 - progress) * from.altitude() + progress * to.altitude()); + + return result; +} + +QT_END_NAMESPACE diff --git a/src/positioning/qgeoprojection_p.h b/src/positioning/qgeoprojection_p.h new file mode 100644 index 0000000..b80678b --- /dev/null +++ b/src/positioning/qgeoprojection_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOPROJECTION_P_H +#define QGEOPROJECTION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + +#include +#include +#include "qpositioningglobal.h" + +QT_BEGIN_NAMESPACE + +class QGeoCoordinate; +class QDoubleVector2D; + +class Q_POSITIONING_EXPORT QGeoProjection +{ +public: + static QDoubleVector2D coordToMercator(const QGeoCoordinate &coord); + static QGeoCoordinate mercatorToCoord(const QDoubleVector2D &mercator); + static QGeoCoordinate coordinateInterpolation(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress); + +private: + static double realmod(const double a, const double b); +}; + +QT_END_NAMESPACE + +#endif // QGEOPROJECTION_P_H diff --git a/src/positioning/qgeorectangle.cpp b/src/positioning/qgeorectangle.cpp new file mode 100644 index 0000000..bbcafd6 --- /dev/null +++ b/src/positioning/qgeorectangle.cpp @@ -0,0 +1,1063 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeorectangle.h" +#include "qgeorectangle_p.h" + +#include "qgeocoordinate.h" +#include "qnumeric.h" +#include +QT_BEGIN_NAMESPACE + +/*! + \class QGeoRectangle + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QGeoRectangle class defines a rectangular geographic area. + + The rectangle is defined in terms of a QGeoCoordinate which specifies the + top left coordinate of the rectangle and a QGeoCoordinate which specifies + the bottom right coordinate of the rectangle. + + A geo rectangle is considered invalid if the top left or bottom right + coordinates are invalid or if the top left coordinate is south of the + bottom right coordinate. + + Geo rectangles can never cross the poles. + + Several methods behave as though the geo rectangle is defined in terms of a + center coordinate, the width of the geo rectangle in degrees and the height + of the geo rectangle in degrees. + + If the height or center of a geo rectangle is adjusted such that it would + cross one of the poles the height is modified such that the geo rectangle + touches but does not cross the pole and that the center coordinate is still + in the center of the geo rectangle. + + This class is a \l Q_GADGET since Qt 5.5. It can be + \l{Cpp_value_integration_positioning}{directly used from C++ and QML}. +*/ + +/*! + \property QGeoRectangle::bottomLeft + \brief This property holds the bottom left coorindate of this geo rectangle. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoRectangle::bottomRight + \brief This property holds the bottom right coordinate of this geo rectangle. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoRectangle::topLeft + \brief This property holds the top left coordinate of this geo rectangle. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoRectangle::topRight + \brief This property holds the top right coordinate of this geo rectangle. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoRectangle::center + \brief This property holds the center of this geo rectangle. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \sa QGeoShape::center + + \since 5.5 +*/ + +/*! + \property QGeoRectangle::width + \brief This property holds the width of this geo rectangle in degrees. + + The property value is undefined if this geo rectangle is invalid. + + If the new width is less than 0.0 or if this geo rectangle is invalid, this + function does nothing. To set up the values of an invalid + geo rectangle based on the center, width, and height, you should use + \l setCenter() first to make the geo rectangle valid. + + 360.0 is the width used only if the new width is equal or greater than 360. + In such cases the leftmost longitude of the geo rectangle is set to -180.0 + degrees and the rightmost longitude of the geo rectangle is set to 180.0 + degrees. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoRectangle::height + \brief This property holds the height of this geo rectangle in degrees. + + The property value is undefined if this geo rectangle is invalid. + + If the new height is less than 0.0 or if this geo rectangle is invalid, + the property is not changed. To set up the values of an invalid + geo rectangle based on the center, width, and height, you should use + \l setCenter() first to make the geo rectangle valid. + + If the change in height would cause the geo rectangle to cross a pole, + the height is adjusted such that the geo rectangle only touches the pole. + + This change is done such that the center coordinate is still at the + center of the geo rectangle, which may result in a geo rectangle with + a smaller height than expected. + + 180.0 is the height used only if the new height is greater or equal than 180. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +inline QGeoRectanglePrivate *QGeoRectangle::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QGeoRectanglePrivate *QGeoRectangle::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +struct RectangleVariantConversions +{ + RectangleVariantConversions() + { + QMetaType::registerConverter(); + QMetaType::registerConverter(); + } +}; + + +Q_GLOBAL_STATIC(RectangleVariantConversions, initRectangleConversions) + +/*! + Constructs a new, invalid geo rectangle. +*/ +QGeoRectangle::QGeoRectangle() +: QGeoShape(new QGeoRectanglePrivate) +{ + initRectangleConversions(); +} + +/*! + Constructs a new geo rectangle centered at \a center with a + width in degrees of \a degreesWidth and a height in degrees of \a degreesHeight. + + If \a degreesHeight would take the geo rectangle beyond one of the poles, + the height of the geo rectangle will be truncated such that the geo rectangle + only extends up to the pole. The center of the geo rectangle will be + unchanged, and the height will be adjusted such that the center point is at + the center of the truncated geo rectangle. +*/ +QGeoRectangle::QGeoRectangle(const QGeoCoordinate ¢er, double degreesWidth, double degreesHeight) +{ + initRectangleConversions(); + d_ptr = new QGeoRectanglePrivate(center, center); + setWidth(degreesWidth); + setHeight(degreesHeight); +} + +/*! + Constructs a new geo rectangle with a top left coordinate \a topLeft and a bottom right + coordinate \a bottomRight. +*/ +QGeoRectangle::QGeoRectangle(const QGeoCoordinate &topLeft, const QGeoCoordinate &bottomRight) +{ + initRectangleConversions(); + d_ptr = new QGeoRectanglePrivate(topLeft, bottomRight); +} + +/*! + Constructs a new geo rectangle, of minimum size, containing all of the \a coordinates. +*/ +QGeoRectangle::QGeoRectangle(const QList &coordinates) +{ + initRectangleConversions(); + if (coordinates.isEmpty()) { + d_ptr = new QGeoRectanglePrivate; + } else { + const QGeoCoordinate &startCoordinate = coordinates.first(); + d_ptr = new QGeoRectanglePrivate(startCoordinate, startCoordinate); + + foreach (const QGeoCoordinate &coordinate, coordinates) { + d_ptr->extendShape(coordinate); + } + } +} + +/*! + Constructs a geo rectangle from the contents of \a other. +*/ +QGeoRectangle::QGeoRectangle(const QGeoRectangle &other) +: QGeoShape(other) +{ + initRectangleConversions(); +} + +/*! + Constructs a geo rectangle from the contents of \a other. +*/ +QGeoRectangle::QGeoRectangle(const QGeoShape &other) +: QGeoShape(other) +{ + initRectangleConversions(); + if (type() != QGeoShape::RectangleType) + d_ptr = new QGeoRectanglePrivate; +} + +/*! + Destroys this geo rectangle. +*/ +QGeoRectangle::~QGeoRectangle() +{ +} + +/*! + Assigns \a other to this geo rectangle and returns a reference to this geo rectangle. +*/ +QGeoRectangle &QGeoRectangle::operator=(const QGeoRectangle &other) +{ + QGeoShape::operator=(other); + return *this; +} + +/*! + Returns whether this geo rectangle is equal to \a other. +*/ +bool QGeoRectangle::operator==(const QGeoRectangle &other) const +{ + Q_D(const QGeoRectangle); + + return *d == *other.d_func(); +} + +/*! + Returns whether this geo rectangle is not equal to \a other. +*/ +bool QGeoRectangle::operator!=(const QGeoRectangle &other) const +{ + Q_D(const QGeoRectangle); + + return !(*d == *other.d_func()); +} + +bool QGeoRectanglePrivate::isValid() const +{ + return topLeft.isValid() && bottomRight.isValid() && + topLeft.latitude() >= bottomRight.latitude(); +} + +bool QGeoRectanglePrivate::isEmpty() const +{ + if (!isValid()) + return true; + + return topLeft.latitude() == bottomRight.latitude() || + topLeft.longitude() == bottomRight.longitude(); +} + +/*! + Sets the top left coordinate of this geo rectangle to \a topLeft. +*/ +void QGeoRectangle::setTopLeft(const QGeoCoordinate &topLeft) +{ + Q_D(QGeoRectangle); + + d->topLeft = topLeft; +} + +/*! + Returns the top left coordinate of this geo rectangle. +*/ +QGeoCoordinate QGeoRectangle::topLeft() const +{ + Q_D(const QGeoRectangle); + + return d->topLeft; +} + +/*! + Sets the top right coordinate of this geo rectangle to \a topRight. +*/ +void QGeoRectangle::setTopRight(const QGeoCoordinate &topRight) +{ + Q_D(QGeoRectangle); + + d->topLeft.setLatitude(topRight.latitude()); + d->bottomRight.setLongitude(topRight.longitude()); +} + +/*! + Returns the top right coordinate of this geo rectangle. +*/ +QGeoCoordinate QGeoRectangle::topRight() const +{ + // TODO remove? + if (!isValid()) + return QGeoCoordinate(); + + Q_D(const QGeoRectangle); + + return QGeoCoordinate(d->topLeft.latitude(), d->bottomRight.longitude()); +} + +/*! + Sets the bottom left coordinate of this geo rectangle to \a bottomLeft. +*/ +void QGeoRectangle::setBottomLeft(const QGeoCoordinate &bottomLeft) +{ + Q_D(QGeoRectangle); + + d->bottomRight.setLatitude(bottomLeft.latitude()); + d->topLeft.setLongitude(bottomLeft.longitude()); +} + +/*! + Returns the bottom left coordinate of this geo rectangle. +*/ +QGeoCoordinate QGeoRectangle::bottomLeft() const +{ + // TODO remove? + if (!isValid()) + return QGeoCoordinate(); + + Q_D(const QGeoRectangle); + + return QGeoCoordinate(d->bottomRight.latitude(), d->topLeft.longitude()); +} + +/*! + Sets the bottom right coordinate of this geo rectangle to \a bottomRight. +*/ +void QGeoRectangle::setBottomRight(const QGeoCoordinate &bottomRight) +{ + Q_D(QGeoRectangle); + + d->bottomRight = bottomRight; +} + +/*! + Returns the bottom right coordinate of this geo rectangle. +*/ +QGeoCoordinate QGeoRectangle::bottomRight() const +{ + Q_D(const QGeoRectangle); + + return d->bottomRight; +} + +/*! + Sets the center of this geo rectangle to \a center. + + If this causes the geo rectangle to cross on of the poles the height of the + geo rectangle will be truncated such that the geo rectangle only extends up + to the pole. The center of the geo rectangle will be unchanged, and the + height will be adjusted such that the center point is at the center of the + truncated geo rectangle. + +*/ +void QGeoRectangle::setCenter(const QGeoCoordinate ¢er) +{ + Q_D(QGeoRectangle); + + if (!isValid()) { + d->topLeft = center; + d->bottomRight = center; + return; + } + double width = this->width(); + double height = this->height(); + + double tlLat = center.latitude() + height / 2.0; + double tlLon = center.longitude() - width / 2.0; + double brLat = center.latitude() - height / 2.0; + double brLon = center.longitude() + width / 2.0; + + if (tlLon < -180.0) + tlLon += 360.0; + if (tlLon > 180.0) + tlLon -= 360.0; + + if (brLon < -180.0) + brLon += 360.0; + if (brLon > 180.0) + brLon -= 360.0; + + if (tlLat > 90.0) { + brLat = 2 * center.latitude() - 90.0; + tlLat = 90.0; + } + + if (tlLat < -90.0) { + brLat = -90.0; + tlLat = -90.0; + } + + if (brLat > 90.0) { + tlLat = 90.0; + brLat = 90.0; + } + + if (brLat < -90.0) { + tlLat = 2 * center.latitude() + 90.0; + brLat = -90.0; + } + + if (width == 360.0) { + tlLon = -180.0; + brLon = 180.0; + } + + d->topLeft = QGeoCoordinate(tlLat, tlLon); + d->bottomRight = QGeoCoordinate(brLat, brLon); +} + +/*! + Returns the center of this geo rectangle. Equivalent to QGeoShape::center(). +*/ +QGeoCoordinate QGeoRectangle::center() const +{ + Q_D(const QGeoRectangle); + + return d->center(); +} + +/*! + Sets the width of this geo rectangle in degrees to \a degreesWidth. +*/ +void QGeoRectangle::setWidth(double degreesWidth) +{ + if (!isValid()) + return; + + if (degreesWidth < 0.0) + return; + + Q_D(QGeoRectangle); + + if (degreesWidth >= 360.0) { + d->topLeft.setLongitude(-180.0); + d->bottomRight.setLongitude(180.0); + return; + } + + double tlLat = d->topLeft.latitude(); + double brLat = d->bottomRight.latitude(); + + QGeoCoordinate c = center(); + + double tlLon = c.longitude() - degreesWidth / 2.0; + + if (tlLon < -180.0) + tlLon += 360.0; + if (tlLon > 180.0) + tlLon -= 360.0; + + double brLon = c.longitude() + degreesWidth / 2.0; + + if (brLon < -180.0) + brLon += 360.0; + if (brLon > 180.0) + brLon -= 360.0; + + d->topLeft = QGeoCoordinate(tlLat, tlLon); + d->bottomRight = QGeoCoordinate(brLat, brLon); +} + +/*! + Returns the width of this geo rectangle in degrees. + + The return value is undefined if this geo rectangle is invalid. +*/ +double QGeoRectangle::width() const +{ + if (!isValid()) + return qQNaN(); + + Q_D(const QGeoRectangle); + + double result = d->bottomRight.longitude() - d->topLeft.longitude(); + if (result < 0.0) + result += 360.0; + if (result > 360.0) + result -= 360.0; + + return result; +} + +/*! + Sets the height of this geo rectangle in degrees to \a degreesHeight. +*/ +void QGeoRectangle::setHeight(double degreesHeight) +{ + if (!isValid()) + return; + + if (degreesHeight < 0.0) + return; + + if (degreesHeight >= 180.0) { + degreesHeight = 180.0; + } + + Q_D(QGeoRectangle); + + double tlLon = d->topLeft.longitude(); + double brLon = d->bottomRight.longitude(); + + QGeoCoordinate c = center(); + + double tlLat = c.latitude() + degreesHeight / 2.0; + double brLat = c.latitude() - degreesHeight / 2.0; + + if (tlLat > 90.0) { + brLat = 2* c.latitude() - 90.0; + tlLat = 90.0; + } + + if (tlLat < -90.0) { + brLat = -90.0; + tlLat = -90.0; + } + + if (brLat > 90.0) { + tlLat = 90.0; + brLat = 90.0; + } + + if (brLat < -90.0) { + tlLat = 2 * c.latitude() + 90.0; + brLat = -90.0; + } + + d->topLeft = QGeoCoordinate(tlLat, tlLon); + d->bottomRight = QGeoCoordinate(brLat, brLon); +} + +/*! + Returns the height of this geo rectangle in degrees. + + The return value is undefined if this geo rectangle is invalid. +*/ +double QGeoRectangle::height() const +{ + if (!isValid()) + return qQNaN(); + + Q_D(const QGeoRectangle); + + double result = d->topLeft.latitude() - d->bottomRight.latitude(); + if (result < 0.0) + result = qQNaN(); + return result; +} + +bool QGeoRectanglePrivate::contains(const QGeoCoordinate &coordinate) const +{ + if (!isValid() || !coordinate.isValid()) + return false; + + double left = topLeft.longitude(); + double right = bottomRight.longitude(); + double top = topLeft.latitude(); + double bottom = bottomRight.latitude(); + + double lon = coordinate.longitude(); + double lat = coordinate.latitude(); + + if (lat > top) + return false; + if (lat < bottom) + return false; + + if ((lat == 90.0) && (top == 90.0)) + return true; + + if ((lat == -90.0) && (bottom == -90.0)) + return true; + + if (left <= right) { + if ((lon < left) || (lon > right)) + return false; + } else { + if ((lon < left) && (lon > right)) + return false; + } + + return true; +} + +QGeoCoordinate QGeoRectanglePrivate::center() const +{ + if (!isValid()) + return QGeoCoordinate(); + + double cLat = (topLeft.latitude() + bottomRight.latitude()) / 2.0; + double cLon = (bottomRight.longitude() + topLeft.longitude()) / 2.0; + + if (topLeft.longitude() > bottomRight.longitude()) + cLon = cLon - 180.0; + + if (cLon < -180.0) + cLon += 360.0; + if (cLon > 180.0) + cLon -= 360.0; + + return QGeoCoordinate(cLat, cLon); +} + +/*! + Returns whether the geo rectangle \a rectangle is contained within this + geo rectangle. +*/ +bool QGeoRectangle::contains(const QGeoRectangle &rectangle) const +{ + Q_D(const QGeoRectangle); + + return (d->contains(rectangle.topLeft()) + && d->contains(rectangle.topRight()) + && d->contains(rectangle.bottomLeft()) + && d->contains(rectangle.bottomRight())); +} + +/*! + Returns whether the geo rectangle \a rectangle intersects this geo rectangle. + + If the top or bottom edges of both geo rectangles are at one of the poles + the geo rectangles are considered to be intersecting, since the longitude + is irrelevant when the edges are at the pole. +*/ +bool QGeoRectangle::intersects(const QGeoRectangle &rectangle) const +{ + Q_D(const QGeoRectangle); + + double left1 = d->topLeft.longitude(); + double right1 = d->bottomRight.longitude(); + double top1 = d->topLeft.latitude(); + double bottom1 = d->bottomRight.latitude(); + + double left2 = rectangle.d_func()->topLeft.longitude(); + double right2 = rectangle.d_func()->bottomRight.longitude(); + double top2 = rectangle.d_func()->topLeft.latitude(); + double bottom2 = rectangle.d_func()->bottomRight.latitude(); + + if (top1 < bottom2) + return false; + + if (bottom1 > top2) + return false; + + if ((top1 == 90.0) && (top1 == top2)) + return true; + + if ((bottom1 == -90.0) && (bottom1 == bottom2)) + return true; + + if (left1 < right1) { + if (left2 < right2) { + if ((left1 > right2) || (right1 < left2)) + return false; + } else { + if ((left1 > right2) && (right1 < left2)) + return false; + } + } else { + if (left2 < right2) { + if ((left2 > right1) && (right2 < left1)) + return false; + } else { + // if both wrap then they have to intersect + } + } + + return true; +} + +/*! + Translates this geo rectangle by \a degreesLatitude northwards and \a + degreesLongitude eastwards. + + Negative values of \a degreesLatitude and \a degreesLongitude correspond to + southward and westward translation respectively. + + If the translation would have caused the geo rectangle to cross a pole the + geo rectangle will be translated until the top or bottom edge of the geo rectangle + touches the pole but not further. +*/ +void QGeoRectangle::translate(double degreesLatitude, double degreesLongitude) +{ + // TODO handle dlat, dlon larger than 360 degrees + + Q_D(QGeoRectangle); + + double tlLat = d->topLeft.latitude(); + double tlLon = d->topLeft.longitude(); + double brLat = d->bottomRight.latitude(); + double brLon = d->bottomRight.longitude(); + + if ((tlLat != 90.0) || (brLat != -90.0)) { + tlLat += degreesLatitude; + brLat += degreesLatitude; + } + + if ( (tlLon != -180.0) || (brLon != 180.0) ) { + tlLon += degreesLongitude; + brLon += degreesLongitude; + } + + if (tlLon < -180.0) + tlLon += 360.0; + if (tlLon > 180.0) + tlLon -= 360.0; + + if (brLon < -180.0) + brLon += 360.0; + if (brLon > 180.0) + brLon -= 360.0; + + if (tlLat > 90.0) + tlLat = 90.0; + + if (tlLat < -90.0) + tlLat = -90.0; + + if (brLat > 90.0) + brLat = 90.0; + + if (brLat < -90.0) + brLat = -90.0; + + d->topLeft = QGeoCoordinate(tlLat, tlLon); + d->bottomRight = QGeoCoordinate(brLat, brLon); +} + +/*! + Returns a copy of this geo rectangle translated by \a degreesLatitude northwards and \a + degreesLongitude eastwards. + + Negative values of \a degreesLatitude and \a degreesLongitude correspond to + southward and westward translation respectively. + + \sa translate() +*/ +QGeoRectangle QGeoRectangle::translated(double degreesLatitude, double degreesLongitude) const +{ + QGeoRectangle result(*this); + result.translate(degreesLatitude, degreesLongitude); + return result; +} + +/*! + Returns the smallest geo rectangle which contains both this geo rectangle and \a rectangle. + + If the centers of the two geo rectangles are separated by exactly 180.0 degrees then the + width is set to 360.0 degrees with the leftmost longitude set to -180.0 degrees and the + rightmost longitude set to 180.0 degrees. This is done to ensure that the result is + independent of the order of the operands. + +*/ +QGeoRectangle QGeoRectangle::united(const QGeoRectangle &rectangle) const +{ + QGeoRectangle result(*this); + result |= rectangle; + return result; +} + +/*! + Extends the rectangle in the smallest possible way to include \a coordinate in + the shape. + + Both the rectangle and coordinate needs to be valid. If the rectangle already covers + the coordinate noting happens. + +*/ +void QGeoRectanglePrivate::extendShape(const QGeoCoordinate &coordinate) +{ + if (!isValid() || !coordinate.isValid() || contains(coordinate)) + return; + + double left = topLeft.longitude(); + double right = bottomRight.longitude(); + double top = topLeft.latitude(); + double bottom = bottomRight.latitude(); + + double inputLat = coordinate.latitude(); + double inputLon = coordinate.longitude(); + + top = qMax(top, inputLat); + bottom = qMin(bottom, inputLat); + + bool wrap = left > right; + + if (wrap && inputLon > right && inputLon < left) { + if (qAbs(left - inputLon) < qAbs(right - inputLon)) + left = inputLon; + else + right = inputLon; + } else if (!wrap) { + if (inputLon < left) { + if (360 - (right - inputLon) < left - inputLon) + right = inputLon; + else + left = inputLon; + } else if (inputLon > right) { + if (360 - (inputLon - left) < inputLon - right) + left = inputLon; + else + right = inputLon; + } + } + topLeft = QGeoCoordinate(top, left); + bottomRight = QGeoCoordinate(bottom, right); +} + +/*! + \fn QGeoRectangle QGeoRectangle::operator|(const QGeoRectangle &rectangle) const + + Returns the smallest geo rectangle which contains both this geo rectangle and \a rectangle. + + If the centers of the two geo rectangles are separated by exactly 180.0 degrees then the + width is set to 360.0 degrees with the leftmost longitude set to -180.0 degrees and the + rightmost longitude set to 180.0 degrees. This is done to ensure that the result is + independent of the order of the operands. + +*/ + +/*! + Returns the smallest geo rectangle which contains both this geo rectangle and \a rectangle. + + If the centers of the two geo rectangles are separated by exactly 180.0 degrees then the + width is set to 360.0 degrees with the leftmost longitude set to -180.0 degrees and the + rightmost longitude set to 180.0 degrees. This is done to ensure that the result is + independent of the order of the operands. + +*/ +QGeoRectangle &QGeoRectangle::operator|=(const QGeoRectangle &rectangle) +{ + // If non-intersecting goes for most narrow box + + Q_D(QGeoRectangle); + + double left1 = d->topLeft.longitude(); + double right1 = d->bottomRight.longitude(); + double top1 = d->topLeft.latitude(); + double bottom1 = d->bottomRight.latitude(); + + double left2 = rectangle.d_func()->topLeft.longitude(); + double right2 = rectangle.d_func()->bottomRight.longitude(); + double top2 = rectangle.d_func()->topLeft.latitude(); + double bottom2 = rectangle.d_func()->bottomRight.latitude(); + + double top = qMax(top1, top2); + double bottom = qMin(bottom1, bottom2); + + double left = 0.0; + double right = 0.0; + + bool wrap1 = (left1 > right1); + bool wrap2 = (left2 > right2); + + if ((wrap1 && wrap2) || (!wrap1 && !wrap2)) { + + double w = qAbs((left1 + right1 - left2 - right2) / 2.0); + + if (w < 180.0) { + left = qMin(left1, left2); + right = qMax(right1, right2); + } else if (w > 180.0) { + left = qMax(left1, left2); + right = qMin(right1, right2); + } else { + left = -180.0; + right = 180.0; + } + + } else { + double wrapLeft = 0.0; + double wrapRight = 0.0; + double nonWrapLeft = 0.0; + double nonWrapRight = 0.0; + + if (wrap1) { + wrapLeft = left1; + wrapRight = right1; + nonWrapLeft = left2; + nonWrapRight = right2; + } else { + wrapLeft = left2; + wrapRight = right2; + nonWrapLeft = left1; + nonWrapRight = right1; + } + + bool joinWrapLeft = (nonWrapRight >= wrapLeft); + bool joinWrapRight = (nonWrapLeft <= wrapRight); + + if (joinWrapLeft) { + if (joinWrapRight) { + left = -180.0; + right = 180.0; + } else { + left = nonWrapLeft; + right = wrapRight; + } + } else { + if (joinWrapRight) { + left = wrapLeft; + right = nonWrapRight; + } else { + double wrapRightDistance = nonWrapLeft - wrapRight; + double wrapLeftDistance = wrapLeft - nonWrapRight; + + if (wrapLeftDistance == wrapRightDistance) { + left = -180.0; + right = 180.0; + } else if (wrapLeftDistance < wrapRightDistance) { + left = nonWrapLeft; + right = wrapRight; + } else { + left = wrapLeft; + right = nonWrapRight; + } + } + } + } + + if (((left1 == -180) && (right1 == 180.0)) + || ((left2 == -180) && (right2 == 180.0))) { + left = -180; + right = 180; + } + + d->topLeft = QGeoCoordinate(top, left); + d->bottomRight = QGeoCoordinate(bottom, right); + + return *this; +} + +/*! + Returns the geo rectangle properties as a string. + + \since 5.5 +*/ +QString QGeoRectangle::toString() const +{ + if (type() != QGeoShape::RectangleType) { + qWarning("Not a rectangle a %d\n", type()); + return QStringLiteral("QGeoRectangle(not a rectangle)"); + } + + return QStringLiteral("QGeoRectangle({%1, %2}, {%3, %4})") + .arg(topLeft().latitude()) + .arg(topLeft().longitude()) + .arg(bottomRight().latitude()) + .arg(bottomRight().longitude()); +} + +/******************************************************************************* +*******************************************************************************/ + +QGeoRectanglePrivate::QGeoRectanglePrivate() +: QGeoShapePrivate(QGeoShape::RectangleType) +{ +} + +QGeoRectanglePrivate::QGeoRectanglePrivate(const QGeoCoordinate &topLeft, + const QGeoCoordinate &bottomRight) +: QGeoShapePrivate(QGeoShape::RectangleType), topLeft(topLeft), bottomRight(bottomRight) +{ +} + +QGeoRectanglePrivate::QGeoRectanglePrivate(const QGeoRectanglePrivate &other) +: QGeoShapePrivate(QGeoShape::RectangleType), topLeft(other.topLeft), + bottomRight(other.bottomRight) +{ +} + +QGeoRectanglePrivate::~QGeoRectanglePrivate() {} + +QGeoShapePrivate *QGeoRectanglePrivate::clone() const +{ + return new QGeoRectanglePrivate(*this); +} + +bool QGeoRectanglePrivate::operator==(const QGeoShapePrivate &other) const +{ + if (!QGeoShapePrivate::operator==(other)) + return false; + + const QGeoRectanglePrivate &otherBox = static_cast(other); + + return topLeft == otherBox.topLeft && bottomRight == otherBox.bottomRight; +} + +QT_END_NAMESPACE + diff --git a/src/positioning/qgeorectangle.h b/src/positioning/qgeorectangle.h new file mode 100644 index 0000000..7e92173 --- /dev/null +++ b/src/positioning/qgeorectangle.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEORECTANGLE_H +#define QGEORECTANGLE_H + +#include + +QT_BEGIN_NAMESPACE + +class QGeoCoordinate; +class QGeoRectanglePrivate; + +class Q_POSITIONING_EXPORT QGeoRectangle : public QGeoShape +{ + Q_GADGET + Q_PROPERTY(QGeoCoordinate bottomLeft READ bottomLeft WRITE setBottomLeft) + Q_PROPERTY(QGeoCoordinate bottomRight READ bottomRight WRITE setBottomRight) + Q_PROPERTY(QGeoCoordinate topLeft READ topLeft WRITE setTopLeft) + Q_PROPERTY(QGeoCoordinate topRight READ topRight WRITE setTopRight) + Q_PROPERTY(QGeoCoordinate center READ center WRITE setCenter) + Q_PROPERTY(double height READ height WRITE setHeight) + Q_PROPERTY(double width READ width WRITE setWidth) + +public: + QGeoRectangle(); + QGeoRectangle(const QGeoCoordinate ¢er, double degreesWidth, double degreesHeight); + QGeoRectangle(const QGeoCoordinate &topLeft, const QGeoCoordinate &bottomRight); + QGeoRectangle(const QList &coordinates); + QGeoRectangle(const QGeoRectangle &other); + QGeoRectangle(const QGeoShape &other); + + ~QGeoRectangle(); + + QGeoRectangle &operator=(const QGeoRectangle &other); + + using QGeoShape::operator==; + bool operator==(const QGeoRectangle &other) const; + + using QGeoShape::operator!=; + bool operator!=(const QGeoRectangle &other) const; + + void setTopLeft(const QGeoCoordinate &topLeft); + QGeoCoordinate topLeft() const; + + void setTopRight(const QGeoCoordinate &topRight); + QGeoCoordinate topRight() const; + + void setBottomLeft(const QGeoCoordinate &bottomLeft); + QGeoCoordinate bottomLeft() const; + + void setBottomRight(const QGeoCoordinate &bottomRight); + QGeoCoordinate bottomRight() const; + + void setCenter(const QGeoCoordinate ¢er); + QGeoCoordinate center() const; + + void setWidth(double degreesWidth); + double width() const; + + void setHeight(double degreesHeight); + double height() const; + + using QGeoShape::contains; + bool contains(const QGeoRectangle &rectangle) const; + bool intersects(const QGeoRectangle &rectangle) const; + + void translate(double degreesLatitude, double degreesLongitude); + QGeoRectangle translated(double degreesLatitude, double degreesLongitude) const; + + QGeoRectangle united(const QGeoRectangle &rectangle) const; + QGeoRectangle operator|(const QGeoRectangle &rectangle) const; + QGeoRectangle &operator|=(const QGeoRectangle &rectangle); + + Q_INVOKABLE QString toString() const; + +private: + inline QGeoRectanglePrivate *d_func(); + inline const QGeoRectanglePrivate *d_func() const; +}; + +Q_DECLARE_TYPEINFO(QGeoRectangle, Q_MOVABLE_TYPE); + +inline QGeoRectangle QGeoRectangle::operator|(const QGeoRectangle &rectangle) const +{ + return united(rectangle); +} + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoRectangle) + +#endif + diff --git a/src/positioning/qgeorectangle_p.h b/src/positioning/qgeorectangle_p.h new file mode 100644 index 0000000..188e554 --- /dev/null +++ b/src/positioning/qgeorectangle_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEORECTANGLE_P_H +#define QGEORECTANGLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgeoshape_p.h" +#include "qgeocoordinate.h" + +QT_BEGIN_NAMESPACE + +class QGeoRectanglePrivate : public QGeoShapePrivate +{ +public: + QGeoRectanglePrivate(); + QGeoRectanglePrivate(const QGeoCoordinate &topLeft, const QGeoCoordinate &bottomRight); + QGeoRectanglePrivate(const QGeoRectanglePrivate &other); + ~QGeoRectanglePrivate(); + + bool isValid() const Q_DECL_OVERRIDE; + bool isEmpty() const Q_DECL_OVERRIDE; + bool contains(const QGeoCoordinate &coordinate) const Q_DECL_OVERRIDE; + + QGeoCoordinate center() const Q_DECL_OVERRIDE; + + void extendShape(const QGeoCoordinate &coordinate) Q_DECL_OVERRIDE; + + QGeoShapePrivate *clone() const Q_DECL_OVERRIDE; + + bool operator==(const QGeoShapePrivate &other) const Q_DECL_OVERRIDE; + + QGeoCoordinate topLeft; + QGeoCoordinate bottomRight; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeosatelliteinfo.cpp b/src/positioning/qgeosatelliteinfo.cpp new file mode 100644 index 0000000..9168183 --- /dev/null +++ b/src/positioning/qgeosatelliteinfo.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeosatelliteinfo.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoSatelliteInfoPrivate +{ +public: + int signal; + int satId; + QGeoSatelliteInfo::SatelliteSystem system; + QHash doubleAttribs; +}; + + +/*! + \class QGeoSatelliteInfo + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QGeoSatelliteInfo class contains basic information about a satellite. + + \sa QGeoSatelliteInfoSource +*/ + +/*! + \enum QGeoSatelliteInfo::Attribute + Defines the attributes for the satellite information. + \value Elevation The elevation of the satellite, in degrees. + \value Azimuth The azimuth to true north, in degrees. +*/ + +/*! + \enum QGeoSatelliteInfo::SatelliteSystem + Defines the GNSS system of the satellite. + \value Undefined Not defined. + \value GPS Global Positioning System (USA). + \value GLONASS Global Positioning System (Russia). + +*/ + + +/*! + Creates a satellite information object. +*/ +QGeoSatelliteInfo::QGeoSatelliteInfo() + : d(new QGeoSatelliteInfoPrivate) +{ + d->signal = -1; + d->satId = -1; + d->system = QGeoSatelliteInfo::Undefined; +} + +/*! + Creates a satellite information object with the values of \a other. +*/ + +QGeoSatelliteInfo::QGeoSatelliteInfo(const QGeoSatelliteInfo &other) + : d(new QGeoSatelliteInfoPrivate) +{ + operator=(other); +} + +/*! + Destroys a satellite information object. +*/ +QGeoSatelliteInfo::~QGeoSatelliteInfo() +{ + delete d; +} + +/*! + Assigns the values from \a other to this object. +*/ +QGeoSatelliteInfo &QGeoSatelliteInfo::operator=(const QGeoSatelliteInfo & other) +{ + if (this == &other) + return *this; + + d->signal = other.d->signal; + d->satId = other.d->satId; + d->system = other.d->system; + d->doubleAttribs = other.d->doubleAttribs; + return *this; +} + +/*! + Returns true if all the information for this satellite + are the same as those of \a other. +*/ +bool QGeoSatelliteInfo::operator==(const QGeoSatelliteInfo &other) const +{ + return d->signal == other.d->signal + && d->satId == other.d->satId + && d->system == other.d->system + && d->doubleAttribs == other.d->doubleAttribs; +} + +/*! + \fn bool QGeoSatelliteInfo::operator!=(const QGeoSatelliteInfo &other) const; + + Returns true if any of the information for this satellite + are not the same as those of \a other. +*/ + + +/*! + Sets the Satellite System (GPS, GLONASS, ...) to \a system. +*/ +void QGeoSatelliteInfo::setSatelliteSystem(SatelliteSystem system) +{ + d->system = system; +} + +/*! + Returns the Satellite System (GPS, GLONASS, ...) +*/ +QGeoSatelliteInfo::SatelliteSystem QGeoSatelliteInfo::satelliteSystem() const +{ + return d->system; +} + +/*! + Sets the satellite identifier number to \a satId. + + The satellite identifier number can be used to identify a satellite inside the satellite system. + For satellite system GPS the satellite identifier number represents the PRN (Pseudo-random noise) number. + For satellite system GLONASS the satellite identifier number represents the slot number. +*/ +void QGeoSatelliteInfo::setSatelliteIdentifier(int satId) +{ + d->satId = satId; +} + +/*! + Returns the satellite identifier number. + + The satellite identifier number can be used to identify a satellite inside the satellite system. + For satellite system GPS the satellite identifier number represents the PRN (Pseudo-random noise) number. + For satellite system GLONASS the satellite identifier number represents the slot number. +*/ +int QGeoSatelliteInfo::satelliteIdentifier() const +{ + return d->satId; +} + +/*! + Sets the signal strength to \a signalStrength, in decibels. +*/ +void QGeoSatelliteInfo::setSignalStrength(int signalStrength) +{ + d->signal = signalStrength; +} + +/*! + Returns the signal strength, or -1 if the value has not been set. +*/ +int QGeoSatelliteInfo::signalStrength() const +{ + return d->signal; +} + +/*! + Sets the value for \a attribute to \a value. +*/ +void QGeoSatelliteInfo::setAttribute(Attribute attribute, qreal value) +{ + d->doubleAttribs[int(attribute)] = value; +} + +/*! + Returns the value of the specified \a attribute as a qreal value. + + Returns -1 if the value has not been set. + + \sa hasAttribute(), setAttribute() +*/ +qreal QGeoSatelliteInfo::attribute(Attribute attribute) const +{ + if (d->doubleAttribs.contains(int(attribute))) + return d->doubleAttribs[int(attribute)]; + return -1; +} + +/*! + Removes the specified \a attribute and its value. +*/ +void QGeoSatelliteInfo::removeAttribute(Attribute attribute) +{ + d->doubleAttribs.remove(int(attribute)); +} + +/*! + Returns true if the specified \a attribute is present in this update. +*/ +bool QGeoSatelliteInfo::hasAttribute(Attribute attribute) const +{ + return d->doubleAttribs.contains(int(attribute)); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QGeoSatelliteInfo &info) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QGeoSatelliteInfo(system=" << info.d->system; + dbg << ", satId=" << info.d->satId; + dbg << ", signal-strength=" << info.d->signal; + + + QList attribs = info.d->doubleAttribs.keys(); + for (int i = 0; i < attribs.count(); ++i) { + dbg << ", "; + switch (attribs[i]) { + case QGeoSatelliteInfo::Elevation: + dbg << "Elevation="; + break; + case QGeoSatelliteInfo::Azimuth: + dbg << "Azimuth="; + break; + } + dbg << info.d->doubleAttribs[attribs[i]]; + } + dbg << ')'; + return dbg; +} +#endif + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QGeoSatelliteInfo &info) + \relates QGeoSatelliteInfo + + Writes the given \a info to the specified \a stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink + +*/ + +QDataStream &operator<<(QDataStream &stream, const QGeoSatelliteInfo &info) +{ + stream << info.d->signal; + stream << info.d->doubleAttribs; + stream << info.d->satId; + stream << info.d->system; + return stream; +} +#endif + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator>>(QDataStream &stream, QGeoSatelliteInfo &info) + \relates QGeoSatelliteInfo + + Reads satellite information from the specified \a stream into the given + \a info. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ + +QDataStream &operator>>(QDataStream &stream, QGeoSatelliteInfo &info) +{ + int system; + stream >> info.d->signal; + stream >> info.d->doubleAttribs; + stream >> info.d->satId; + stream >> system; + info.d->system = (QGeoSatelliteInfo::SatelliteSystem)system; + return stream; +} +#endif + +QT_END_NAMESPACE diff --git a/src/positioning/qgeosatelliteinfo.h b/src/positioning/qgeosatelliteinfo.h new file mode 100644 index 0000000..e68d8d9 --- /dev/null +++ b/src/positioning/qgeosatelliteinfo.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOSATELLITEINFO_H +#define QGEOSATELLITEINFO_H + +#include + +QT_BEGIN_NAMESPACE + +class QDebug; +class QDataStream; + +class QGeoSatelliteInfoPrivate; +class Q_POSITIONING_EXPORT QGeoSatelliteInfo +{ +public: + enum Attribute { + Elevation, + Azimuth + }; + + enum SatelliteSystem { + Undefined = 0x00, + GPS = 0x01, + GLONASS = 0x02 + }; + + QGeoSatelliteInfo(); + QGeoSatelliteInfo(const QGeoSatelliteInfo &other); + ~QGeoSatelliteInfo(); + + QGeoSatelliteInfo &operator=(const QGeoSatelliteInfo &other); + + bool operator==(const QGeoSatelliteInfo &other) const; + inline bool operator!=(const QGeoSatelliteInfo &other) const { + return !operator==(other); + } + + void setSatelliteSystem(SatelliteSystem system); + SatelliteSystem satelliteSystem() const; + + void setSatelliteIdentifier(int satId); + int satelliteIdentifier() const; + + void setSignalStrength(int signalStrength); + int signalStrength() const; + + void setAttribute(Attribute attribute, qreal value); + qreal attribute(Attribute attribute) const; + void removeAttribute(Attribute attribute); + + bool hasAttribute(Attribute attribute) const; + +private: +#ifndef QT_NO_DEBUG_STREAM + friend Q_POSITIONING_EXPORT QDebug operator<<(QDebug dbg, const QGeoSatelliteInfo &info); +#endif +#ifndef QT_NO_DATASTREAM + friend Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &stream, const QGeoSatelliteInfo &info); + friend Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &stream, QGeoSatelliteInfo &info); +#endif + QGeoSatelliteInfoPrivate *d; +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_POSITIONING_EXPORT QDebug operator<<(QDebug dbg, const QGeoSatelliteInfo &info); +#endif + +#ifndef QT_NO_DATASTREAM +Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &stream, const QGeoSatelliteInfo &info); +Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &stream, QGeoSatelliteInfo &info); +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeosatelliteinfosource.cpp b/src/positioning/qgeosatelliteinfosource.cpp new file mode 100644 index 0000000..297f484 --- /dev/null +++ b/src/positioning/qgeosatelliteinfosource.cpp @@ -0,0 +1,350 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include +#include "qgeopositioninfosourcefactory.h" +#include "qgeopositioninfosource_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoSatelliteInfoSource + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QGeoSatelliteInfoSource class is an abstract base class for the distribution of satellite information updates. + + The static function QGeoSatelliteInfoSource::createDefaultSource() creates a default + satellite data source that is appropriate for the platform, if one is + available. Otherwise, available QGeoPositionInfoSourceFactory plugins will + be checked for one that has a satellite data source available. + + Call startUpdates() and stopUpdates() to start and stop regular updates, + or requestUpdate() to request a single update. + When an update is available, satellitesInViewUpdated() and/or + satellitesInUseUpdated() will be emitted. + + If regular satellite updates are required, setUpdateInterval() can be used + to specify how often these updates should be emitted. If no interval is + specified, updates are simply provided whenever they are available. + For example: + + \code + // Emit updates every 10 seconds if available + QGeoSatelliteInfoSource *source = QGeoSatelliteInfoSource::createDefaultSource(0); + if (source) + source->setUpdateInterval(10000); + \endcode + + To remove an update interval that was previously set, call + setUpdateInterval() with a value of 0. + + Note that the satellite source may have a minimum value requirement for + update intervals, as returned by minimumUpdateInterval(). +*/ + +class QGeoSatelliteInfoSourcePrivate +{ +public: + int interval; + QString providerName; +}; + +/*! + Creates a satellite source with the specified \a parent. +*/ +QGeoSatelliteInfoSource::QGeoSatelliteInfoSource(QObject *parent) + : QObject(parent), + d(new QGeoSatelliteInfoSourcePrivate) +{ + d->interval = 0; +} + +/*! + Destroys the satellite source. +*/ +QGeoSatelliteInfoSource::~QGeoSatelliteInfoSource() +{ + delete d; +} + +/*! + Returns the unique name of the satellite source implementation in use. + + This is the same name that can be passed to createSource() in order to + create a new instance of a particular satellite source implementation. +*/ +QString QGeoSatelliteInfoSource::sourceName() const +{ + return d->providerName; +} + + +/*! + \property QGeoSatelliteInfoSource::updateInterval + \brief This property holds the requested interval in milliseconds between each update. + + If the update interval is not set (or is set to 0) the + source will provide updates as often as necessary. + + If the update interval is set, the source will provide updates at an + interval as close to the requested interval as possible. If the requested + interval is less than the minimumUpdateInterval(), + the minimum interval is used instead. + + Changes to the update interval will happen as soon as is practical, however the + time the change takes may vary between implementations. Whether or not the elapsed + time from the previous interval is counted as part of the new interval is also + implementation dependent. + + The default value for this property is 0. + + Note: Subclass implementations must call the base implementation of + setUpdateInterval() so that updateInterval() returns the correct value. +*/ +void QGeoSatelliteInfoSource::setUpdateInterval(int msec) +{ + d->interval = msec; +} + +int QGeoSatelliteInfoSource::updateInterval() const +{ + return d->interval; +} + + + +/*! + Creates and returns a source with the specified \a parent that reads + from the system's default source of satellite update information, or the + highest priority available plugin. + + Returns 0 if the system has no default satellite source, no valid plugins + could be found or the user does not have the permission to access the satellite data. +*/ +QGeoSatelliteInfoSource *QGeoSatelliteInfoSource::createDefaultSource(QObject *parent) +{ + QList plugins = QGeoPositionInfoSourcePrivate::pluginsSorted(); + foreach (const QJsonObject &obj, plugins) { + if (obj.value(QStringLiteral("Satellite")).isBool() + && obj.value(QStringLiteral("Satellite")).toBool()) + { + const QString testableKey = QStringLiteral("Testable"); + if (obj.contains(testableKey) && !obj.value(testableKey).toBool()) { + static bool inTest = qEnvironmentVariableIsSet("QT_QTESTLIB_RUNNING"); + if (inTest) + continue; + } + QGeoPositionInfoSourcePrivate d; + d.metaData = obj; + d.loadPlugin(); + QGeoSatelliteInfoSource *s = 0; + if (d.factory) + s = d.factory->satelliteInfoSource(parent); + if (s) + s->d->providerName = d.metaData.value(QStringLiteral("Provider")).toString(); + return s; + } + } + + return 0; +} + +/*! + Creates and returns a source with the given \a parent, + by loading the plugin named \a sourceName. + + Returns 0 if the plugin cannot be found. +*/ +QGeoSatelliteInfoSource *QGeoSatelliteInfoSource::createSource(const QString &sourceName, QObject *parent) +{ + QHash plugins = QGeoPositionInfoSourcePrivate::plugins(); + if (plugins.contains(sourceName)) { + QGeoPositionInfoSourcePrivate d; + d.metaData = plugins.value(sourceName); + d.loadPlugin(); + QGeoSatelliteInfoSource *src = 0; + if (d.factory) + src = d.factory->satelliteInfoSource(parent); + if (src) + src->d->providerName = d.metaData.value(QStringLiteral("Provider")).toString(); + return src; + } + + return 0; +} + +/*! + Returns a list of available source plugins, including the default system + backend if one is available. +*/ +QStringList QGeoSatelliteInfoSource::availableSources() +{ + QStringList plugins; + const QHash meta = QGeoPositionInfoSourcePrivate::plugins(); + for (auto it = meta.cbegin(), end = meta.cend(); it != end; ++it) { + if (it.value().value(QStringLiteral("Satellite")).isBool() + && it.value().value(QStringLiteral("Satellite")).toBool()) { + plugins << it.key(); + } + } + + return plugins; +} + +/*! + \fn void QGeoSatelliteInfoSource::satellitesInViewUpdated(const QList &satellites); + + If startUpdates() or requestUpdate() is called, this signal is emitted + when an update is available on the satellites that are + currently in view. + + The \a satellites parameter holds the satellites currently in view. +*/ + +/*! + \fn void QGeoSatelliteInfoSource::satellitesInUseUpdated(const QList &satellites); + + If startUpdates() or requestUpdate() is called, this signal is emitted + when an update is available on the number of satellites that are + currently in use. + + These are the satellites that are used to get a "fix" - that + is, those used to determine the current position. + + The \a satellites parameter holds the satellites currently in use. +*/ + +/*! + \property QGeoSatelliteInfoSource::minimumUpdateInterval + \brief This property holds the minimum time (in milliseconds) required to retrieve a satellite update. + + This is the minimum value accepted by setUpdateInterval() and + requestUpdate(). +*/ + + +/*! + \fn virtual void QGeoSatelliteInfoSource::startUpdates() = 0; + + Starts emitting updates at regular intervals. The updates will be + provided whenever new satellite information becomes available. + + If satellite information cannot be retrieved or some other + form of timeout has occurred the satellitesInViewUpdated() + and satellitesInUseUpdated() signals may be emitted with + empty parameter lists. + + \sa satellitesInViewUpdated(), satellitesInUseUpdated() +*/ + +/*! + \fn virtual void QGeoSatelliteInfoSource::stopUpdates() = 0; + + Stops emitting updates at regular intervals. +*/ + +/*! + \fn virtual void QGeoSatelliteInfoSource::requestUpdate(int timeout = 0); + + Attempts to get the current satellite information and emit + satellitesInViewUpdated() and satellitesInUseUpdated() with this + information. If the current satellite information cannot be found + within the given \a timeout (in milliseconds) or if \a timeout is less than the value returned by + minimumUpdateInterval(), requestTimeout() is + emitted. + + If the timeout is zero, the timeout defaults to a reasonable timeout + period as appropriate for the source. + + This does nothing if another update request is in progress. However + it can be called even if startUpdates() has already been called and + regular updates are in progress. +*/ + +/*! + \fn void QGeoSatelliteInfoSource::requestTimeout(); + + Emitted if requestUpdate() was called and the current satellite + information could not be retrieved within the specified timeout. + + While the triggering of this signal may be considered an error condition, + it does not imply the emission of the \c error() signal. Only the emission of + \c requestTimeout() is required to indicate a timeout. +*/ + +/*! + \fn QGeoSatelliteInfoSource::Error QGeoSatelliteInfoSource::error() const = 0 + + Returns the last error that occurred. + + This signal is not emitted when a requestTimeout() has occurred. +*/ + +/*! + \fn void QGeoSatelliteInfoSource::error(QGeoSatelliteInfoSource::Error satelliteError) + + This signal is emitted after an error occurred. The \a satelliteError + parameter describes the type of error that occurred. + +*/ + +/*! + \enum QGeoSatelliteInfoSource::Error + + The Error enumeration represents the errors which can occur. + + \value AccessError The connection setup to the satellite backend failed because the + application lacked the required privileges. + \value ClosedError The satellite backend closed the connection, which happens for example in case + the user is switching location services to off. This object becomes invalid and should be deleted. + A new satellite source can be created by calling createDefaultSource() later on. + \value NoError No error has occurred. + \value UnknownSourceError An unidentified error occurred. + */ + + +#include "moc_qgeosatelliteinfosource.cpp" + +QT_END_NAMESPACE diff --git a/src/positioning/qgeosatelliteinfosource.h b/src/positioning/qgeosatelliteinfosource.h new file mode 100644 index 0000000..391eefc --- /dev/null +++ b/src/positioning/qgeosatelliteinfosource.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOSATELLITEINFOSOURCE_H +#define QGEOSATELLITEINFOSOURCE_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGeoSatelliteInfoSourcePrivate; +class Q_POSITIONING_EXPORT QGeoSatelliteInfoSource : public QObject +{ + Q_OBJECT + Q_PROPERTY(int updateInterval READ updateInterval WRITE setUpdateInterval) + Q_PROPERTY(int minimumUpdateInterval READ minimumUpdateInterval) + +public: + enum Error { + AccessError = 0, + ClosedError = 1, + NoError = 2, + UnknownSourceError = -1 + }; + Q_ENUMS(Error) + + explicit QGeoSatelliteInfoSource(QObject *parent); + virtual ~QGeoSatelliteInfoSource(); + + static QGeoSatelliteInfoSource *createDefaultSource(QObject *parent); + static QGeoSatelliteInfoSource *createSource(const QString &sourceName, QObject *parent); + static QStringList availableSources(); + + QString sourceName() const; + + virtual void setUpdateInterval(int msec); + int updateInterval() const; + virtual int minimumUpdateInterval() const = 0; + virtual Error error() const = 0; + +public Q_SLOTS: + virtual void startUpdates() = 0; + virtual void stopUpdates() = 0; + + virtual void requestUpdate(int timeout = 0) = 0; + +Q_SIGNALS: + void satellitesInViewUpdated(const QList &satellites); + void satellitesInUseUpdated(const QList &satellites); + void requestTimeout(); + void error(QGeoSatelliteInfoSource::Error); + +private: + Q_DISABLE_COPY(QGeoSatelliteInfoSource) + QGeoSatelliteInfoSourcePrivate *d; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qgeoshape.cpp b/src/positioning/qgeoshape.cpp new file mode 100644 index 0000000..bac3760 --- /dev/null +++ b/src/positioning/qgeoshape.cpp @@ -0,0 +1,377 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoshape.h" +#include "qgeoshape_p.h" +#include "qgeorectangle.h" +#include "qgeocircle.h" + +#ifndef QT_NO_DEBUG_STREAM +#include +#endif + +#ifndef QT_NO_DATASTREAM +#include +#endif + +QT_BEGIN_NAMESPACE + +QGeoShapePrivate::QGeoShapePrivate(QGeoShape::ShapeType type) +: type(type) +{ +} + +QGeoShapePrivate::~QGeoShapePrivate() +{ +} + +bool QGeoShapePrivate::operator==(const QGeoShapePrivate &other) const +{ + return type == other.type; +} + +/*! + \class QGeoShape + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QGeoShape class defines a geographic area. + + This class is the base class for classes which specify a geographic + area. + + For the sake of consistency, subclasses should describe the specific + details of the associated areas in terms of QGeoCoordinate instances + and distances in meters. + + This class is a \l Q_GADGET since Qt 5.5. It can be + \l{Cpp_value_integration_positioning}{directly used from C++ and QML}. +*/ + +/*! + \enum QGeoShape::ShapeType + + Describes the type of the shape. + + \value UnknownType A shape of unknown type. + \value RectangleType A rectangular shape. + \value CircleType A circular shape. +*/ + +/*! + \property QGeoShape::type + \brief This property holds the type of this geo shape. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoShape::isValid + \brief This property holds the validity of the geo shape. + + A geo shape is considered to be invalid if some of the data that is required to + unambiguously describe the geo shape has not been set or has been set to an + unsuitable value depending on the subclass of this object. The default constructed + objects of this type are invalid. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ + +/*! + \property QGeoShape::isEmpty + \brief This property defines whether this geo shape is empty. + + An empty geo shape is a region which has a geometrical area of 0. + + While this property is introduced in Qt 5.5, the related accessor functions + exist since the first version of this class. + + \since 5.5 +*/ +inline QGeoShapePrivate *QGeoShape::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QGeoShapePrivate *QGeoShape::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +/*! + Constructs a new invalid geo shape of \l UnknownType. +*/ +QGeoShape::QGeoShape() +{ +} + +/*! + Constructs a new geo shape which is a copy of \a other. +*/ +QGeoShape::QGeoShape(const QGeoShape &other) +: d_ptr(other.d_ptr) +{ +} + +/*! + \internal +*/ +QGeoShape::QGeoShape(QGeoShapePrivate *d) +: d_ptr(d) +{ +} + +/*! + Destroys this geo shape. +*/ +QGeoShape::~QGeoShape() +{ +} + +/*! + Returns the type of this geo shape. +*/ +QGeoShape::ShapeType QGeoShape::type() const +{ + Q_D(const QGeoShape); + + if (d) + return d->type; + else + return UnknownType; +} + +/*! + Returns whether this geo shape is valid. + +*/ +bool QGeoShape::isValid() const +{ + Q_D(const QGeoShape); + + if (d) + return d->isValid(); + else + return false; +} + +/*! + Returns whether this geo shape is empty. + + An empty geo shape is a region which has a geometrical area of 0. +*/ +bool QGeoShape::isEmpty() const +{ + Q_D(const QGeoShape); + + if (d) + return d->isEmpty(); + else + return true; +} + +/*! + Returns whether the coordinate \a coordinate is contained within this geo shape. +*/ +bool QGeoShape::contains(const QGeoCoordinate &coordinate) const +{ + Q_D(const QGeoShape); + + if (d) + return d->contains(coordinate); + else + return false; +} + +/*! + Returns the coordinate located at the geometric center of the geo shape. + + \since 5.5 +*/ +QGeoCoordinate QGeoShape::center() const +{ + Q_D(const QGeoShape); + + if (d) + return d->center(); + else + return QGeoCoordinate(); +} + +/*! + Extends the geo shape to also cover the coordinate \a coordinate +*/ +void QGeoShape::extendShape(const QGeoCoordinate &coordinate) +{ + Q_D(QGeoShape); + + if (d) + d->extendShape(coordinate); +} + + +/*! + Returns true if the \a other geo shape is equivalent to this geo shape, otherwise returns + false. +*/ +bool QGeoShape::operator==(const QGeoShape &other) const +{ + Q_D(const QGeoShape); + + if (d == other.d_func()) + return true; + + if (!d || !(other.d_func())) + return false; + + return *d == *other.d_func(); +} + +/*! + Returns true if the \a other geo shape is not equivalent to this geo shape, otherwise returns + false. +*/ +bool QGeoShape::operator!=(const QGeoShape &other) const +{ + return !(*this == other); +} + +/*! + Assigns \a other to this geo shape and returns a reference to this geo shape. +*/ +QGeoShape &QGeoShape::operator=(const QGeoShape &other) +{ + if (this == &other) + return *this; + + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns a string representation of this geo shape. + + \since 5.5 +*/ +QString QGeoShape::toString() const +{ + return QStringLiteral("QGeoShape(%1)").arg(type()); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QGeoShape &shape) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QGeoShape("; + switch (shape.type()) { + case QGeoShape::UnknownType: + dbg << "Unknown"; + break; + case QGeoShape::RectangleType: + dbg << "Rectangle"; + break; + case QGeoShape::CircleType: + dbg << "Circle"; + } + + dbg << ')'; + + return dbg; +} +#endif + +#ifndef QT_NO_DATASTREAM +QDataStream &operator<<(QDataStream &stream, const QGeoShape &shape) +{ + stream << quint32(shape.type()); + switch (shape.type()) { + case QGeoShape::UnknownType: + break; + case QGeoShape::RectangleType: { + QGeoRectangle r = shape; + stream << r.topLeft() << r.bottomRight(); + break; + } + case QGeoShape::CircleType: { + QGeoCircle c = shape; + stream << c.center() << c.radius(); + break; + } + } + + return stream; +} + +QDataStream &operator>>(QDataStream &stream, QGeoShape &shape) +{ + quint32 type; + stream >> type; + + switch (type) { + case QGeoShape::UnknownType: + shape = QGeoShape(); + break; + case QGeoShape::RectangleType: { + QGeoCoordinate tl; + QGeoCoordinate br; + stream >> tl >> br; + shape = QGeoRectangle(tl, br); + break; + } + case QGeoShape::CircleType: { + QGeoCoordinate c; + qreal r; + stream >> c >> r; + shape = QGeoCircle(c, r); + break; + } + } + + return stream; +} +#endif + +QT_END_NAMESPACE diff --git a/src/positioning/qgeoshape.h b/src/positioning/qgeoshape.h new file mode 100644 index 0000000..f8a3035 --- /dev/null +++ b/src/positioning/qgeoshape.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSHAPE_H +#define QGEOSHAPE_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDebug; +class QGeoShapePrivate; + +class Q_POSITIONING_EXPORT QGeoShape +{ + Q_GADGET + Q_PROPERTY(ShapeType type READ type) + Q_PROPERTY(bool isValid READ isValid) + Q_PROPERTY(bool isEmpty READ isEmpty) + Q_ENUMS(ShapeType) + +public: + QGeoShape(); + QGeoShape(const QGeoShape &other); + ~QGeoShape(); + + enum ShapeType { + UnknownType, + RectangleType, + CircleType + }; + + ShapeType type() const; + + bool isValid() const; + bool isEmpty() const; + Q_INVOKABLE bool contains(const QGeoCoordinate &coordinate) const; + + QGeoCoordinate center() const; + + void extendShape(const QGeoCoordinate &coordinate); + + bool operator==(const QGeoShape &other) const; + bool operator!=(const QGeoShape &other) const; + + QGeoShape &operator=(const QGeoShape &other); + + Q_INVOKABLE QString toString() const; +protected: + QGeoShape(QGeoShapePrivate *d); + + QSharedDataPointer d_ptr; + +private: + inline QGeoShapePrivate *d_func(); + inline const QGeoShapePrivate *d_func() const; +}; + +Q_DECLARE_TYPEINFO(QGeoShape, Q_MOVABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_POSITIONING_EXPORT QDebug operator<<(QDebug, const QGeoShape &); +#endif + +#ifndef QT_NO_DATASTREAM +Q_POSITIONING_EXPORT QDataStream &operator<<(QDataStream &stream, const QGeoShape &shape); +Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &stream, QGeoShape &shape); +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoShape) + +#endif + diff --git a/src/positioning/qgeoshape_p.h b/src/positioning/qgeoshape_p.h new file mode 100644 index 0000000..275886f --- /dev/null +++ b/src/positioning/qgeoshape_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSHAPE_P_H +#define QGEOSHAPE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "qgeoshape.h" + +QT_BEGIN_NAMESPACE + +class QGeoShapePrivate : public QSharedData +{ +public: + explicit QGeoShapePrivate(QGeoShape::ShapeType type); + virtual ~QGeoShapePrivate(); + + virtual bool isValid() const = 0; + virtual bool isEmpty() const = 0; + virtual bool contains(const QGeoCoordinate &coordinate) const = 0; + + virtual QGeoCoordinate center() const = 0; + + virtual void extendShape(const QGeoCoordinate &coordinate) = 0; + + virtual QGeoShapePrivate *clone() const = 0; + + virtual bool operator==(const QGeoShapePrivate &other) const; + + QGeoShape::ShapeType type; +}; + +// don't use the copy constructor when detaching from a QSharedDataPointer, use virtual clone() +// call instead. +template <> +Q_INLINE_TEMPLATE QGeoShapePrivate *QSharedDataPointer::clone() +{ + return d->clone(); +} + +QT_END_NAMESPACE + +#endif + diff --git a/src/positioning/qlocationdata_simulator.cpp b/src/positioning/qlocationdata_simulator.cpp new file mode 100644 index 0000000..3045b86 --- /dev/null +++ b/src/positioning/qlocationdata_simulator.cpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocationdata_simulator_p.h" + +#include + +QT_BEGIN_NAMESPACE + +QGeoPositionInfoData::QGeoPositionInfoData() + : latitude(0.0), + longitude(0.0), + altitude(0.0), + direction(0.0), + groundSpeed(0.0), + verticalSpeed(0.0), + magneticVariation(0.0), + horizontalAccuracy(0.0), + verticalAccuracy(0.0), + dateTime(), + minimumInterval(0), + enabled(false) {} + +QGeoSatelliteInfoData::SatelliteInfo::SatelliteInfo() + : azimuth(0.0), + elevation(0.0), + signalStrength(0), + inUse(false), + satelliteSystem(Undefined), + satelliteIdentifier(0) {} + +void qt_registerLocationTypes() +{ + qRegisterMetaTypeStreamOperators("QGeoPositionInfoData"); + qRegisterMetaTypeStreamOperators("QGeoSatelliteInfoData"); + qRegisterMetaTypeStreamOperators("QGeoSatelliteInfoData::SatelliteInfo"); +} + +QDataStream &operator<<(QDataStream &out, const QGeoPositionInfoData &s) +{ + out << s.latitude << s.longitude << s.altitude; + out << s.direction << s.groundSpeed << s.verticalSpeed << s.magneticVariation << s.horizontalAccuracy << s.verticalAccuracy; + out << s.dateTime; + out << s.minimumInterval << s.enabled; + return out; +} + +QDataStream &operator>>(QDataStream &in, QGeoPositionInfoData &s) +{ + in >> s.latitude >> s.longitude >> s.altitude; + in >> s.direction >> s.groundSpeed >> s.verticalSpeed >> s.magneticVariation >> s.horizontalAccuracy >> s.verticalAccuracy; + in >> s.dateTime; + in >> s.minimumInterval >> s.enabled; + return in; +} + +QDataStream &operator<<(QDataStream &out, const QGeoSatelliteInfoData &s) +{ + out << s.satellites; + return out; +} + +QDataStream &operator>>(QDataStream &in, QGeoSatelliteInfoData &s) +{ + in >> s.satellites; + return in; +} + +QDataStream &operator<<(QDataStream &out, const QGeoSatelliteInfoData::SatelliteInfo &s) +{ + out << s.azimuth << s.elevation << s.signalStrength << s.inUse << static_cast(s.satelliteSystem) << s.satelliteIdentifier; + return out; +} + +QDataStream &operator>>(QDataStream &in, QGeoSatelliteInfoData::SatelliteInfo &s) +{ + qint32 satelliteSystem; + in >> s.azimuth >> s.elevation >> s.signalStrength >> s.inUse >> satelliteSystem >> s.satelliteIdentifier; + s.satelliteSystem = static_cast(satelliteSystem); + return in; +} + +QT_END_NAMESPACE diff --git a/src/positioning/qlocationdata_simulator_p.h b/src/positioning/qlocationdata_simulator_p.h new file mode 100644 index 0000000..47f3acf --- /dev/null +++ b/src/positioning/qlocationdata_simulator_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFODATA_SIMULATOR_P_H +#define QGEOPOSITIONINFODATA_SIMULATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +// +// DO NOT REMOVE +// ------------- +// +// This header file contains structures used to serialize communication between +// simulator's client and server implementations, it is included by simulator +// positioning plugin. + +#include "qpositioningglobal_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +struct Q_POSITIONING_PRIVATE_EXPORT QGeoPositionInfoData +{ + QGeoPositionInfoData(); + + // Coordinate information + double latitude; + double longitude; + double altitude; + + // Attributes + // ### transmit whether attributes are set or not + qreal direction; + qreal groundSpeed; + qreal verticalSpeed; + qreal magneticVariation; + qreal horizontalAccuracy; + qreal verticalAccuracy; + + // DateTime info + QDateTime dateTime; + + int minimumInterval; + bool enabled; +}; + +struct Q_POSITIONING_PRIVATE_EXPORT QGeoSatelliteInfoData +{ + struct SatelliteInfo + { + SatelliteInfo(); + + // This enum duplicates the SatelliteSystem enum defined in qgeosatelliteinfo.h, which cannot be + // included as this file must compile with Qt4 (it is used by Qt Simulator) + enum SatelliteSystem + { + Undefined = 0x00, + GPS = 0x01, + GLONASS = 0x02 + }; + + qreal azimuth; + qreal elevation; + int signalStrength; + bool inUse; + SatelliteSystem satelliteSystem; + int satelliteIdentifier; + }; + + QList satellites; +}; + +Q_POSITIONING_PRIVATE_EXPORT void qt_registerLocationTypes(); +Q_POSITIONING_PRIVATE_EXPORT QDataStream &operator<<(QDataStream &out, const QGeoPositionInfoData &s); +Q_POSITIONING_PRIVATE_EXPORT QDataStream &operator>>(QDataStream &in, QGeoPositionInfoData &s); +Q_POSITIONING_PRIVATE_EXPORT QDataStream &operator<<(QDataStream &out, const QGeoSatelliteInfoData &s); +Q_POSITIONING_PRIVATE_EXPORT QDataStream &operator>>(QDataStream &in, QGeoSatelliteInfoData &s); +Q_POSITIONING_PRIVATE_EXPORT QDataStream &operator<<(QDataStream &out, const QGeoSatelliteInfoData::SatelliteInfo &s); +Q_POSITIONING_PRIVATE_EXPORT QDataStream &operator>>(QDataStream &in, QGeoSatelliteInfoData::SatelliteInfo &s); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoPositionInfoData) +Q_DECLARE_METATYPE(QGeoSatelliteInfoData) +Q_DECLARE_METATYPE(QGeoSatelliteInfoData::SatelliteInfo) + +#endif // QGEOPOSITIONINFODATA_SIMULATOR_P_H diff --git a/src/positioning/qlocationutils.cpp b/src/positioning/qlocationutils.cpp new file mode 100644 index 0000000..829f1bb --- /dev/null +++ b/src/positioning/qlocationutils.cpp @@ -0,0 +1,403 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qlocationutils_p.h" +#include "qgeopositioninfo.h" + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +// converts e.g. 15306.0235 from NMEA sentence to 153.100392 +static double qlocationutils_nmeaDegreesToDecimal(double nmeaDegrees) +{ + double deg; + double min = 100.0 * modf(nmeaDegrees / 100.0, °); + return deg + (min / 60.0); +} + +static void qlocationutils_readGga(const char *data, int size, QGeoPositionInfo *info, double uere, + bool *hasFix) +{ + QByteArray sentence(data, size); + QList parts = sentence.split(','); + QGeoCoordinate coord; + + if (hasFix && parts.count() > 6 && parts[6].count() > 0) + *hasFix = parts[6].toInt() > 0; + + if (parts.count() > 1 && parts[1].count() > 0) { + QTime time; + if (QLocationUtils::getNmeaTime(parts[1], &time)) + info->setTimestamp(QDateTime(QDate(), time, Qt::UTC)); + } + + if (parts.count() > 5 && parts[3].count() == 1 && parts[5].count() == 1) { + double lat; + double lng; + if (QLocationUtils::getNmeaLatLong(parts[2], parts[3][0], parts[4], parts[5][0], &lat, &lng)) { + coord.setLatitude(lat); + coord.setLongitude(lng); + } + } + + if (parts.count() > 8 && !parts[8].isEmpty()) { + bool hasHdop = false; + double hdop = parts[8].toDouble(&hasHdop); + if (hasHdop) + info->setAttribute(QGeoPositionInfo::HorizontalAccuracy, 2 * hdop * uere); + } + + if (parts.count() > 9 && parts[9].count() > 0) { + bool hasAlt = false; + double alt = parts[9].toDouble(&hasAlt); + if (hasAlt) + coord.setAltitude(alt); + } + + if (coord.type() != QGeoCoordinate::InvalidCoordinate) + info->setCoordinate(coord); +} + +static void qlocationutils_readGsa(const char *data, int size, QGeoPositionInfo *info, double uere, + bool *hasFix) +{ + QList parts = QByteArray::fromRawData(data, size).split(','); + + if (hasFix && parts.count() > 2 && !parts[2].isEmpty()) + *hasFix = parts[2].toInt() > 0; + + if (parts.count() > 16 && !parts[16].isEmpty()) { + bool hasHdop = false; + double hdop = parts[16].toDouble(&hasHdop); + if (hasHdop) + info->setAttribute(QGeoPositionInfo::HorizontalAccuracy, 2 * hdop * uere); + } + + if (parts.count() > 17 && !parts[17].isEmpty()) { + bool hasVdop = false; + double vdop = parts[17].toDouble(&hasVdop); + if (hasVdop) + info->setAttribute(QGeoPositionInfo::VerticalAccuracy, 2 * vdop * uere); + } +} + +static void qlocationutils_readGll(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) +{ + QByteArray sentence(data, size); + QList parts = sentence.split(','); + QGeoCoordinate coord; + + if (hasFix && parts.count() > 6 && parts[6].count() > 0) + *hasFix = (parts[6][0] == 'A'); + + if (parts.count() > 5 && parts[5].count() > 0) { + QTime time; + if (QLocationUtils::getNmeaTime(parts[5], &time)) + info->setTimestamp(QDateTime(QDate(), time, Qt::UTC)); + } + + if (parts.count() > 4 && parts[2].count() == 1 && parts[4].count() == 1) { + double lat; + double lng; + if (QLocationUtils::getNmeaLatLong(parts[1], parts[2][0], parts[3], parts[4][0], &lat, &lng)) { + coord.setLatitude(lat); + coord.setLongitude(lng); + } + } + + if (coord.type() != QGeoCoordinate::InvalidCoordinate) + info->setCoordinate(coord); +} + +static void qlocationutils_readRmc(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) +{ + QByteArray sentence(data, size); + QList parts = sentence.split(','); + QGeoCoordinate coord; + QDate date; + QTime time; + + if (hasFix && parts.count() > 2 && parts[2].count() > 0) + *hasFix = (parts[2][0] == 'A'); + + if (parts.count() > 9 && parts[9].count() == 6) { + date = QDate::fromString(QString::fromLatin1(parts[9]), QStringLiteral("ddMMyy")); + if (date.isValid()) + date = date.addYears(100); // otherwise starts from 1900 + else + date = QDate(); + } + + if (parts.count() > 1 && parts[1].count() > 0) + QLocationUtils::getNmeaTime(parts[1], &time); + + if (parts.count() > 6 && parts[4].count() == 1 && parts[6].count() == 1) { + double lat; + double lng; + if (QLocationUtils::getNmeaLatLong(parts[3], parts[4][0], parts[5], parts[6][0], &lat, &lng)) { + coord.setLatitude(lat); + coord.setLongitude(lng); + } + } + + bool parsed = false; + double value = 0.0; + if (parts.count() > 7 && parts[7].count() > 0) { + value = parts[7].toDouble(&parsed); + if (parsed) + info->setAttribute(QGeoPositionInfo::GroundSpeed, qreal(value * 1.852 / 3.6)); // knots -> m/s + } + if (parts.count() > 8 && parts[8].count() > 0) { + value = parts[8].toDouble(&parsed); + if (parsed) + info->setAttribute(QGeoPositionInfo::Direction, qreal(value)); + } + if (parts.count() > 11 && parts[11].count() == 1 + && (parts[11][0] == 'E' || parts[11][0] == 'W')) { + value = parts[10].toDouble(&parsed); + if (parsed) { + if (parts[11][0] == 'W') + value *= -1; + info->setAttribute(QGeoPositionInfo::MagneticVariation, qreal(value)); + } + } + + if (coord.type() != QGeoCoordinate::InvalidCoordinate) + info->setCoordinate(coord); + + info->setTimestamp(QDateTime(date, time, Qt::UTC)); +} + +static void qlocationutils_readVtg(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) +{ + if (hasFix) + *hasFix = false; + + QByteArray sentence(data, size); + QList parts = sentence.split(','); + + bool parsed = false; + double value = 0.0; + if (parts.count() > 1 && parts[1].count() > 0) { + value = parts[1].toDouble(&parsed); + if (parsed) + info->setAttribute(QGeoPositionInfo::Direction, qreal(value)); + } + if (parts.count() > 7 && parts[7].count() > 0) { + value = parts[7].toDouble(&parsed); + if (parsed) + info->setAttribute(QGeoPositionInfo::GroundSpeed, qreal(value / 3.6)); // km/h -> m/s + } +} + +static void qlocationutils_readZda(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) +{ + if (hasFix) + *hasFix = false; + + QByteArray sentence(data, size); + QList parts = sentence.split(','); + QDate date; + QTime time; + + if (parts.count() > 1 && parts[1].count() > 0) + QLocationUtils::getNmeaTime(parts[1], &time); + + if (parts.count() > 4 && parts[2].count() > 0 && parts[3].count() > 0 + && parts[4].count() == 4) { // must be full 4-digit year + int day = parts[2].toUInt(); + int month = parts[3].toUInt(); + int year = parts[4].toUInt(); + if (day > 0 && month > 0 && year > 0) + date.setDate(year, month, day); + } + + info->setTimestamp(QDateTime(date, time, Qt::UTC)); +} + +bool QLocationUtils::getPosInfoFromNmea(const char *data, int size, QGeoPositionInfo *info, + double uere, bool *hasFix) +{ + if (!info) + return false; + + if (hasFix) + *hasFix = false; + if (size < 6 || data[0] != '$' || !hasValidNmeaChecksum(data, size)) + return false; + + // Adjust size so that * and following characters are not parsed by the following functions. + for (int i = 0; i < size; ++i) { + if (data[i] == '*') { + size = i; + break; + } + } + + if (data[3] == 'G' && data[4] == 'G' && data[5] == 'A') { + // "$--GGA" sentence. + qlocationutils_readGga(data, size, info, uere, hasFix); + return true; + } + + if (data[3] == 'G' && data[4] == 'S' && data[5] == 'A') { + // "$--GSA" sentence. + qlocationutils_readGsa(data, size, info, uere, hasFix); + return true; + } + + if (data[3] == 'G' && data[4] == 'L' && data[5] == 'L') { + // "$--GLL" sentence. + qlocationutils_readGll(data, size, info, hasFix); + return true; + } + + if (data[3] == 'R' && data[4] == 'M' && data[5] == 'C') { + // "$--RMC" sentence. + qlocationutils_readRmc(data, size, info, hasFix); + return true; + } + + if (data[3] == 'V' && data[4] == 'T' && data[5] == 'G') { + // "$--VTG" sentence. + qlocationutils_readVtg(data, size, info, hasFix); + return true; + } + + if (data[3] == 'Z' && data[4] == 'D' && data[5] == 'A') { + // "$--ZDA" sentence. + qlocationutils_readZda(data, size, info, hasFix); + return true; + } + + return false; +} + +bool QLocationUtils::hasValidNmeaChecksum(const char *data, int size) +{ + int asteriskIndex = -1; + for (int i = 0; i < size; ++i) { + if (data[i] == '*') { + asteriskIndex = i; + break; + } + } + + const int CSUM_LEN = 2; + if (asteriskIndex < 0 || asteriskIndex + CSUM_LEN >= size) + return false; + + // XOR byte value of all characters between '$' and '*' + int result = 0; + for (int i = 1; i < asteriskIndex; ++i) + result ^= data[i]; + /* + char calc[CSUM_LEN + 1]; + ::snprintf(calc, CSUM_LEN + 1, "%02x", result); + return ::strncmp(calc, &data[asteriskIndex+1], 2) == 0; + */ + + QByteArray checkSumBytes(&data[asteriskIndex + 1], 2); + bool ok = false; + int checksum = checkSumBytes.toInt(&ok,16); + return ok && checksum == result; +} + +bool QLocationUtils::getNmeaTime(const QByteArray &bytes, QTime *time) +{ + int dotIndex = bytes.indexOf('.'); + QTime tempTime; + + if (dotIndex < 0) { + tempTime = QTime::fromString(QString::fromLatin1(bytes.constData()), + QStringLiteral("hhmmss")); + } else { + tempTime = QTime::fromString(QString::fromLatin1(bytes.mid(0, dotIndex)), + QStringLiteral("hhmmss")); + bool hasMsecs = false; + int midLen = qMin(3, bytes.size() - dotIndex - 1); + int msecs = bytes.mid(dotIndex + 1, midLen).toUInt(&hasMsecs); + if (hasMsecs) + tempTime = tempTime.addMSecs(msecs); + } + + if (tempTime.isValid()) { + *time = tempTime; + return true; + } + return false; +} + +bool QLocationUtils::getNmeaLatLong(const QByteArray &latString, char latDirection, const QByteArray &lngString, char lngDirection, double *lat, double *lng) +{ + if ((latDirection != 'N' && latDirection != 'S') + || (lngDirection != 'E' && lngDirection != 'W')) { + return false; + } + + bool hasLat = false; + bool hasLong = false; + double tempLat = latString.toDouble(&hasLat); + double tempLng = lngString.toDouble(&hasLong); + if (hasLat && hasLong) { + tempLat = qlocationutils_nmeaDegreesToDecimal(tempLat); + if (latDirection == 'S') + tempLat *= -1; + tempLng = qlocationutils_nmeaDegreesToDecimal(tempLng); + if (lngDirection == 'W') + tempLng *= -1; + + if (isValidLat(tempLat) && isValidLong(tempLng)) { + *lat = tempLat; + *lng = tempLng; + return true; + } + } + return false; +} + +QT_END_NAMESPACE + diff --git a/src/positioning/qlocationutils_p.h b/src/positioning/qlocationutils_p.h new file mode 100644 index 0000000..00c4d3e --- /dev/null +++ b/src/positioning/qlocationutils_p.h @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QLOCATIONUTILS_P_H +#define QLOCATIONUTILS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE +class QTime; +class QByteArray; + +class QGeoPositionInfo; +class QLocationUtils +{ +public: + enum CardinalDirection { + CardinalN, + CardinalE, + CardinalS, + CardinalW, + CardinalNE, + CardinalSE, + CardinalSW, + CardinalNW, + CardinalNNE, + CardinalENE, + CardinalESE, + CardinalSSE, + CardinalSSW, + CardinalWSW, + CardinalWNW, + CardinalNNW + }; + + inline static bool isValidLat(double lat) { + return lat >= -90 && lat <= 90; + } + inline static bool isValidLong(double lng) { + return lng >= -180 && lng <= 180; + } + + inline static double clipLat(double lat) { + if (lat > 90) + lat = 90; + else if (lat < -90) + lat = -90; + return lat; + } + + inline static double wrapLong(double lng) { + if (lng > 180) + lng -= 360; + else if (lng < -180) + lng += 360; + return lng; + } + + inline static CardinalDirection azimuthToCardinalDirection4(double azimuth) + { + azimuth = fmod(azimuth, 360.0); + if (azimuth < 45.0 || azimuth > 315.0 ) + return CardinalN; + else if (azimuth < 135.0) + return CardinalE; + else if (azimuth < 225.0) + return CardinalS; + else + return CardinalW; + } + + inline static CardinalDirection azimuthToCardinalDirection8(double azimuth) + { + azimuth = fmod(azimuth, 360.0); + if (azimuth < 22.5 || azimuth > 337.5 ) + return CardinalN; + else if (azimuth < 67.5) + return CardinalNE; + else if (azimuth < 112.5) + return CardinalE; + else if (azimuth < 157.5) + return CardinalSE; + else if (azimuth < 202.5) + return CardinalS; + + else if (azimuth < 247.5) + return CardinalSW; + else if (azimuth < 292.5) + return CardinalW; + else + return CardinalNW; + } + + inline static CardinalDirection azimuthToCardinalDirection16(double azimuth) + { + azimuth = fmod(azimuth, 360.0); + if (azimuth < 11.5 || azimuth > 348.75 ) + return CardinalN; + else if (azimuth < 33.75) + return CardinalNNE; + else if (azimuth < 56.25) + return CardinalNE; + else if (azimuth < 78.75) + return CardinalENE; + else if (azimuth < 101.25) + return CardinalE; + else if (azimuth < 123.75) + return CardinalESE; + else if (azimuth < 146.25) + return CardinalSE; + else if (azimuth < 168.75) + return CardinalSSE; + else if (azimuth < 191.25) + return CardinalS; + + else if (azimuth < 213.75) + return CardinalSSW; + else if (azimuth < 236.25) + return CardinalSW; + else if (azimuth < 258.75) + return CardinalWSW; + else if (azimuth < 281.25) + return CardinalW; + else if (azimuth < 303.75) + return CardinalWNW; + else if (azimuth < 326.25) + return CardinalNW; + else + return CardinalNNW; + } + + /* + Creates a QGeoPositionInfo from a GGA, GLL, RMC, VTG or ZDA sentence. + + Note: + - GGA and GLL sentences have time but not date so the update's + QDateTime object will have an invalid date. + - RMC reports date with a two-digit year so in this case the year + is assumed to be after the year 2000. + */ + Q_AUTOTEST_EXPORT static bool getPosInfoFromNmea(const char *data, int size, + QGeoPositionInfo *info, double uere, + bool *hasFix = 0); + + /* + Returns true if the given NMEA sentence has a valid checksum. + */ + Q_AUTOTEST_EXPORT static bool hasValidNmeaChecksum(const char *data, int size); + + /* + Returns time from a string in hhmmss or hhmmss.z+ format. + */ + Q_AUTOTEST_EXPORT static bool getNmeaTime(const QByteArray &bytes, QTime *time); + + /* + Accepts for example ("2734.7964", 'S', "15306.0124", 'E') and returns the + lat-long values. Fails if lat or long fail isValidLat() or isValidLong(). + */ + Q_AUTOTEST_EXPORT static bool getNmeaLatLong(const QByteArray &latString, char latDirection, const QByteArray &lngString, char lngDirection, double *lat, double *lon); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qnmeapositioninfosource.cpp b/src/positioning/qnmeapositioninfosource.cpp new file mode 100644 index 0000000..c5fde41 --- /dev/null +++ b/src/positioning/qnmeapositioninfosource.cpp @@ -0,0 +1,680 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qnmeapositioninfosource_p.h" +#include "qlocationutils_p.h" + +#include +#include +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +QNmeaRealTimeReader::QNmeaRealTimeReader(QNmeaPositionInfoSourcePrivate *sourcePrivate) + : QNmeaReader(sourcePrivate) +{ +} + +void QNmeaRealTimeReader::readAvailableData() +{ + while (m_proxy->m_device->canReadLine()){ + QGeoPositionInfo update; + bool hasFix = false; + + char buf[1024]; + qint64 size = m_proxy->m_device->readLine(buf, sizeof(buf)); + if (m_proxy->parsePosInfoFromNmeaData(buf, size, &update, &hasFix)) + m_proxy->notifyNewUpdate(&update, hasFix); + } +} + + +//============================================================ + +QNmeaSimulatedReader::QNmeaSimulatedReader(QNmeaPositionInfoSourcePrivate *sourcePrivate) + : QNmeaReader(sourcePrivate), + m_currTimerId(-1), + m_hasValidDateTime(false) +{ +} + +QNmeaSimulatedReader::~QNmeaSimulatedReader() +{ + if (m_currTimerId > 0) + killTimer(m_currTimerId); +} + +void QNmeaSimulatedReader::readAvailableData() +{ + if (m_currTimerId > 0) // we are already reading + return; + + if (!m_hasValidDateTime) { // first update + Q_ASSERT(m_proxy->m_device && (m_proxy->m_device->openMode() & QIODevice::ReadOnly)); + + if (!setFirstDateTime()) { + //m_proxy->notifyReachedEndOfFile(); + qWarning("QNmeaPositionInfoSource: cannot find NMEA sentence with valid date & time"); + return; + } + + m_hasValidDateTime = true; + simulatePendingUpdate(); + + } else { + // previously read to EOF, but now new data has arrived + processNextSentence(); + } +} + +bool QNmeaSimulatedReader::setFirstDateTime() +{ + // find the first update with valid date and time + QGeoPositionInfo update; + bool hasFix = false; + while (m_proxy->m_device->bytesAvailable() > 0) { + char buf[1024]; + qint64 size = m_proxy->m_device->readLine(buf, sizeof(buf)); + if (size <= 0) + continue; + bool ok = m_proxy->parsePosInfoFromNmeaData(buf, size, &update, &hasFix); + if (ok && update.timestamp().isValid()) { + QPendingGeoPositionInfo pending; + pending.info = update; + pending.hasFix = hasFix; + m_pendingUpdates.enqueue(pending); + return true; + } + } + return false; +} + +void QNmeaSimulatedReader::simulatePendingUpdate() +{ + if (m_pendingUpdates.size() > 0) { + // will be dequeued in processNextSentence() + QPendingGeoPositionInfo &pending = m_pendingUpdates.head(); + m_proxy->notifyNewUpdate(&pending.info, pending.hasFix); + } + + processNextSentence(); +} + +void QNmeaSimulatedReader::timerEvent(QTimerEvent *event) +{ + killTimer(event->timerId()); + m_currTimerId = -1; + simulatePendingUpdate(); +} + +void QNmeaSimulatedReader::processNextSentence() +{ + QGeoPositionInfo info; + bool hasFix = false; + int timeToNextUpdate = -1; + QTime prevTime; + if (m_pendingUpdates.size() > 0) + prevTime = m_pendingUpdates.head().info.timestamp().time(); + + // find the next update with a valid time (as long as the time is valid, + // we can calculate when the update should be emitted) + while (m_proxy->m_device && m_proxy->m_device->bytesAvailable() > 0) { + char buf[1024]; + qint64 size = m_proxy->m_device->readLine(buf, sizeof(buf)); + if (size <= 0) + continue; + if (m_proxy->parsePosInfoFromNmeaData(buf, size, &info, &hasFix)) { + QTime time = info.timestamp().time(); + if (time.isValid()) { + if (!prevTime.isValid()) { + timeToNextUpdate = 0; + break; + } + timeToNextUpdate = prevTime.msecsTo(time); + if (timeToNextUpdate >= 0) + break; + } else { + timeToNextUpdate = 0; + break; + } + } + } + + if (timeToNextUpdate < 0) + return; + + m_pendingUpdates.dequeue(); + + QPendingGeoPositionInfo pending; + pending.info = info; + pending.hasFix = hasFix; + m_pendingUpdates.enqueue(pending); + m_currTimerId = startTimer(timeToNextUpdate); +} + + +//============================================================ + + +QNmeaPositionInfoSourcePrivate::QNmeaPositionInfoSourcePrivate(QNmeaPositionInfoSource *parent, QNmeaPositionInfoSource::UpdateMode updateMode) + : QObject(parent), + m_updateMode(updateMode), + m_device(0), + m_invokedStart(false), + m_positionError(QGeoPositionInfoSource::UnknownSourceError), + m_userEquivalentRangeError(qQNaN()), + m_source(parent), + m_nmeaReader(0), + m_updateTimer(0), + m_requestTimer(0), + m_horizontalAccuracy(qQNaN()), + m_verticalAccuracy(qQNaN()), + m_noUpdateLastInterval(false), + m_updateTimeoutSent(false), + m_connectedReadyRead(false) +{ +} + +QNmeaPositionInfoSourcePrivate::~QNmeaPositionInfoSourcePrivate() +{ + delete m_nmeaReader; + delete m_updateTimer; +} + +bool QNmeaPositionInfoSourcePrivate::openSourceDevice() +{ + if (!m_device) { + qWarning("QNmeaPositionInfoSource: no QIODevice data source, call setDevice() first"); + return false; + } + + if (!m_device->isOpen() && !m_device->open(QIODevice::ReadOnly)) { + qWarning("QNmeaPositionInfoSource: cannot open QIODevice data source"); + return false; + } + + connect(m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed())); + connect(m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed())); + connect(m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed())); + + return true; +} + +void QNmeaPositionInfoSourcePrivate::sourceDataClosed() +{ + if (m_nmeaReader && m_device && m_device->bytesAvailable()) + m_nmeaReader->readAvailableData(); +} + +void QNmeaPositionInfoSourcePrivate::readyRead() +{ + if (m_nmeaReader) + m_nmeaReader->readAvailableData(); +} + +bool QNmeaPositionInfoSourcePrivate::initialize() +{ + if (m_nmeaReader) + return true; + + if (!openSourceDevice()) + return false; + + if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode) + m_nmeaReader = new QNmeaRealTimeReader(this); + else + m_nmeaReader = new QNmeaSimulatedReader(this); + + return true; +} + +void QNmeaPositionInfoSourcePrivate::prepareSourceDevice() +{ + // some data may already be available + if (m_updateMode == QNmeaPositionInfoSource::SimulationMode) { + if (m_nmeaReader && m_device->bytesAvailable()) + m_nmeaReader->readAvailableData(); + } + + if (!m_connectedReadyRead) { + connect(m_device, SIGNAL(readyRead()), SLOT(readyRead())); + m_connectedReadyRead = true; + } +} + +bool QNmeaPositionInfoSourcePrivate::parsePosInfoFromNmeaData(const char *data, int size, + QGeoPositionInfo *posInfo, bool *hasFix) +{ + return m_source->parsePosInfoFromNmeaData(data, size, posInfo, hasFix); +} + +void QNmeaPositionInfoSourcePrivate::startUpdates() +{ + if (m_invokedStart) + return; + + m_invokedStart = true; + m_pendingUpdate = QGeoPositionInfo(); + m_noUpdateLastInterval = false; + + bool initialized = initialize(); + if (!initialized) + return; + + if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode) { + // skip over any buffered data - we only want the newest data + if (m_device->bytesAvailable()) { + if (m_device->isSequential()) + m_device->readAll(); + else + m_device->seek(m_device->bytesAvailable()); + } + } + + if (m_updateTimer) + m_updateTimer->stop(); + + if (m_source->updateInterval() > 0) { + if (!m_updateTimer) + m_updateTimer = new QBasicTimer; + m_updateTimer->start(m_source->updateInterval(), this); + } + + if (initialized) + prepareSourceDevice(); +} + +void QNmeaPositionInfoSourcePrivate::stopUpdates() +{ + m_invokedStart = false; + if (m_updateTimer) + m_updateTimer->stop(); + m_pendingUpdate = QGeoPositionInfo(); + m_noUpdateLastInterval = false; +} + +void QNmeaPositionInfoSourcePrivate::requestUpdate(int msec) +{ + if (m_requestTimer && m_requestTimer->isActive()) + return; + + if (msec <= 0 || msec < m_source->minimumUpdateInterval()) { + emit m_source->updateTimeout(); + return; + } + + if (!m_requestTimer) { + m_requestTimer = new QTimer(this); + connect(m_requestTimer, SIGNAL(timeout()), SLOT(updateRequestTimeout())); + } + + bool initialized = initialize(); + if (!initialized) { + emit m_source->updateTimeout(); + return; + } + + m_requestTimer->start(msec); + + if (initialized) + prepareSourceDevice(); +} + +void QNmeaPositionInfoSourcePrivate::updateRequestTimeout() +{ + m_requestTimer->stop(); + emit m_source->updateTimeout(); +} + +void QNmeaPositionInfoSourcePrivate::notifyNewUpdate(QGeoPositionInfo *update, bool hasFix) +{ + // include before uncommenting + //qDebug() << "QNmeaPositionInfoSourcePrivate::notifyNewUpdate()" << update->timestamp() << hasFix << m_invokedStart << (m_requestTimer && m_requestTimer->isActive()); + + QDate date = update->timestamp().date(); + if (date.isValid()) { + m_currentDate = date; + } else { + // some sentence have time but no date + QTime time = update->timestamp().time(); + if (time.isValid() && m_currentDate.isValid()) + update->setTimestamp(QDateTime(m_currentDate, time, Qt::UTC)); + } + + // Some attributes are sent in separate NMEA sentences. Save and restore the accuracy + // measurements. + if (update->hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) + m_horizontalAccuracy = update->attribute(QGeoPositionInfo::HorizontalAccuracy); + else if (!qIsNaN(m_horizontalAccuracy)) + update->setAttribute(QGeoPositionInfo::HorizontalAccuracy, m_horizontalAccuracy); + if (update->hasAttribute(QGeoPositionInfo::VerticalAccuracy)) + m_verticalAccuracy = update->attribute(QGeoPositionInfo::VerticalAccuracy); + else if (!qIsNaN(m_verticalAccuracy)) + update->setAttribute(QGeoPositionInfo::VerticalAccuracy, m_verticalAccuracy); + + if (hasFix && update->isValid()) { + if (m_requestTimer && m_requestTimer->isActive()) { + m_requestTimer->stop(); + emitUpdated(*update); + } else if (m_invokedStart) { + if (m_updateTimer && m_updateTimer->isActive()) { + // for periodic updates, only want the most recent update + m_pendingUpdate = *update; + if (m_noUpdateLastInterval) { + emitPendingUpdate(); + m_noUpdateLastInterval = false; + } + } else { + emitUpdated(*update); + } + } + m_lastUpdate = *update; + } +} + +void QNmeaPositionInfoSourcePrivate::timerEvent(QTimerEvent *) +{ + emitPendingUpdate(); +} + +void QNmeaPositionInfoSourcePrivate::emitPendingUpdate() +{ + if (m_pendingUpdate.isValid()) { + m_updateTimeoutSent = false; + m_noUpdateLastInterval = false; + emitUpdated(m_pendingUpdate); + m_pendingUpdate = QGeoPositionInfo(); + } else { + if (m_noUpdateLastInterval && !m_updateTimeoutSent) { + m_updateTimeoutSent = true; + m_pendingUpdate = QGeoPositionInfo(); + emit m_source->updateTimeout(); + } + m_noUpdateLastInterval = true; + } +} + +void QNmeaPositionInfoSourcePrivate::emitUpdated(const QGeoPositionInfo &update) +{ + m_lastUpdate = update; + emit m_source->positionUpdated(update); +} + +//========================================================= + +/*! + \class QNmeaPositionInfoSource + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.2 + + \brief The QNmeaPositionInfoSource class provides positional information using a NMEA data source. + + NMEA is a commonly used protocol for the specification of one's global + position at a certain point in time. The QNmeaPositionInfoSource class reads NMEA + data and uses it to provide positional data in the form of + QGeoPositionInfo objects. + + A QNmeaPositionInfoSource instance operates in either \l {RealTimeMode} or + \l {SimulationMode}. These modes allow NMEA data to be read from either a + live source of positional data, or replayed for simulation purposes from + previously recorded NMEA data. + + The source of NMEA data is set with setDevice(). + + Use startUpdates() to start receiving regular position updates and stopUpdates() to stop these + updates. If you only require updates occasionally, you can call requestUpdate() to request a + single update. + + In both cases the position information is received via the positionUpdated() signal and the + last known position can be accessed with lastKnownPosition(). + + QNmeaPositionInfoSource supports reporting the accuracy of the horizontal and vertical position. + To enable position accuracy reporting an estimate of the User Equivalent Range Error associated + with the NMEA source must be set with setUserEquivalentRangeError(). +*/ + + +/*! + \enum QNmeaPositionInfoSource::UpdateMode + Defines the available update modes. + + \value RealTimeMode Positional data is read and distributed from the data source as it becomes available. Use this mode if you are using a live source of positional data (for example, a GPS hardware device). + \value SimulationMode The data and time information in the NMEA source data is used to provide positional updates at the rate at which the data was originally recorded. Use this mode if the data source contains previously recorded NMEA data and you want to replay the data for simulation purposes. +*/ + + +/*! + Constructs a QNmeaPositionInfoSource instance with the given \a parent + and \a updateMode. +*/ +QNmeaPositionInfoSource::QNmeaPositionInfoSource(UpdateMode updateMode, QObject *parent) + : QGeoPositionInfoSource(parent), + d(new QNmeaPositionInfoSourcePrivate(this, updateMode)) +{ +} + +/*! + Destroys the position source. +*/ +QNmeaPositionInfoSource::~QNmeaPositionInfoSource() +{ + delete d; +} + +/*! + Sets the User Equivalent Range Error (UERE) to \a uere. The UERE is used in calculating an + estimate of the accuracy of the position information reported by the position info source. The + UERE should be set to a value appropriate for the GPS device which generated the NMEA stream. + + The true UERE value is calculated from multiple error sources including errors introduced by + the satellites and signal propogation delays through the atmosphere as well as errors + introduced by the receiving GPS equipment. For details on GPS accuracy see + \l {http://edu-observatory.org/gps/gps_accuracy.html}. + + A typical value for UERE is approximately 5.1. + + \since 5.3 + + \sa userEquivalentRangeError() +*/ +void QNmeaPositionInfoSource::setUserEquivalentRangeError(double uere) +{ + d->m_userEquivalentRangeError = uere; +} + +/*! + Returns the current User Equivalent Range Error (UERE). The UERE is used in calculating an + estimate of the accuracy of the position information reported by the position info source. The + default value is NaN which means no accuracy information will be provided. + + \since 5.3 + + \sa setUserEquivalentRangeError() +*/ +double QNmeaPositionInfoSource::userEquivalentRangeError() const +{ + return d->m_userEquivalentRangeError; +} + +/*! + Parses an NMEA sentence string into a QGeoPositionInfo. + + The default implementation will parse standard NMEA sentences. + This method should be reimplemented in a subclass whenever the need to deal with non-standard + NMEA sentences arises. + + The parser reads \a size bytes from \a data and uses that information to setup \a posInfo and + \a hasFix. If \a hasFix is set to false then \a posInfo may contain only the time or the date + and the time. + + Returns true if the sentence was succsesfully parsed, otherwise returns false and should not + modifiy \a posInfo or \a hasFix. +*/ +bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(const char *data, int size, + QGeoPositionInfo *posInfo, bool *hasFix) +{ + return QLocationUtils::getPosInfoFromNmea(data, size, posInfo, d->m_userEquivalentRangeError, + hasFix); +} + +/*! + Returns the update mode. +*/ +QNmeaPositionInfoSource::UpdateMode QNmeaPositionInfoSource::updateMode() const +{ + return d->m_updateMode; +} + +/*! + Sets the NMEA data source to \a device. If the device is not open, it + will be opened in QIODevice::ReadOnly mode. + + The source device can only be set once and must be set before calling + startUpdates() or requestUpdate(). + + \b {Note:} The \a device must emit QIODevice::readyRead() for the + source to be notified when data is available for reading. + QNmeaPositionInfoSource does not assume the ownership of the device, + and hence does not deallocate it upon destruction. +*/ +void QNmeaPositionInfoSource::setDevice(QIODevice *device) +{ + if (device != d->m_device) { + if (!d->m_device) + d->m_device = device; + else + qWarning("QNmeaPositionInfoSource: source device has already been set"); + } +} + +/*! + Returns the NMEA data source. +*/ +QIODevice *QNmeaPositionInfoSource::device() const +{ + return d->m_device; +} + +/*! + \reimp +*/ +void QNmeaPositionInfoSource::setUpdateInterval(int msec) +{ + int interval = msec; + if (interval != 0) + interval = qMax(msec, minimumUpdateInterval()); + QGeoPositionInfoSource::setUpdateInterval(interval); + if (d->m_invokedStart) { + d->stopUpdates(); + d->startUpdates(); + } +} + +/*! + \reimp +*/ +void QNmeaPositionInfoSource::startUpdates() +{ + d->startUpdates(); +} + +/*! + \reimp +*/ +void QNmeaPositionInfoSource::stopUpdates() +{ + d->stopUpdates(); +} + +/*! + \reimp +*/ +void QNmeaPositionInfoSource::requestUpdate(int msec) +{ + d->requestUpdate(msec == 0 ? 60000 * 5 : msec); +} + +/*! + \reimp +*/ +QGeoPositionInfo QNmeaPositionInfoSource::lastKnownPosition(bool) const +{ + // the bool value does not matter since we only use satellite positioning + return d->m_lastUpdate; +} + +/*! + \reimp +*/ +QGeoPositionInfoSource::PositioningMethods QNmeaPositionInfoSource::supportedPositioningMethods() const +{ + return SatellitePositioningMethods; +} + +/*! + \reimp +*/ +int QNmeaPositionInfoSource::minimumUpdateInterval() const +{ + return 100; +} + +/*! + \reimp +*/ +QGeoPositionInfoSource::Error QNmeaPositionInfoSource::error() const +{ + return d->m_positionError; +} + +void QNmeaPositionInfoSource::setError(QGeoPositionInfoSource::Error positionError) +{ + d->m_positionError = positionError; + emit QGeoPositionInfoSource::error(positionError); +} + +#include "moc_qnmeapositioninfosource.cpp" +#include "moc_qnmeapositioninfosource_p.cpp" + +QT_END_NAMESPACE diff --git a/src/positioning/qnmeapositioninfosource.h b/src/positioning/qnmeapositioninfosource.h new file mode 100644 index 0000000..d951f57 --- /dev/null +++ b/src/positioning/qnmeapositioninfosource.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QNMEAPOSITIONINFOSOURCE_H +#define QNMEAPOSITIONINFOSOURCE_H + +#include + +QT_BEGIN_NAMESPACE + +class QIODevice; + +class QNmeaPositionInfoSourcePrivate; +class Q_POSITIONING_EXPORT QNmeaPositionInfoSource : public QGeoPositionInfoSource +{ + Q_OBJECT +public: + enum UpdateMode { + RealTimeMode = 1, + SimulationMode + }; + + explicit QNmeaPositionInfoSource(UpdateMode updateMode, QObject *parent = Q_NULLPTR); + ~QNmeaPositionInfoSource(); + + void setUserEquivalentRangeError(double uere); + double userEquivalentRangeError() const; + + UpdateMode updateMode() const; + + void setDevice(QIODevice *source); + QIODevice *device() const; + + void setUpdateInterval(int msec); + + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const; + PositioningMethods supportedPositioningMethods() const; + int minimumUpdateInterval() const; + Error error() const; + + +public Q_SLOTS: + void startUpdates(); + void stopUpdates(); + void requestUpdate(int timeout = 0); + +protected: + virtual bool parsePosInfoFromNmeaData(const char *data, + int size, + QGeoPositionInfo *posInfo, + bool *hasFix); + +private: + Q_DISABLE_COPY(QNmeaPositionInfoSource) + friend class QNmeaPositionInfoSourcePrivate; + QNmeaPositionInfoSourcePrivate *d; + void setError(QGeoPositionInfoSource::Error positionError); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qnmeapositioninfosource_p.h b/src/positioning/qnmeapositioninfosource_p.h new file mode 100644 index 0000000..056de90 --- /dev/null +++ b/src/positioning/qnmeapositioninfosource_p.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QNMEAPOSITIONINFOSOURCE_P_H +#define QNMEAPOSITIONINFOSOURCE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qnmeapositioninfosource.h" +#include "qgeopositioninfo.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QBasicTimer; +class QTimerEvent; +class QTimer; + +class QNmeaReader; +struct QPendingGeoPositionInfo +{ + QGeoPositionInfo info; + bool hasFix; +}; + + +class QNmeaPositionInfoSourcePrivate : public QObject +{ + Q_OBJECT +public: + QNmeaPositionInfoSourcePrivate(QNmeaPositionInfoSource *parent, QNmeaPositionInfoSource::UpdateMode updateMode); + ~QNmeaPositionInfoSourcePrivate(); + + void startUpdates(); + void stopUpdates(); + void requestUpdate(int msec); + + bool parsePosInfoFromNmeaData(const char *data, + int size, + QGeoPositionInfo *posInfo, + bool *hasFix); + + void notifyNewUpdate(QGeoPositionInfo *update, bool fixStatus); + + QNmeaPositionInfoSource::UpdateMode m_updateMode; + QPointer m_device; + QGeoPositionInfo m_lastUpdate; + bool m_invokedStart; + QGeoPositionInfoSource::Error m_positionError; + double m_userEquivalentRangeError; + +public Q_SLOTS: + void readyRead(); + +protected: + void timerEvent(QTimerEvent *event); + +private Q_SLOTS: + void emitPendingUpdate(); + void sourceDataClosed(); + void updateRequestTimeout(); + +private: + bool openSourceDevice(); + bool initialize(); + void prepareSourceDevice(); + void emitUpdated(const QGeoPositionInfo &update); + + QNmeaPositionInfoSource *m_source; + QNmeaReader *m_nmeaReader; + QBasicTimer *m_updateTimer; + QGeoPositionInfo m_pendingUpdate; + QDate m_currentDate; + QTimer *m_requestTimer; + qreal m_horizontalAccuracy; + qreal m_verticalAccuracy; + bool m_noUpdateLastInterval; + bool m_updateTimeoutSent; + bool m_connectedReadyRead; +}; + + +class QNmeaReader +{ +public: + explicit QNmeaReader(QNmeaPositionInfoSourcePrivate *sourcePrivate) + : m_proxy(sourcePrivate) {} + virtual ~QNmeaReader() {} + + virtual void readAvailableData() = 0; + +protected: + QNmeaPositionInfoSourcePrivate *m_proxy; +}; + + +class QNmeaRealTimeReader : public QNmeaReader +{ +public: + explicit QNmeaRealTimeReader(QNmeaPositionInfoSourcePrivate *sourcePrivate); + virtual void readAvailableData(); +}; + + +class QNmeaSimulatedReader : public QObject, public QNmeaReader +{ + Q_OBJECT +public: + explicit QNmeaSimulatedReader(QNmeaPositionInfoSourcePrivate *sourcePrivate); + ~QNmeaSimulatedReader(); + virtual void readAvailableData(); + +protected: + virtual void timerEvent(QTimerEvent *event); + +private Q_SLOTS: + void simulatePendingUpdate(); + +private: + bool setFirstDateTime(); + void processNextSentence(); + + QQueue m_pendingUpdates; + int m_currTimerId; + bool m_hasValidDateTime; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/positioning/qpositioningglobal.h b/src/positioning/qpositioningglobal.h new file mode 100644 index 0000000..ea4de29 --- /dev/null +++ b/src/positioning/qpositioningglobal.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QPOSITIONINGGLOBAL_H +#define QPOSITIONINGGLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QT_BUILD_POSITIONING_LIB) +# define Q_POSITIONING_EXPORT Q_DECL_EXPORT +# else +# define Q_POSITIONING_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_POSITIONING_EXPORT +#endif + + +QT_END_NAMESPACE + +#endif // QPOSITIONINGGLOBAL_H + diff --git a/src/positioning/qpositioningglobal_p.h b/src/positioning/qpositioningglobal_p.h new file mode 100644 index 0000000..747e450 --- /dev/null +++ b/src/positioning/qpositioningglobal_p.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QPOSITIONINGGLOBAL_P_H +#define QPOSITIONINGGLOBAL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qpositioningglobal.h" + +QT_BEGIN_NAMESPACE + +#define Q_POSITIONING_PRIVATE_EXPORT Q_POSITIONING_EXPORT + +QT_END_NAMESPACE + +#endif // QPOSITIONINGGLOBAL_P_H + diff --git a/src/src.pro b/src/src.pro new file mode 100644 index 0000000..4b9c498 --- /dev/null +++ b/src/src.pro @@ -0,0 +1,33 @@ +TEMPLATE = subdirs + +SUBDIRS += positioning +plugins.depends += positioning + +qtHaveModule(quick) { + SUBDIRS += 3rdparty + + SUBDIRS += location + location.depends += positioning 3rdparty + plugins.depends += location + + SUBDIRS += imports + imports.depends += positioning location +} + +SUBDIRS += plugins + +!android:contains(QT_CONFIG, private_tests) { + SUBDIRS += positioning_doc_snippets + positioning_doc_snippets.subdir = positioning/doc/snippets + + #plugin dependency required during static builds + positioning_doc_snippets.depends = positioning plugins + + qtHaveModule(quick) { + SUBDIRS += location_doc_snippets + location_doc_snippets.subdir = location/doc/snippets + + #plugin dependency required during static builds + location_doc_snippets.depends = location plugins + } +} diff --git a/sync.profile b/sync.profile new file mode 100644 index 0000000..fa9885a --- /dev/null +++ b/sync.profile @@ -0,0 +1,20 @@ +%modules = ( # path to module name map + "QtLocation" => "$basedir/src/location", + "QtPositioning" => "$basedir/src/positioning", +); +%moduleheaders = ( # restrict the module headers to those found in relative path +); +# Module dependencies. +# Every module that is required to build this module should have one entry. +# Each of the module version specifiers can take one of the following values: +# - A specific Git revision. +# - any git symbolic ref resolvable from the module's repository (e.g. "refs/heads/master" to track master branch) +# - an empty string to use the same branch under test (dependencies will become "refs/heads/master" if we are in the master branch) +# +%dependencies = ( + "qtbase" => "", + "qtxmlpatterns" => "", + "qtdeclarative" => "", + "qtquickcontrols" => "", + "qtserialport" => "", +); diff --git a/tests/applications/positioning_backend/main.cpp b/tests/applications/positioning_backend/main.cpp new file mode 100644 index 0000000..930f1c9 --- /dev/null +++ b/tests/applications/positioning_backend/main.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "widget.h" +#include + +#include +#include +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + Widget *w1 = new Widget; + Widget *w2 = new Widget; + + QTabWidget tabWidget; + tabWidget.setTabPosition(QTabWidget::South); + + tabWidget.addTab(w1, "Instance 1"); + tabWidget.addTab(w2, "Instance 2"); + + tabWidget.show(); + return a.exec(); +} diff --git a/tests/applications/positioning_backend/positioning_backend.pro b/tests/applications/positioning_backend/positioning_backend.pro new file mode 100644 index 0000000..410dbc8 --- /dev/null +++ b/tests/applications/positioning_backend/positioning_backend.pro @@ -0,0 +1,14 @@ +QT += core gui positioning widgets + +TARGET = posbackendtesting +TEMPLATE = app + + +SOURCES += main.cpp\ + widget.cpp + +HEADERS += widget.h + +FORMS += widget.ui + +winrt: WINRT_MANIFEST.capabilities_device += location diff --git a/tests/applications/positioning_backend/widget.cpp b/tests/applications/positioning_backend/widget.cpp new file mode 100644 index 0000000..ecf9b7e --- /dev/null +++ b/tests/applications/positioning_backend/widget.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "widget.h" +#include "ui_widget.h" +#include +#include + +Widget::Widget(QWidget *parent) : + QWidget(parent), + ui(new Ui::Widget) +{ + ui->setupUi(this); + qDebug() << "Available:" << QGeoPositionInfoSource::availableSources(); + m_posSource = QGeoPositionInfoSource::createDefaultSource(this); + if (!m_posSource) + qFatal("No Position Source created!"); + connect(m_posSource, SIGNAL(positionUpdated(QGeoPositionInfo)), + this, SLOT(positionUpdated(QGeoPositionInfo))); + + connect(ui->horizontalSlider, SIGNAL(valueChanged(int)), + this, SLOT(setInterval(int))); + connect(m_posSource, SIGNAL(updateTimeout()), + this, SLOT(positionTimedOut())); + + ui->groupBox->setLayout(ui->gridLayout); + ui->horizontalSlider->setMinimum(m_posSource->minimumUpdateInterval()); + ui->labelTimeOut->setVisible(false); + + connect(m_posSource, SIGNAL(error(QGeoPositionInfoSource::Error)), + this, SLOT(errorChanged(QGeoPositionInfoSource::Error))); +} + +void Widget::positionUpdated(QGeoPositionInfo gpsPos) +{ + QGeoCoordinate coord = gpsPos.coordinate(); + ui->labelLatitude->setText(QString::number(coord.latitude())); + ui->labelLongitude->setText(QString::number(coord.longitude())); + ui->labelAltitude->setText(QString::number(coord.altitude())); + ui->labelTimeStamp->setText(gpsPos.timestamp().toString()); + if (gpsPos.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) + ui->labelHAccuracy->setText(QString::number(gpsPos.attribute(QGeoPositionInfo::HorizontalAccuracy))); + else + ui->labelHAccuracy->setText(QStringLiteral("N/A")); + + if (gpsPos.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) + ui->labelVAccuracy->setText(QString::number(gpsPos.attribute(QGeoPositionInfo::VerticalAccuracy))); + else + ui->labelVAccuracy->setText(QStringLiteral("N/A")); +} + +void Widget::positionTimedOut() +{ + ui->labelTimeOut->setVisible(true); +} + +void Widget::errorChanged(QGeoPositionInfoSource::Error err) +{ + ui->labelErrorState->setText(err == 3 ? QStringLiteral("OK") : QString::number(err)); +} + +Widget::~Widget() +{ + delete ui; +} + +void Widget::setInterval(int msec) +{ + m_posSource->setUpdateInterval(msec); +} + +void Widget::on_buttonRetrieve_clicked() +{ + // Requesting current position for _one_ time + m_posSource->requestUpdate(10000); +} + +void Widget::on_buttonStart_clicked() +{ + // Either start or stop the current position info source + bool running = ui->checkBox->isChecked(); + if (running) { + m_posSource->stopUpdates(); + ui->checkBox->setChecked(false); + } else { + m_posSource->startUpdates(); + ui->checkBox->setChecked(true); + } +} + +void Widget::on_radioButton_clicked() +{ + m_posSource->setPreferredPositioningMethods(QGeoPositionInfoSource::NoPositioningMethods); +} + +void Widget::on_radioButton_2_clicked() +{ + m_posSource->setPreferredPositioningMethods(QGeoPositionInfoSource::SatellitePositioningMethods); +} + +void Widget::on_radioButton_3_clicked() +{ + m_posSource->setPreferredPositioningMethods(QGeoPositionInfoSource::NonSatellitePositioningMethods); +} + +void Widget::on_radioButton_4_clicked() +{ + m_posSource->setPreferredPositioningMethods(QGeoPositionInfoSource::AllPositioningMethods); +} + +void Widget::on_buttonUpdateSupported_clicked() +{ + QGeoPositionInfoSource::PositioningMethods m = m_posSource->supportedPositioningMethods(); + QString text; + switch (m) { + case QGeoPositionInfoSource::NoPositioningMethods: + text = QStringLiteral("None"); + break; + case QGeoPositionInfoSource::SatellitePositioningMethods: + text = QStringLiteral("Satellite"); + break; + case QGeoPositionInfoSource::NonSatellitePositioningMethods: + text = QStringLiteral("Non Satellite"); + break; + case QGeoPositionInfoSource::AllPositioningMethods: + text = QStringLiteral("All"); + break; + } + + ui->labelSupported->setText(text); +} diff --git a/tests/applications/positioning_backend/widget.h b/tests/applications/positioning_backend/widget.h new file mode 100644 index 0000000..fc04c42 --- /dev/null +++ b/tests/applications/positioning_backend/widget.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef WIDGET_H +#define WIDGET_H + +#include +#include + +namespace Ui { + class Widget; +} + +class Widget : public QWidget +{ + Q_OBJECT + +public: + explicit Widget(QWidget *parent = 0); + ~Widget(); + +public slots: + void positionUpdated(QGeoPositionInfo gpsPos); + void setInterval(int msec); + void positionTimedOut(); + void errorChanged(QGeoPositionInfoSource::Error err); +private slots: + void on_buttonRetrieve_clicked(); + void on_buttonStart_clicked(); + void on_radioButton_2_clicked(); + void on_radioButton_clicked(); + void on_radioButton_3_clicked(); + void on_radioButton_4_clicked(); + + void on_buttonUpdateSupported_clicked(); + +private: + Ui::Widget *ui; + QGeoPositionInfoSource *m_posSource; +}; + +#endif // WIDGET_H diff --git a/tests/applications/positioning_backend/widget.ui b/tests/applications/positioning_backend/widget.ui new file mode 100644 index 0000000..d19497d --- /dev/null +++ b/tests/applications/positioning_backend/widget.ui @@ -0,0 +1,295 @@ + + + Widget + + + + 0 + 0 + 276 + 467 + + + + Widget + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Latitude: + + + + + + + N/A + + + + + + + Longitude: + + + + + + + N/A + + + + + + + Altitude: + + + + + + + N/A + + + + + + + TimeStamp: + + + + + + + N/A + + + + + + + Horizontal Accuracy: + + + + + + + N/A + + + + + + + Vertical Accuracy: + + + + + + + N/A + + + + + + + TimeOut: + + + + + + + true + + + !!!!!TimeOut!!!!! + + + + + + + Supported Methods: + + + + + + + Error State: + + + + + + + N/A + + + + + + + + + N/A + + + + + + + Update + + + + + + + + + + + Method + + + + + 43 + 21 + 243 + 71 + + + + + + + None + + + + + + + Satelite + + + true + + + + + + + Non-Satelite + + + + + + + All + + + + + + + + + + + + + Interval: + + + + + + + 50 + + + 10000 + + + Qt::Horizontal + + + + + + + 0 + + + + + + + + + false + + + Running + + + + + + + + + Start/Stop + + + + + + + Retrieve + + + + + + + + + + + + horizontalSlider + valueChanged(int) + labelInterval + setNum(int) + + + 217 + 137 + + + 386 + 138 + + + + + diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro new file mode 100644 index 0000000..e3236de --- /dev/null +++ b/tests/auto/auto.pro @@ -0,0 +1,83 @@ +TEMPLATE = subdirs + +qtHaveModule(location) { + + #Place unit tests + SUBDIRS += qplace \ + qplaceattribute \ + qplacecategory \ + qplacecontactdetail \ + qplacecontentrequest \ + qplacedetailsreply \ + qplaceeditorial \ + qplacematchreply \ + qplacematchrequest \ + qplaceimage \ + qplaceratings \ + qplaceresult \ + qproposedsearchresult \ + qplacereply \ + qplacereview \ + qplacesearchrequest \ + qplacesupplier \ + qplacesearchresult \ + qplacesearchreply \ + qplacesearchsuggestionreply \ + qplaceuser \ + qplacemanager \ + qplacemanager_nokia \ + qplacemanager_unsupported \ + placesplugin_unsupported + + #misc tests + SUBDIRS += qmlinterface \ + cmake \ + doublevectors + + #Map and Navigation tests + SUBDIRS += geotestplugin \ + qgeocodingmanagerplugins \ + qgeocameracapabilities\ + qgeocameradata \ + qgeocodereply \ + qgeocodingmanager \ + qgeomaneuver \ + qgeotiledmapscene \ + qgeoroute \ + qgeoroutereply \ + qgeorouterequest \ + qgeoroutesegment \ + qgeoroutingmanager \ + qgeoroutingmanagerplugins \ + qgeoserviceprovider \ + qgeotiledmap \ + qgeotilespec \ + qgeoroutexmlparser \ + maptype \ + nokia_services \ + qgeocameratiles + + qtHaveModule(quick) { + SUBDIRS += declarative_core \ + declarative_geoshape + + !mac: SUBDIRS += declarative_ui + } +} + + +SUBDIRS += \ + positionplugin \ + positionplugintest \ + qgeoaddress \ + qgeoareamonitor \ + qgeoshape \ + qgeorectangle \ + qgeocircle \ + qgeocoordinate \ + qgeolocation \ + qgeopositioninfo \ + qgeopositioninfosource \ + qgeosatelliteinfo \ + qgeosatelliteinfosource \ + qnmeapositioninfosource diff --git a/tests/auto/bic/data/QtPositioning.5.3.0.linux-gcc-amd64.txt b/tests/auto/bic/data/QtPositioning.5.3.0.linux-gcc-amd64.txt new file mode 100644 index 0000000..d29c24a --- /dev/null +++ b/tests/auto/bic/data/QtPositioning.5.3.0.linux-gcc-amd64.txt @@ -0,0 +1,3822 @@ +Class std::__true_type + size=1 align=1 + base size=0 base align=1 +std::__true_type (0x0x7f95f2df8f00) 0 empty + +Class std::__false_type + size=1 align=1 + base size=0 base align=1 +std::__false_type (0x0x7f95f2df8f60) 0 empty + +Class std::input_iterator_tag + size=1 align=1 + base size=0 base align=1 +std::input_iterator_tag (0x0x7f95f1d8fb40) 0 empty + +Class std::output_iterator_tag + size=1 align=1 + base size=0 base align=1 +std::output_iterator_tag (0x0x7f95f1d8fba0) 0 empty + +Class std::forward_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::forward_iterator_tag (0x0x7f95f1d26820) 0 empty + std::input_iterator_tag (0x0x7f95f1d8fc00) 0 empty + +Class std::bidirectional_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::bidirectional_iterator_tag (0x0x7f95f1d26888) 0 empty + std::forward_iterator_tag (0x0x7f95f1d268f0) 0 empty + std::input_iterator_tag (0x0x7f95f1d8fc60) 0 empty + +Class std::random_access_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::random_access_iterator_tag (0x0x7f95f1d26958) 0 empty + std::bidirectional_iterator_tag (0x0x7f95f1d269c0) 0 empty + std::forward_iterator_tag (0x0x7f95f1d26a28) 0 empty + std::input_iterator_tag (0x0x7f95f1d8fcc0) 0 empty + +Class wait + size=4 align=4 + base size=4 base align=4 +wait (0x0x7f95f1de9840) 0 + +Class __locale_struct + size=232 align=8 + base size=232 base align=8 +__locale_struct (0x0x7f95f1de9a80) 0 + +Class timespec + size=16 align=8 + base size=16 base align=8 +timespec (0x0x7f95f1de9b40) 0 + +Class timeval + size=16 align=8 + base size=16 base align=8 +timeval (0x0x7f95f1de9ba0) 0 + +Class pthread_attr_t + size=56 align=8 + base size=56 base align=8 +pthread_attr_t (0x0x7f95f1de9c60) 0 + +Class __pthread_internal_list + size=16 align=8 + base size=16 base align=8 +__pthread_internal_list (0x0x7f95f1de9cc0) 0 + +Class random_data + size=48 align=8 + base size=48 base align=8 +random_data (0x0x7f95f1eab180) 0 + +Class drand48_data + size=24 align=8 + base size=24 base align=8 +drand48_data (0x0x7f95f1eab1e0) 0 + +Vtable for std::exception +std::exception::_ZTVSt9exception: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt9exception) +16 (int (*)(...))std::exception::~exception +24 (int (*)(...))std::exception::~exception +32 (int (*)(...))std::exception::what + +Class std::exception + size=8 align=8 + base size=8 base align=8 +std::exception (0x0x7f95f1eab240) 0 nearly-empty + vptr=((& std::exception::_ZTVSt9exception) + 16u) + +Vtable for std::bad_exception +std::bad_exception::_ZTVSt13bad_exception: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt13bad_exception) +16 (int (*)(...))std::bad_exception::~bad_exception +24 (int (*)(...))std::bad_exception::~bad_exception +32 (int (*)(...))std::bad_exception::what + +Class std::bad_exception + size=8 align=8 + base size=8 base align=8 +std::bad_exception (0x0x7f95f1d26d68) 0 nearly-empty + vptr=((& std::bad_exception::_ZTVSt13bad_exception) + 16u) + std::exception (0x0x7f95f1eab2a0) 0 nearly-empty + primary-for std::bad_exception (0x0x7f95f1d26d68) + +Vtable for std::bad_alloc +std::bad_alloc::_ZTVSt9bad_alloc: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt9bad_alloc) +16 (int (*)(...))std::bad_alloc::~bad_alloc +24 (int (*)(...))std::bad_alloc::~bad_alloc +32 (int (*)(...))std::bad_alloc::what + +Class std::bad_alloc + size=8 align=8 + base size=8 base align=8 +std::bad_alloc (0x0x7f95f1d26dd0) 0 nearly-empty + vptr=((& std::bad_alloc::_ZTVSt9bad_alloc) + 16u) + std::exception (0x0x7f95f1eab300) 0 nearly-empty + primary-for std::bad_alloc (0x0x7f95f1d26dd0) + +Class std::nothrow_t + size=1 align=1 + base size=0 base align=1 +std::nothrow_t (0x0x7f95f1eab360) 0 empty + +Class qIsNull(double)::U + size=8 align=8 + base size=8 base align=8 +qIsNull(double)::U (0x0x7f95f0cb36c0) 0 + +Class qIsNull(float)::U + size=4 align=4 + base size=4 base align=4 +qIsNull(float)::U (0x0x7f95f0cb3720) 0 + +Class QtPrivate::big_ + size=2 align=1 + base size=2 base align=1 +QtPrivate::big_ (0x0x7f95f0cb3900) 0 + +Class QSysInfo + size=1 align=1 + base size=0 base align=1 +QSysInfo (0x0x7f95f0a18060) 0 empty + +Class QMessageLogContext + size=32 align=8 + base size=32 base align=8 +QMessageLogContext (0x0x7f95f0a180c0) 0 + +Class QMessageLogger + size=32 align=8 + base size=32 base align=8 +QMessageLogger (0x0x7f95f0a18120) 0 + +Class QFlag + size=4 align=4 + base size=4 base align=4 +QFlag (0x0x7f95f0a18180) 0 + +Class QIncompatibleFlag + size=4 align=4 + base size=4 base align=4 +QIncompatibleFlag (0x0x7f95f0a182a0) 0 + +Class QAtomicInt + size=4 align=4 + base size=4 base align=4 +QAtomicInt (0x0x7f95f06ee3a8) 0 + QAtomicInteger (0x0x7f95f06ee410) 0 + QBasicAtomicInteger (0x0x7f95f0a18d80) 0 + +Class QInternal + size=1 align=1 + base size=0 base align=1 +QInternal (0x0x7f95f04f5ea0) 0 empty + +Class QGenericArgument + size=16 align=8 + base size=16 base align=8 +QGenericArgument (0x0x7f95f069fde0) 0 + +Class QGenericReturnArgument + size=16 align=8 + base size=16 base align=8 +QGenericReturnArgument (0x0x7f95f05ca548) 0 + QGenericArgument (0x0x7f95f069fe40) 0 + +Class QMetaObject + size=48 align=8 + base size=48 base align=8 +QMetaObject (0x0x7f95f0304000) 0 + +Class QMetaObject::Connection + size=8 align=8 + base size=8 base align=8 +QMetaObject::Connection (0x0x7f95f0304120) 0 + +Class QLatin1Char + size=1 align=1 + base size=1 base align=1 +QLatin1Char (0x0x7f95f0304360) 0 + +Class QChar + size=2 align=2 + base size=2 base align=2 +QChar (0x0x7f95f03043c0) 0 + +Class QtPrivate::RefCount + size=4 align=4 + base size=4 base align=4 +QtPrivate::RefCount (0x0x7f95f03044e0) 0 + +Class QArrayData + size=24 align=8 + base size=24 base align=8 +QArrayData (0x0x7f95f0304540) 0 + +Class QByteArrayDataPtr + size=8 align=8 + base size=8 base align=8 +QByteArrayDataPtr (0x0x7f95f03048a0) 0 + +Class QByteArray + size=8 align=8 + base size=8 base align=8 +QByteArray (0x0x7f95f0304900) 0 + +Class QByteRef + size=16 align=8 + base size=12 base align=8 +QByteRef (0x0x7f95f0304a80) 0 + +Class lconv + size=96 align=8 + base size=96 base align=8 +lconv (0x0x7f95f0304ea0) 0 + +Vtable for __cxxabiv1::__forced_unwind +__cxxabiv1::__forced_unwind::_ZTVN10__cxxabiv115__forced_unwindE: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN10__cxxabiv115__forced_unwindE) +16 (int (*)(...))__cxxabiv1::__forced_unwind::~__forced_unwind +24 (int (*)(...))__cxxabiv1::__forced_unwind::~__forced_unwind +32 (int (*)(...))__cxa_pure_virtual + +Class __cxxabiv1::__forced_unwind + size=8 align=8 + base size=8 base align=8 +__cxxabiv1::__forced_unwind (0x0x7f95f0304f00) 0 nearly-empty + vptr=((& __cxxabiv1::__forced_unwind::_ZTVN10__cxxabiv115__forced_unwindE) + 16u) + +Class sched_param + size=4 align=4 + base size=4 base align=4 +sched_param (0x0x7f95f02039c0) 0 + +Class __sched_param + size=4 align=4 + base size=4 base align=4 +__sched_param (0x0x7f95f0203a20) 0 + +Class timex + size=208 align=8 + base size=208 base align=8 +timex (0x0x7f95f0203ae0) 0 + +Class tm + size=56 align=8 + base size=56 base align=8 +tm (0x0x7f95f0203b40) 0 + +Class itimerspec + size=32 align=8 + base size=32 base align=8 +itimerspec (0x0x7f95f0203ba0) 0 + +Class _pthread_cleanup_buffer + size=32 align=8 + base size=32 base align=8 +_pthread_cleanup_buffer (0x0x7f95f0203c00) 0 + +Class __pthread_cleanup_frame + size=24 align=8 + base size=24 base align=8 +__pthread_cleanup_frame (0x0x7f95f0203d20) 0 + +Class __pthread_cleanup_class + size=24 align=8 + base size=24 base align=8 +__pthread_cleanup_class (0x0x7f95f0203d80) 0 + +Class QLatin1String + size=16 align=8 + base size=16 base align=8 +QLatin1String (0x0x7f95eff0a4e0) 0 + +Class QStringDataPtr + size=8 align=8 + base size=8 base align=8 +QStringDataPtr (0x0x7f95eff0a660) 0 + +Class QString::Null + size=1 align=1 + base size=0 base align=1 +QString::Null (0x0x7f95eff0a720) 0 empty + +Class QString + size=8 align=8 + base size=8 base align=8 +QString (0x0x7f95eff0a6c0) 0 + +Class QCharRef + size=16 align=8 + base size=12 base align=8 +QCharRef (0x0x7f95eff0a8a0) 0 + +Class QStringRef + size=16 align=8 + base size=16 base align=8 +QStringRef (0x0x7f95eff0ab40) 0 + +Class std::locale + size=8 align=8 + base size=8 base align=8 +std::locale (0x0x7f95eff0ad20) 0 + +Vtable for std::locale::facet +std::locale::facet::_ZTVNSt6locale5facetE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt6locale5facetE) +16 (int (*)(...))std::locale::facet::~facet +24 (int (*)(...))std::locale::facet::~facet + +Class std::locale::facet + size=16 align=8 + base size=12 base align=8 +std::locale::facet (0x0x7f95eff0ad80) 0 + vptr=((& std::locale::facet::_ZTVNSt6locale5facetE) + 16u) + +Class std::locale::id + size=8 align=8 + base size=8 base align=8 +std::locale::id (0x0x7f95eff0ade0) 0 + +Class std::locale::_Impl + size=40 align=8 + base size=40 base align=8 +std::locale::_Impl (0x0x7f95eff0ae40) 0 + +Vtable for std::ios_base::failure +std::ios_base::failure::_ZTVNSt8ios_base7failureE: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt8ios_base7failureE) +16 (int (*)(...))std::ios_base::failure::~failure +24 (int (*)(...))std::ios_base::failure::~failure +32 (int (*)(...))std::ios_base::failure::what + +Class std::ios_base::failure + size=16 align=8 + base size=16 base align=8 +std::ios_base::failure (0x0x7f95eff0e000) 0 + vptr=((& std::ios_base::failure::_ZTVNSt8ios_base7failureE) + 16u) + std::exception (0x0x7f95efbc92a0) 0 nearly-empty + primary-for std::ios_base::failure (0x0x7f95eff0e000) + +Class std::ios_base::_Callback_list + size=24 align=8 + base size=24 base align=8 +std::ios_base::_Callback_list (0x0x7f95efbc9300) 0 + +Class std::ios_base::_Words + size=16 align=8 + base size=16 base align=8 +std::ios_base::_Words (0x0x7f95efbc9360) 0 + +Class std::ios_base::Init + size=1 align=1 + base size=0 base align=1 +std::ios_base::Init (0x0x7f95efbc93c0) 0 empty + +Vtable for std::ios_base +std::ios_base::_ZTVSt8ios_base: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt8ios_base) +16 (int (*)(...))std::ios_base::~ios_base +24 (int (*)(...))std::ios_base::~ios_base + +Class std::ios_base + size=216 align=8 + base size=216 base align=8 +std::ios_base (0x0x7f95efbc9240) 0 + vptr=((& std::ios_base::_ZTVSt8ios_base) + 16u) + +Class std::ctype_base + size=1 align=1 + base size=0 base align=1 +std::ctype_base (0x0x7f95efbc9540) 0 empty + +Class std::__num_base + size=1 align=1 + base size=0 base align=1 +std::__num_base (0x0x7f95efbc9c00) 0 empty + +VTT for std::basic_ostream +std::basic_ostream::_ZTTSo: 2u entries +0 ((& std::basic_ostream::_ZTVSo) + 24u) +8 ((& std::basic_ostream::_ZTVSo) + 64u) + +VTT for std::basic_ostream +std::basic_ostream::_ZTTSt13basic_ostreamIwSt11char_traitsIwEE: 2u entries +0 ((& std::basic_ostream::_ZTVSt13basic_ostreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_ostream::_ZTVSt13basic_ostreamIwSt11char_traitsIwEE) + 64u) + +VTT for std::basic_istream +std::basic_istream::_ZTTSi: 2u entries +0 ((& std::basic_istream::_ZTVSi) + 24u) +8 ((& std::basic_istream::_ZTVSi) + 64u) + +VTT for std::basic_istream +std::basic_istream::_ZTTSt13basic_istreamIwSt11char_traitsIwEE: 2u entries +0 ((& std::basic_istream::_ZTVSt13basic_istreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_istream::_ZTVSt13basic_istreamIwSt11char_traitsIwEE) + 64u) + +Construction vtable for std::basic_istream (0x0x7f95ef810a90 instance) in std::basic_iostream +std::basic_iostream::_ZTCSd0_Si: 10u entries +0 24u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISi) +24 (int (*)(...))std::basic_istream<_CharT, _Traits>::~basic_istream > +32 (int (*)(...))std::basic_istream<_CharT, _Traits>::~basic_istream > +40 18446744073709551592u +48 (int (*)(...))-24 +56 (int (*)(...))(& _ZTISi) +64 (int (*)(...))std::basic_istream::_ZTv0_n24_NSiD1Ev +72 (int (*)(...))std::basic_istream::_ZTv0_n24_NSiD0Ev + +Construction vtable for std::basic_ostream (0x0x7f95ef810b60 instance) in std::basic_iostream +std::basic_iostream::_ZTCSd16_So: 10u entries +0 8u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISo) +24 (int (*)(...))std::basic_ostream<_CharT, _Traits>::~basic_ostream > +32 (int (*)(...))std::basic_ostream<_CharT, _Traits>::~basic_ostream > +40 18446744073709551608u +48 (int (*)(...))-8 +56 (int (*)(...))(& _ZTISo) +64 (int (*)(...))std::basic_ostream::_ZTv0_n24_NSoD1Ev +72 (int (*)(...))std::basic_ostream::_ZTv0_n24_NSoD0Ev + +VTT for std::basic_iostream +std::basic_iostream::_ZTTSd: 7u entries +0 ((& std::basic_iostream::_ZTVSd) + 24u) +8 ((& std::basic_iostream::_ZTCSd0_Si) + 24u) +16 ((& std::basic_iostream::_ZTCSd0_Si) + 64u) +24 ((& std::basic_iostream::_ZTCSd16_So) + 24u) +32 ((& std::basic_iostream::_ZTCSd16_So) + 64u) +40 ((& std::basic_iostream::_ZTVSd) + 104u) +48 ((& std::basic_iostream::_ZTVSd) + 64u) + +Construction vtable for std::basic_istream (0x0x7f95ef810e38 instance) in std::basic_iostream +std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E: 10u entries +0 24u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISt13basic_istreamIwSt11char_traitsIwEE) +24 (int (*)(...))std::basic_istream<_CharT, _Traits>::~basic_istream > +32 (int (*)(...))std::basic_istream<_CharT, _Traits>::~basic_istream > +40 18446744073709551592u +48 (int (*)(...))-24 +56 (int (*)(...))(& _ZTISt13basic_istreamIwSt11char_traitsIwEE) +64 (int (*)(...))std::basic_istream::_ZTv0_n24_NSt13basic_istreamIwSt11char_traitsIwEED1Ev +72 (int (*)(...))std::basic_istream::_ZTv0_n24_NSt13basic_istreamIwSt11char_traitsIwEED0Ev + +Construction vtable for std::basic_ostream (0x0x7f95ef810f08 instance) in std::basic_iostream +std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E: 10u entries +0 8u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISt13basic_ostreamIwSt11char_traitsIwEE) +24 (int (*)(...))std::basic_ostream<_CharT, _Traits>::~basic_ostream > +32 (int (*)(...))std::basic_ostream<_CharT, _Traits>::~basic_ostream > +40 18446744073709551608u +48 (int (*)(...))-8 +56 (int (*)(...))(& _ZTISt13basic_ostreamIwSt11char_traitsIwEE) +64 (int (*)(...))std::basic_ostream::_ZTv0_n24_NSt13basic_ostreamIwSt11char_traitsIwEED1Ev +72 (int (*)(...))std::basic_ostream::_ZTv0_n24_NSt13basic_ostreamIwSt11char_traitsIwEED0Ev + +VTT for std::basic_iostream +std::basic_iostream::_ZTTSt14basic_iostreamIwSt11char_traitsIwEE: 7u entries +0 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E) + 24u) +16 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E) + 64u) +24 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E) + 24u) +32 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E) + 64u) +40 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 104u) +48 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 64u) + +Class std::__detail::_List_node_base + size=16 align=8 + base size=16 base align=8 +std::__detail::_List_node_base (0x0x7f95ef8b0000) 0 + +Class QListData::Data + size=24 align=8 + base size=24 base align=8 +QListData::Data (0x0x7f95ef8b0300) 0 + +Class QListData + size=8 align=8 + base size=8 base align=8 +QListData (0x0x7f95ef8b02a0) 0 + +Class QScopedPointerPodDeleter + size=1 align=1 + base size=0 base align=1 +QScopedPointerPodDeleter (0x0x7f95ef8b06c0) 0 empty + +Class std::_Bit_reference + size=16 align=8 + base size=16 base align=8 +std::_Bit_reference (0x0x7f95ef6a5420) 0 + +Class std::_Bit_iterator_base + size=16 align=8 + base size=12 base align=8 +std::_Bit_iterator_base (0x0x7f95ef67d208) 0 + std::iterator (0x0x7f95ef6a54e0) 0 empty + +Class std::_Bit_iterator + size=16 align=8 + base size=12 base align=8 +std::_Bit_iterator (0x0x7f95ef67d270) 0 + std::_Bit_iterator_base (0x0x7f95ef67d2d8) 0 + std::iterator (0x0x7f95ef6a5540) 0 empty + +Class std::_Bit_const_iterator + size=16 align=8 + base size=12 base align=8 +std::_Bit_const_iterator (0x0x7f95ef67d340) 0 + std::_Bit_iterator_base (0x0x7f95ef67d3a8) 0 + std::iterator (0x0x7f95ef6a55a0) 0 empty + +Class std::_Rb_tree_node_base + size=32 align=8 + base size=32 base align=8 +std::_Rb_tree_node_base (0x0x7f95ef6a5960) 0 + +Class QtPrivate::AbstractDebugStreamFunction + size=16 align=8 + base size=16 base align=8 +QtPrivate::AbstractDebugStreamFunction (0x0x7f95ef6a5d80) 0 + +Class QtPrivate::AbstractComparatorFunction + size=24 align=8 + base size=24 base align=8 +QtPrivate::AbstractComparatorFunction (0x0x7f95ef6a5e40) 0 + +Class QtPrivate::AbstractConverterFunction + size=8 align=8 + base size=8 base align=8 +QtPrivate::AbstractConverterFunction (0x0x7f95ef6a5f00) 0 + +Class QMetaType + size=80 align=8 + base size=80 base align=8 +QMetaType (0x0x7f95ef180360) 0 + +Class QtMetaTypePrivate::VariantData + size=24 align=8 + base size=20 base align=8 +QtMetaTypePrivate::VariantData (0x0x7f95ef1806c0) 0 + +Class QtMetaTypePrivate::QSequentialIterableImpl + size=104 align=8 + base size=104 base align=8 +QtMetaTypePrivate::QSequentialIterableImpl (0x0x7f95ef180ae0) 0 + +Class QtMetaTypePrivate::QAssociativeIterableImpl + size=112 align=8 + base size=112 base align=8 +QtMetaTypePrivate::QAssociativeIterableImpl (0x0x7f95ef180cc0) 0 + +Class QtMetaTypePrivate::QPairVariantInterfaceImpl + size=40 align=8 + base size=40 base align=8 +QtMetaTypePrivate::QPairVariantInterfaceImpl (0x0x7f95ef180d80) 0 + +Class QtPrivate::QSlotObjectBase + size=16 align=8 + base size=16 base align=8 +QtPrivate::QSlotObjectBase (0x0x7f95eeff3060) 0 + +Vtable for QObjectData +QObjectData::_ZTV11QObjectData: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QObjectData) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))__cxa_pure_virtual + +Class QObjectData + size=48 align=8 + base size=48 base align=8 +QObjectData (0x0x7f95eeff31e0) 0 + vptr=((& QObjectData::_ZTV11QObjectData) + 16u) + +Class QObject::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QObject::QPrivateSignal (0x0x7f95eeff3360) 0 empty + +Vtable for QObject +QObject::_ZTV7QObject: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QObject) +16 (int (*)(...))QObject::metaObject +24 (int (*)(...))QObject::qt_metacast +32 (int (*)(...))QObject::qt_metacall +40 (int (*)(...))QObject::~QObject +48 (int (*)(...))QObject::~QObject +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QObject + size=16 align=8 + base size=16 base align=8 +QObject (0x0x7f95eeff3300) 0 + vptr=((& QObject::_ZTV7QObject) + 16u) + +Vtable for QObjectUserData +QObjectUserData::_ZTV15QObjectUserData: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QObjectUserData) +16 (int (*)(...))QObjectUserData::~QObjectUserData +24 (int (*)(...))QObjectUserData::~QObjectUserData + +Class QObjectUserData + size=8 align=8 + base size=8 base align=8 +QObjectUserData (0x0x7f95eeff3660) 0 nearly-empty + vptr=((& QObjectUserData::_ZTV15QObjectUserData) + 16u) + +Class QSignalBlocker + size=16 align=8 + base size=10 base align=8 +QSignalBlocker (0x0x7f95eeff36c0) 0 + +Class QAbstractAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractAnimation::QPrivateSignal (0x0x7f95eeff3780) 0 empty + +Vtable for QAbstractAnimation +QAbstractAnimation::_ZTV18QAbstractAnimation: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractAnimation) +16 (int (*)(...))QAbstractAnimation::metaObject +24 (int (*)(...))QAbstractAnimation::qt_metacast +32 (int (*)(...))QAbstractAnimation::qt_metacall +40 (int (*)(...))QAbstractAnimation::~QAbstractAnimation +48 (int (*)(...))QAbstractAnimation::~QAbstractAnimation +56 (int (*)(...))QAbstractAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QAbstractAnimation + size=16 align=8 + base size=16 base align=8 +QAbstractAnimation (0x0x7f95ef204dd0) 0 + vptr=((& QAbstractAnimation::_ZTV18QAbstractAnimation) + 16u) + QObject (0x0x7f95eeff3720) 0 + primary-for QAbstractAnimation (0x0x7f95ef204dd0) + +Class QAnimationDriver::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAnimationDriver::QPrivateSignal (0x0x7f95eeff3840) 0 empty + +Vtable for QAnimationDriver +QAnimationDriver::_ZTV16QAnimationDriver: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QAnimationDriver) +16 (int (*)(...))QAnimationDriver::metaObject +24 (int (*)(...))QAnimationDriver::qt_metacast +32 (int (*)(...))QAnimationDriver::qt_metacall +40 (int (*)(...))QAnimationDriver::~QAnimationDriver +48 (int (*)(...))QAnimationDriver::~QAnimationDriver +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAnimationDriver::advance +120 (int (*)(...))QAnimationDriver::elapsed +128 (int (*)(...))QAnimationDriver::start +136 (int (*)(...))QAnimationDriver::stop + +Class QAnimationDriver + size=16 align=8 + base size=16 base align=8 +QAnimationDriver (0x0x7f95ef204e38) 0 + vptr=((& QAnimationDriver::_ZTV16QAnimationDriver) + 16u) + QObject (0x0x7f95eeff37e0) 0 + primary-for QAnimationDriver (0x0x7f95ef204e38) + +Class QAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAnimationGroup::QPrivateSignal (0x0x7f95eeff3900) 0 empty + +Vtable for QAnimationGroup +QAnimationGroup::_ZTV15QAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QAnimationGroup) +16 (int (*)(...))QAnimationGroup::metaObject +24 (int (*)(...))QAnimationGroup::qt_metacast +32 (int (*)(...))QAnimationGroup::qt_metacall +40 (int (*)(...))QAnimationGroup::~QAnimationGroup +48 (int (*)(...))QAnimationGroup::~QAnimationGroup +56 (int (*)(...))QAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QAnimationGroup + size=16 align=8 + base size=16 base align=8 +QAnimationGroup (0x0x7f95ef204ea0) 0 + vptr=((& QAnimationGroup::_ZTV15QAnimationGroup) + 16u) + QAbstractAnimation (0x0x7f95ef204f08) 0 + primary-for QAnimationGroup (0x0x7f95ef204ea0) + QObject (0x0x7f95eeff38a0) 0 + primary-for QAbstractAnimation (0x0x7f95ef204f08) + +Class QParallelAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QParallelAnimationGroup::QPrivateSignal (0x0x7f95eeff39c0) 0 empty + +Vtable for QParallelAnimationGroup +QParallelAnimationGroup::_ZTV23QParallelAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QParallelAnimationGroup) +16 (int (*)(...))QParallelAnimationGroup::metaObject +24 (int (*)(...))QParallelAnimationGroup::qt_metacast +32 (int (*)(...))QParallelAnimationGroup::qt_metacall +40 (int (*)(...))QParallelAnimationGroup::~QParallelAnimationGroup +48 (int (*)(...))QParallelAnimationGroup::~QParallelAnimationGroup +56 (int (*)(...))QParallelAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QParallelAnimationGroup::duration +120 (int (*)(...))QParallelAnimationGroup::updateCurrentTime +128 (int (*)(...))QParallelAnimationGroup::updateState +136 (int (*)(...))QParallelAnimationGroup::updateDirection + +Class QParallelAnimationGroup + size=16 align=8 + base size=16 base align=8 +QParallelAnimationGroup (0x0x7f95ef204f70) 0 + vptr=((& QParallelAnimationGroup::_ZTV23QParallelAnimationGroup) + 16u) + QAnimationGroup (0x0x7f95eecee000) 0 + primary-for QParallelAnimationGroup (0x0x7f95ef204f70) + QAbstractAnimation (0x0x7f95eecee068) 0 + primary-for QAnimationGroup (0x0x7f95eecee000) + QObject (0x0x7f95eeff3960) 0 + primary-for QAbstractAnimation (0x0x7f95eecee068) + +Class QPauseAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPauseAnimation::QPrivateSignal (0x0x7f95eeff3a80) 0 empty + +Vtable for QPauseAnimation +QPauseAnimation::_ZTV15QPauseAnimation: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QPauseAnimation) +16 (int (*)(...))QPauseAnimation::metaObject +24 (int (*)(...))QPauseAnimation::qt_metacast +32 (int (*)(...))QPauseAnimation::qt_metacall +40 (int (*)(...))QPauseAnimation::~QPauseAnimation +48 (int (*)(...))QPauseAnimation::~QPauseAnimation +56 (int (*)(...))QPauseAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QPauseAnimation::duration +120 (int (*)(...))QPauseAnimation::updateCurrentTime +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QPauseAnimation + size=16 align=8 + base size=16 base align=8 +QPauseAnimation (0x0x7f95eecee0d0) 0 + vptr=((& QPauseAnimation::_ZTV15QPauseAnimation) + 16u) + QAbstractAnimation (0x0x7f95eecee138) 0 + primary-for QPauseAnimation (0x0x7f95eecee0d0) + QObject (0x0x7f95eeff3a20) 0 + primary-for QAbstractAnimation (0x0x7f95eecee138) + +Class QEasingCurve + size=8 align=8 + base size=8 base align=8 +QEasingCurve (0x0x7f95eeff3c60) 0 + +Class QMapNodeBase + size=24 align=8 + base size=24 base align=8 +QMapNodeBase (0x0x7f95eeff3e40) 0 + +Class QMapDataBase + size=40 align=8 + base size=40 base align=8 +QMapDataBase (0x0x7f95eeff3f00) 0 + +Class QHashData::Node + size=16 align=8 + base size=16 base align=8 +QHashData::Node (0x0x7f95eee232a0) 0 + +Class QHashData + size=48 align=8 + base size=48 base align=8 +QHashData (0x0x7f95eee23240) 0 + +Class QHashDummyValue + size=1 align=1 + base size=0 base align=1 +QHashDummyValue (0x0x7f95eee23300) 0 empty + +Class QIODevice::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QIODevice::QPrivateSignal (0x0x7f95eee237e0) 0 empty + +Vtable for QIODevice +QIODevice::_ZTV9QIODevice: 30u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QIODevice) +16 (int (*)(...))QIODevice::metaObject +24 (int (*)(...))QIODevice::qt_metacast +32 (int (*)(...))QIODevice::qt_metacall +40 (int (*)(...))QIODevice::~QIODevice +48 (int (*)(...))QIODevice::~QIODevice +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QIODevice::isSequential +120 (int (*)(...))QIODevice::open +128 (int (*)(...))QIODevice::close +136 (int (*)(...))QIODevice::pos +144 (int (*)(...))QIODevice::size +152 (int (*)(...))QIODevice::seek +160 (int (*)(...))QIODevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))__cxa_pure_virtual +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))__cxa_pure_virtual + +Class QIODevice + size=16 align=8 + base size=16 base align=8 +QIODevice (0x0x7f95eeceea28) 0 + vptr=((& QIODevice::_ZTV9QIODevice) + 16u) + QObject (0x0x7f95eee23780) 0 + primary-for QIODevice (0x0x7f95eeceea28) + +Class QDataStream + size=32 align=8 + base size=32 base align=8 +QDataStream (0x0x7f95eee23900) 0 + +Class QRegExp + size=8 align=8 + base size=8 base align=8 +QRegExp (0x0x7f95eee239c0) 0 + +Class QStringMatcher::Data + size=272 align=8 + base size=272 base align=8 +QStringMatcher::Data (0x0x7f95eee23b40) 0 + +Class QStringMatcher + size=1048 align=8 + base size=1048 base align=8 +QStringMatcher (0x0x7f95eee23ae0) 0 + +Class QStringList + size=8 align=8 + base size=8 base align=8 +QStringList (0x0x7f95eeceebc8) 0 + QList (0x0x7f95eee23cc0) 0 + +Class QVariant::PrivateShared + size=16 align=8 + base size=12 base align=8 +QVariant::PrivateShared (0x0x7f95ee8fe000) 0 + +Class QVariant::Private::Data + size=8 align=8 + base size=8 base align=8 +QVariant::Private::Data (0x0x7f95ee8fe0c0) 0 + +Class QVariant::Private + size=16 align=8 + base size=12 base align=8 +QVariant::Private (0x0x7f95ee8fe060) 0 + +Class QVariant::Handler + size=72 align=8 + base size=72 base align=8 +QVariant::Handler (0x0x7f95ee8fe120) 0 + +Class QVariant + size=16 align=8 + base size=16 base align=8 +QVariant (0x0x7f95eee23f60) 0 + +Class QVariantComparisonHelper + size=8 align=8 + base size=8 base align=8 +QVariantComparisonHelper (0x0x7f95ee8fe3c0) 0 + +Class QSequentialIterable::const_iterator + size=112 align=8 + base size=112 base align=8 +QSequentialIterable::const_iterator (0x0x7f95ee8fe480) 0 + +Class QSequentialIterable + size=104 align=8 + base size=104 base align=8 +QSequentialIterable (0x0x7f95ee8fe420) 0 + +Class QAssociativeIterable::const_iterator + size=120 align=8 + base size=120 base align=8 +QAssociativeIterable::const_iterator (0x0x7f95ee8fe540) 0 + +Class QAssociativeIterable + size=112 align=8 + base size=112 base align=8 +QAssociativeIterable (0x0x7f95ee8fe4e0) 0 + +Class QVariantAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QVariantAnimation::QPrivateSignal (0x0x7f95eea14120) 0 empty + +Vtable for QVariantAnimation +QVariantAnimation::_ZTV17QVariantAnimation: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QVariantAnimation) +16 (int (*)(...))QVariantAnimation::metaObject +24 (int (*)(...))QVariantAnimation::qt_metacast +32 (int (*)(...))QVariantAnimation::qt_metacall +40 (int (*)(...))QVariantAnimation::~QVariantAnimation +48 (int (*)(...))QVariantAnimation::~QVariantAnimation +56 (int (*)(...))QVariantAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QVariantAnimation::duration +120 (int (*)(...))QVariantAnimation::updateCurrentTime +128 (int (*)(...))QVariantAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection +144 (int (*)(...))QVariantAnimation::updateCurrentValue +152 (int (*)(...))QVariantAnimation::interpolated + +Class QVariantAnimation + size=16 align=8 + base size=16 base align=8 +QVariantAnimation (0x0x7f95ee9cc5b0) 0 + vptr=((& QVariantAnimation::_ZTV17QVariantAnimation) + 16u) + QAbstractAnimation (0x0x7f95ee9cc618) 0 + primary-for QVariantAnimation (0x0x7f95ee9cc5b0) + QObject (0x0x7f95eea140c0) 0 + primary-for QAbstractAnimation (0x0x7f95ee9cc618) + +Class QPropertyAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPropertyAnimation::QPrivateSignal (0x0x7f95eea141e0) 0 empty + +Vtable for QPropertyAnimation +QPropertyAnimation::_ZTV18QPropertyAnimation: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QPropertyAnimation) +16 (int (*)(...))QPropertyAnimation::metaObject +24 (int (*)(...))QPropertyAnimation::qt_metacast +32 (int (*)(...))QPropertyAnimation::qt_metacall +40 (int (*)(...))QPropertyAnimation::~QPropertyAnimation +48 (int (*)(...))QPropertyAnimation::~QPropertyAnimation +56 (int (*)(...))QPropertyAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QVariantAnimation::duration +120 (int (*)(...))QVariantAnimation::updateCurrentTime +128 (int (*)(...))QPropertyAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection +144 (int (*)(...))QPropertyAnimation::updateCurrentValue +152 (int (*)(...))QVariantAnimation::interpolated + +Class QPropertyAnimation + size=16 align=8 + base size=16 base align=8 +QPropertyAnimation (0x0x7f95ee9cc6e8) 0 + vptr=((& QPropertyAnimation::_ZTV18QPropertyAnimation) + 16u) + QVariantAnimation (0x0x7f95ee9cc750) 0 + primary-for QPropertyAnimation (0x0x7f95ee9cc6e8) + QAbstractAnimation (0x0x7f95ee9cc7b8) 0 + primary-for QVariantAnimation (0x0x7f95ee9cc750) + QObject (0x0x7f95eea14180) 0 + primary-for QAbstractAnimation (0x0x7f95ee9cc7b8) + +Class QSequentialAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSequentialAnimationGroup::QPrivateSignal (0x0x7f95eea142a0) 0 empty + +Vtable for QSequentialAnimationGroup +QSequentialAnimationGroup::_ZTV25QSequentialAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI25QSequentialAnimationGroup) +16 (int (*)(...))QSequentialAnimationGroup::metaObject +24 (int (*)(...))QSequentialAnimationGroup::qt_metacast +32 (int (*)(...))QSequentialAnimationGroup::qt_metacall +40 (int (*)(...))QSequentialAnimationGroup::~QSequentialAnimationGroup +48 (int (*)(...))QSequentialAnimationGroup::~QSequentialAnimationGroup +56 (int (*)(...))QSequentialAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSequentialAnimationGroup::duration +120 (int (*)(...))QSequentialAnimationGroup::updateCurrentTime +128 (int (*)(...))QSequentialAnimationGroup::updateState +136 (int (*)(...))QSequentialAnimationGroup::updateDirection + +Class QSequentialAnimationGroup + size=16 align=8 + base size=16 base align=8 +QSequentialAnimationGroup (0x0x7f95ee9cc820) 0 + vptr=((& QSequentialAnimationGroup::_ZTV25QSequentialAnimationGroup) + 16u) + QAnimationGroup (0x0x7f95ee9cc888) 0 + primary-for QSequentialAnimationGroup (0x0x7f95ee9cc820) + QAbstractAnimation (0x0x7f95ee9cc8f0) 0 + primary-for QAnimationGroup (0x0x7f95ee9cc888) + QObject (0x0x7f95eea14240) 0 + primary-for QAbstractAnimation (0x0x7f95ee9cc8f0) + +Class QTextCodec::ConverterState + size=32 align=8 + base size=32 base align=8 +QTextCodec::ConverterState (0x0x7f95eea14360) 0 + +Vtable for QTextCodec +QTextCodec::_ZTV10QTextCodec: 9u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QTextCodec) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))QTextCodec::aliases +32 (int (*)(...))__cxa_pure_virtual +40 (int (*)(...))__cxa_pure_virtual +48 (int (*)(...))__cxa_pure_virtual +56 (int (*)(...))QTextCodec::~QTextCodec +64 (int (*)(...))QTextCodec::~QTextCodec + +Class QTextCodec + size=8 align=8 + base size=8 base align=8 +QTextCodec (0x0x7f95eea14300) 0 nearly-empty + vptr=((& QTextCodec::_ZTV10QTextCodec) + 16u) + +Class QTextEncoder + size=40 align=8 + base size=40 base align=8 +QTextEncoder (0x0x7f95eea14480) 0 + +Class QTextDecoder + size=40 align=8 + base size=40 base align=8 +QTextDecoder (0x0x7f95eea144e0) 0 + +Class QSharedData + size=4 align=4 + base size=4 base align=4 +QSharedData (0x0x7f95eea14540) 0 + +Class QtSharedPointer::NormalDeleter + size=1 align=1 + base size=0 base align=1 +QtSharedPointer::NormalDeleter (0x0x7f95eea147e0) 0 empty + +Class QtSharedPointer::ExternalRefCountData + size=16 align=8 + base size=16 base align=8 +QtSharedPointer::ExternalRefCountData (0x0x7f95eea14960) 0 + +Class std::__numeric_limits_base + size=1 align=1 + base size=0 base align=1 +std::__numeric_limits_base (0x0x7f95eea14de0) 0 empty + +Class QDate + size=8 align=8 + base size=8 base align=8 +QDate (0x0x7f95ee7cd5a0) 0 + +Class QTime + size=4 align=4 + base size=4 base align=4 +QTime (0x0x7f95ee7cd6c0) 0 + +Class QDateTime + size=8 align=8 + base size=8 base align=8 +QDateTime (0x0x7f95ee7cd7e0) 0 + +Class QLibraryInfo + size=1 align=1 + base size=0 base align=1 +QLibraryInfo (0x0x7f95ee7cd960) 0 empty + +Class QBuffer::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QBuffer::QPrivateSignal (0x0x7f95ee7cda20) 0 empty + +Vtable for QBuffer +QBuffer::_ZTV7QBuffer: 30u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QBuffer) +16 (int (*)(...))QBuffer::metaObject +24 (int (*)(...))QBuffer::qt_metacast +32 (int (*)(...))QBuffer::qt_metacall +40 (int (*)(...))QBuffer::~QBuffer +48 (int (*)(...))QBuffer::~QBuffer +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QBuffer::connectNotify +104 (int (*)(...))QBuffer::disconnectNotify +112 (int (*)(...))QIODevice::isSequential +120 (int (*)(...))QBuffer::open +128 (int (*)(...))QBuffer::close +136 (int (*)(...))QBuffer::pos +144 (int (*)(...))QBuffer::size +152 (int (*)(...))QBuffer::seek +160 (int (*)(...))QBuffer::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QBuffer::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QBuffer::readData +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))QBuffer::writeData + +Class QBuffer + size=16 align=8 + base size=16 base align=8 +QBuffer (0x0x7f95ee9ccf08) 0 + vptr=((& QBuffer::_ZTV7QBuffer) + 16u) + QIODevice (0x0x7f95ee9ccf70) 0 + primary-for QBuffer (0x0x7f95ee9ccf08) + QObject (0x0x7f95ee7cd9c0) 0 + primary-for QIODevice (0x0x7f95ee9ccf70) + +Class QLocale + size=8 align=8 + base size=8 base align=8 +QLocale (0x0x7f95ee7cda80) 0 + +Class _IO_marker + size=24 align=8 + base size=24 base align=8 +_IO_marker (0x0x7f95ee7cdd80) 0 + +Class _IO_FILE + size=216 align=8 + base size=216 base align=8 +_IO_FILE (0x0x7f95ee7cdde0) 0 + +Vtable for QTextStream +QTextStream::_ZTV11QTextStream: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTextStream) +16 (int (*)(...))QTextStream::~QTextStream +24 (int (*)(...))QTextStream::~QTextStream + +Class QTextStream + size=16 align=8 + base size=16 base align=8 +QTextStream (0x0x7f95ee7cdea0) 0 + vptr=((& QTextStream::_ZTV11QTextStream) + 16u) + +Class QTextStreamManipulator + size=40 align=8 + base size=38 base align=8 +QTextStreamManipulator (0x0x7f95ee59f180) 0 + +Class QContiguousCacheData + size=24 align=4 + base size=24 base align=4 +QContiguousCacheData (0x0x7f95ee59f3c0) 0 + +Class QDebug::Stream + size=72 align=8 + base size=72 base align=8 +QDebug::Stream (0x0x7f95ee59fa20) 0 + +Class QDebug + size=8 align=8 + base size=8 base align=8 +QDebug (0x0x7f95ee59f9c0) 0 + +Class QDebugStateSaver + size=8 align=8 + base size=8 base align=8 +QDebugStateSaver (0x0x7f95ee59fb40) 0 + +Class QNoDebug + size=1 align=1 + base size=0 base align=1 +QNoDebug (0x0x7f95ee59fc00) 0 empty + +Class QFileDevice::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileDevice::QPrivateSignal (0x0x7f95ee59fcc0) 0 empty + +Vtable for QFileDevice +QFileDevice::_ZTV11QFileDevice: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QFileDevice) +16 (int (*)(...))QFileDevice::metaObject +24 (int (*)(...))QFileDevice::qt_metacast +32 (int (*)(...))QFileDevice::qt_metacall +40 (int (*)(...))QFileDevice::~QFileDevice +48 (int (*)(...))QFileDevice::~QFileDevice +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QIODevice::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFileDevice::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QFileDevice::fileName +248 (int (*)(...))QFileDevice::resize +256 (int (*)(...))QFileDevice::permissions +264 (int (*)(...))QFileDevice::setPermissions + +Class QFileDevice + size=16 align=8 + base size=16 base align=8 +QFileDevice (0x0x7f95ee5a2208) 0 + vptr=((& QFileDevice::_ZTV11QFileDevice) + 16u) + QIODevice (0x0x7f95ee5a2270) 0 + primary-for QFileDevice (0x0x7f95ee5a2208) + QObject (0x0x7f95ee59fc60) 0 + primary-for QIODevice (0x0x7f95ee5a2270) + +Class QFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFile::QPrivateSignal (0x0x7f95ee59fe40) 0 empty + +Vtable for QFile +QFile::_ZTV5QFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI5QFile) +16 (int (*)(...))QFile::metaObject +24 (int (*)(...))QFile::qt_metacast +32 (int (*)(...))QFile::qt_metacall +40 (int (*)(...))QFile::~QFile +48 (int (*)(...))QFile::~QFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QFile::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFile::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QFile::fileName +248 (int (*)(...))QFile::resize +256 (int (*)(...))QFile::permissions +264 (int (*)(...))QFile::setPermissions + +Class QFile + size=16 align=8 + base size=16 base align=8 +QFile (0x0x7f95ee5a23a8) 0 + vptr=((& QFile::_ZTV5QFile) + 16u) + QFileDevice (0x0x7f95ee5a2410) 0 + primary-for QFile (0x0x7f95ee5a23a8) + QIODevice (0x0x7f95ee5a2478) 0 + primary-for QFileDevice (0x0x7f95ee5a2410) + QObject (0x0x7f95ee59fde0) 0 + primary-for QIODevice (0x0x7f95ee5a2478) + +Class QFileInfo + size=8 align=8 + base size=8 base align=8 +QFileInfo (0x0x7f95ee59ff60) 0 + +Class QDir + size=8 align=8 + base size=8 base align=8 +QDir (0x0x7f95ee371240) 0 + +Class QDirIterator + size=8 align=8 + base size=8 base align=8 +QDirIterator (0x0x7f95ee371540) 0 + +Class QFileSelector::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileSelector::QPrivateSignal (0x0x7f95ee371720) 0 empty + +Vtable for QFileSelector +QFileSelector::_ZTV13QFileSelector: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QFileSelector) +16 (int (*)(...))QFileSelector::metaObject +24 (int (*)(...))QFileSelector::qt_metacast +32 (int (*)(...))QFileSelector::qt_metacall +40 (int (*)(...))QFileSelector::~QFileSelector +48 (int (*)(...))QFileSelector::~QFileSelector +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QFileSelector + size=16 align=8 + base size=16 base align=8 +QFileSelector (0x0x7f95ee5a2958) 0 + vptr=((& QFileSelector::_ZTV13QFileSelector) + 16u) + QObject (0x0x7f95ee3716c0) 0 + primary-for QFileSelector (0x0x7f95ee5a2958) + +Class QFileSystemWatcher::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileSystemWatcher::QPrivateSignal (0x0x7f95ee3717e0) 0 empty + +Vtable for QFileSystemWatcher +QFileSystemWatcher::_ZTV18QFileSystemWatcher: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QFileSystemWatcher) +16 (int (*)(...))QFileSystemWatcher::metaObject +24 (int (*)(...))QFileSystemWatcher::qt_metacast +32 (int (*)(...))QFileSystemWatcher::qt_metacall +40 (int (*)(...))QFileSystemWatcher::~QFileSystemWatcher +48 (int (*)(...))QFileSystemWatcher::~QFileSystemWatcher +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QFileSystemWatcher + size=16 align=8 + base size=16 base align=8 +QFileSystemWatcher (0x0x7f95ee5a29c0) 0 + vptr=((& QFileSystemWatcher::_ZTV18QFileSystemWatcher) + 16u) + QObject (0x0x7f95ee371780) 0 + primary-for QFileSystemWatcher (0x0x7f95ee5a29c0) + +Class QLockFile + size=8 align=8 + base size=8 base align=8 +QLockFile (0x0x7f95ee371840) 0 + +Class QLoggingCategory + size=24 align=8 + base size=24 base align=8 +QLoggingCategory (0x0x7f95ee371960) 0 + +Class QProcessEnvironment + size=8 align=8 + base size=8 base align=8 +QProcessEnvironment (0x0x7f95ee3719c0) 0 + +Class QProcess::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QProcess::QPrivateSignal (0x0x7f95ee371ba0) 0 empty + +Vtable for QProcess +QProcess::_ZTV8QProcess: 31u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI8QProcess) +16 (int (*)(...))QProcess::metaObject +24 (int (*)(...))QProcess::qt_metacast +32 (int (*)(...))QProcess::qt_metacall +40 (int (*)(...))QProcess::~QProcess +48 (int (*)(...))QProcess::~QProcess +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QProcess::isSequential +120 (int (*)(...))QProcess::open +128 (int (*)(...))QProcess::close +136 (int (*)(...))QIODevice::pos +144 (int (*)(...))QIODevice::size +152 (int (*)(...))QIODevice::seek +160 (int (*)(...))QProcess::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QProcess::bytesAvailable +184 (int (*)(...))QProcess::bytesToWrite +192 (int (*)(...))QProcess::canReadLine +200 (int (*)(...))QProcess::waitForReadyRead +208 (int (*)(...))QProcess::waitForBytesWritten +216 (int (*)(...))QProcess::readData +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))QProcess::writeData +240 (int (*)(...))QProcess::setupChildProcess + +Class QProcess + size=16 align=8 + base size=16 base align=8 +QProcess (0x0x7f95ee5a2a90) 0 + vptr=((& QProcess::_ZTV8QProcess) + 16u) + QIODevice (0x0x7f95ee5a2af8) 0 + primary-for QProcess (0x0x7f95ee5a2a90) + QObject (0x0x7f95ee371b40) 0 + primary-for QIODevice (0x0x7f95ee5a2af8) + +Class QResource + size=8 align=8 + base size=8 base align=8 +QResource (0x0x7f95ee371c00) 0 + +Class QSaveFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSaveFile::QPrivateSignal (0x0x7f95ee371d80) 0 empty + +Vtable for QSaveFile +QSaveFile::_ZTV9QSaveFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QSaveFile) +16 (int (*)(...))QSaveFile::metaObject +24 (int (*)(...))QSaveFile::qt_metacast +32 (int (*)(...))QSaveFile::qt_metacall +40 (int (*)(...))QSaveFile::~QSaveFile +48 (int (*)(...))QSaveFile::~QSaveFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QSaveFile::open +128 (int (*)(...))QSaveFile::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFileDevice::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QSaveFile::writeData +240 (int (*)(...))QSaveFile::fileName +248 (int (*)(...))QFileDevice::resize +256 (int (*)(...))QFileDevice::permissions +264 (int (*)(...))QFileDevice::setPermissions + +Class QSaveFile + size=16 align=8 + base size=16 base align=8 +QSaveFile (0x0x7f95ee5a2b60) 0 + vptr=((& QSaveFile::_ZTV9QSaveFile) + 16u) + QFileDevice (0x0x7f95ee5a2bc8) 0 + primary-for QSaveFile (0x0x7f95ee5a2b60) + QIODevice (0x0x7f95ee5a2c30) 0 + primary-for QFileDevice (0x0x7f95ee5a2bc8) + QObject (0x0x7f95ee371d20) 0 + primary-for QIODevice (0x0x7f95ee5a2c30) + +Class QSettings::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSettings::QPrivateSignal (0x0x7f95ee371e40) 0 empty + +Vtable for QSettings +QSettings::_ZTV9QSettings: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QSettings) +16 (int (*)(...))QSettings::metaObject +24 (int (*)(...))QSettings::qt_metacast +32 (int (*)(...))QSettings::qt_metacall +40 (int (*)(...))QSettings::~QSettings +48 (int (*)(...))QSettings::~QSettings +56 (int (*)(...))QSettings::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSettings + size=16 align=8 + base size=16 base align=8 +QSettings (0x0x7f95ee5a2c98) 0 + vptr=((& QSettings::_ZTV9QSettings) + 16u) + QObject (0x0x7f95ee371de0) 0 + primary-for QSettings (0x0x7f95ee5a2c98) + +Class QStandardPaths + size=1 align=1 + base size=0 base align=1 +QStandardPaths (0x0x7f95ee371ea0) 0 empty + +Class QTemporaryDir + size=8 align=8 + base size=8 base align=8 +QTemporaryDir (0x0x7f95ee0f2000) 0 + +Class QTemporaryFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTemporaryFile::QPrivateSignal (0x0x7f95ee0f2120) 0 empty + +Vtable for QTemporaryFile +QTemporaryFile::_ZTV14QTemporaryFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI14QTemporaryFile) +16 (int (*)(...))QTemporaryFile::metaObject +24 (int (*)(...))QTemporaryFile::qt_metacast +32 (int (*)(...))QTemporaryFile::qt_metacall +40 (int (*)(...))QTemporaryFile::~QTemporaryFile +48 (int (*)(...))QTemporaryFile::~QTemporaryFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QTemporaryFile::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFile::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QTemporaryFile::fileName +248 (int (*)(...))QFile::resize +256 (int (*)(...))QFile::permissions +264 (int (*)(...))QFile::setPermissions + +Class QTemporaryFile + size=16 align=8 + base size=16 base align=8 +QTemporaryFile (0x0x7f95ee5a2dd0) 0 + vptr=((& QTemporaryFile::_ZTV14QTemporaryFile) + 16u) + QFile (0x0x7f95ee5a2e38) 0 + primary-for QTemporaryFile (0x0x7f95ee5a2dd0) + QFileDevice (0x0x7f95ee5a2ea0) 0 + primary-for QFile (0x0x7f95ee5a2e38) + QIODevice (0x0x7f95ee5a2f08) 0 + primary-for QFileDevice (0x0x7f95ee5a2ea0) + QObject (0x0x7f95ee0f20c0) 0 + primary-for QIODevice (0x0x7f95ee5a2f08) + +Class QUrl + size=8 align=8 + base size=8 base align=8 +QUrl (0x0x7f95ee0f2240) 0 + +Class QUrlQuery + size=8 align=8 + base size=8 base align=8 +QUrlQuery (0x0x7f95ee0f2660) 0 + +Class QModelIndex + size=24 align=8 + base size=24 base align=8 +QModelIndex (0x0x7f95ee0f27e0) 0 + +Class QPersistentModelIndex + size=8 align=8 + base size=8 base align=8 +QPersistentModelIndex (0x0x7f95ee0f2900) 0 + +Class QAbstractItemModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractItemModel::QPrivateSignal (0x0x7f95ee0f2a80) 0 empty + +Vtable for QAbstractItemModel +QAbstractItemModel::_ZTV18QAbstractItemModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractItemModel) +16 (int (*)(...))QAbstractItemModel::metaObject +24 (int (*)(...))QAbstractItemModel::qt_metacast +32 (int (*)(...))QAbstractItemModel::qt_metacall +40 (int (*)(...))QAbstractItemModel::~QAbstractItemModel +48 (int (*)(...))QAbstractItemModel::~QAbstractItemModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractItemModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractItemModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractItemModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractItemModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractItemModel + size=16 align=8 + base size=16 base align=8 +QAbstractItemModel (0x0x7f95ee1602d8) 0 + vptr=((& QAbstractItemModel::_ZTV18QAbstractItemModel) + 16u) + QObject (0x0x7f95ee0f2a20) 0 + primary-for QAbstractItemModel (0x0x7f95ee1602d8) + +Class QAbstractTableModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractTableModel::QPrivateSignal (0x0x7f95ee0f2d80) 0 empty + +Vtable for QAbstractTableModel +QAbstractTableModel::_ZTV19QAbstractTableModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractTableModel) +16 (int (*)(...))QAbstractTableModel::metaObject +24 (int (*)(...))QAbstractTableModel::qt_metacast +32 (int (*)(...))QAbstractTableModel::qt_metacall +40 (int (*)(...))QAbstractTableModel::~QAbstractTableModel +48 (int (*)(...))QAbstractTableModel::~QAbstractTableModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractTableModel::index +120 (int (*)(...))QAbstractTableModel::parent +128 (int (*)(...))QAbstractItemModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractTableModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractTableModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractTableModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractTableModel + size=16 align=8 + base size=16 base align=8 +QAbstractTableModel (0x0x7f95ee160410) 0 + vptr=((& QAbstractTableModel::_ZTV19QAbstractTableModel) + 16u) + QAbstractItemModel (0x0x7f95ee160478) 0 + primary-for QAbstractTableModel (0x0x7f95ee160410) + QObject (0x0x7f95ee0f2d20) 0 + primary-for QAbstractItemModel (0x0x7f95ee160478) + +Class QAbstractListModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractListModel::QPrivateSignal (0x0x7f95ee0f2e40) 0 empty + +Vtable for QAbstractListModel +QAbstractListModel::_ZTV18QAbstractListModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractListModel) +16 (int (*)(...))QAbstractListModel::metaObject +24 (int (*)(...))QAbstractListModel::qt_metacast +32 (int (*)(...))QAbstractListModel::qt_metacall +40 (int (*)(...))QAbstractListModel::~QAbstractListModel +48 (int (*)(...))QAbstractListModel::~QAbstractListModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractListModel::index +120 (int (*)(...))QAbstractListModel::parent +128 (int (*)(...))QAbstractItemModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))QAbstractListModel::columnCount +152 (int (*)(...))QAbstractListModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractListModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractListModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractListModel + size=16 align=8 + base size=16 base align=8 +QAbstractListModel (0x0x7f95ee1604e0) 0 + vptr=((& QAbstractListModel::_ZTV18QAbstractListModel) + 16u) + QAbstractItemModel (0x0x7f95ee160548) 0 + primary-for QAbstractListModel (0x0x7f95ee1604e0) + QObject (0x0x7f95ee0f2de0) 0 + primary-for QAbstractItemModel (0x0x7f95ee160548) + +Class QAbstractProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractProxyModel::QPrivateSignal (0x0x7f95ee0f2f00) 0 empty + +Vtable for QAbstractProxyModel +QAbstractProxyModel::_ZTV19QAbstractProxyModel: 53u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractProxyModel) +16 (int (*)(...))QAbstractProxyModel::metaObject +24 (int (*)(...))QAbstractProxyModel::qt_metacast +32 (int (*)(...))QAbstractProxyModel::qt_metacall +40 (int (*)(...))QAbstractProxyModel::~QAbstractProxyModel +48 (int (*)(...))QAbstractProxyModel::~QAbstractProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractProxyModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractProxyModel::hasChildren +160 (int (*)(...))QAbstractProxyModel::data +168 (int (*)(...))QAbstractProxyModel::setData +176 (int (*)(...))QAbstractProxyModel::headerData +184 (int (*)(...))QAbstractProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QAbstractProxyModel::mimeTypes +216 (int (*)(...))QAbstractProxyModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractItemModel::dropMimeData +240 (int (*)(...))QAbstractProxyModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractProxyModel::fetchMore +312 (int (*)(...))QAbstractProxyModel::canFetchMore +320 (int (*)(...))QAbstractProxyModel::flags +328 (int (*)(...))QAbstractProxyModel::sort +336 (int (*)(...))QAbstractProxyModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QAbstractProxyModel::setSourceModel +392 (int (*)(...))__cxa_pure_virtual +400 (int (*)(...))__cxa_pure_virtual +408 (int (*)(...))QAbstractProxyModel::mapSelectionToSource +416 (int (*)(...))QAbstractProxyModel::mapSelectionFromSource + +Class QAbstractProxyModel + size=16 align=8 + base size=16 base align=8 +QAbstractProxyModel (0x0x7f95ee1605b0) 0 + vptr=((& QAbstractProxyModel::_ZTV19QAbstractProxyModel) + 16u) + QAbstractItemModel (0x0x7f95ee160618) 0 + primary-for QAbstractProxyModel (0x0x7f95ee1605b0) + QObject (0x0x7f95ee0f2ea0) 0 + primary-for QAbstractItemModel (0x0x7f95ee160618) + +Class QIdentityProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QIdentityProxyModel::QPrivateSignal (0x0x7f95edee8000) 0 empty + +Vtable for QIdentityProxyModel +QIdentityProxyModel::_ZTV19QIdentityProxyModel: 53u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QIdentityProxyModel) +16 (int (*)(...))QIdentityProxyModel::metaObject +24 (int (*)(...))QIdentityProxyModel::qt_metacast +32 (int (*)(...))QIdentityProxyModel::qt_metacall +40 (int (*)(...))QIdentityProxyModel::~QIdentityProxyModel +48 (int (*)(...))QIdentityProxyModel::~QIdentityProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QIdentityProxyModel::index +120 (int (*)(...))QIdentityProxyModel::parent +128 (int (*)(...))QIdentityProxyModel::sibling +136 (int (*)(...))QIdentityProxyModel::rowCount +144 (int (*)(...))QIdentityProxyModel::columnCount +152 (int (*)(...))QAbstractProxyModel::hasChildren +160 (int (*)(...))QAbstractProxyModel::data +168 (int (*)(...))QAbstractProxyModel::setData +176 (int (*)(...))QIdentityProxyModel::headerData +184 (int (*)(...))QAbstractProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QAbstractProxyModel::mimeTypes +216 (int (*)(...))QAbstractProxyModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QIdentityProxyModel::dropMimeData +240 (int (*)(...))QAbstractProxyModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QIdentityProxyModel::insertRows +264 (int (*)(...))QIdentityProxyModel::insertColumns +272 (int (*)(...))QIdentityProxyModel::removeRows +280 (int (*)(...))QIdentityProxyModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractProxyModel::fetchMore +312 (int (*)(...))QAbstractProxyModel::canFetchMore +320 (int (*)(...))QAbstractProxyModel::flags +328 (int (*)(...))QAbstractProxyModel::sort +336 (int (*)(...))QAbstractProxyModel::buddy +344 (int (*)(...))QIdentityProxyModel::match +352 (int (*)(...))QAbstractProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QIdentityProxyModel::setSourceModel +392 (int (*)(...))QIdentityProxyModel::mapToSource +400 (int (*)(...))QIdentityProxyModel::mapFromSource +408 (int (*)(...))QIdentityProxyModel::mapSelectionToSource +416 (int (*)(...))QIdentityProxyModel::mapSelectionFromSource + +Class QIdentityProxyModel + size=16 align=8 + base size=16 base align=8 +QIdentityProxyModel (0x0x7f95ee160680) 0 + vptr=((& QIdentityProxyModel::_ZTV19QIdentityProxyModel) + 16u) + QAbstractProxyModel (0x0x7f95ee1606e8) 0 + primary-for QIdentityProxyModel (0x0x7f95ee160680) + QAbstractItemModel (0x0x7f95ee160750) 0 + primary-for QAbstractProxyModel (0x0x7f95ee1606e8) + QObject (0x0x7f95ee0f2f60) 0 + primary-for QAbstractItemModel (0x0x7f95ee160750) + +Class QItemSelectionRange + size=16 align=8 + base size=16 base align=8 +QItemSelectionRange (0x0x7f95edee8060) 0 + +Class QItemSelectionModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QItemSelectionModel::QPrivateSignal (0x0x7f95edee81e0) 0 empty + +Vtable for QItemSelectionModel +QItemSelectionModel::_ZTV19QItemSelectionModel: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QItemSelectionModel) +16 (int (*)(...))QItemSelectionModel::metaObject +24 (int (*)(...))QItemSelectionModel::qt_metacast +32 (int (*)(...))QItemSelectionModel::qt_metacall +40 (int (*)(...))QItemSelectionModel::~QItemSelectionModel +48 (int (*)(...))QItemSelectionModel::~QItemSelectionModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QItemSelectionModel::setCurrentIndex +120 (int (*)(...))QItemSelectionModel::select +128 (int (*)(...))QItemSelectionModel::select +136 (int (*)(...))QItemSelectionModel::clear +144 (int (*)(...))QItemSelectionModel::reset +152 (int (*)(...))QItemSelectionModel::clearCurrentIndex + +Class QItemSelectionModel + size=16 align=8 + base size=16 base align=8 +QItemSelectionModel (0x0x7f95ee160820) 0 + vptr=((& QItemSelectionModel::_ZTV19QItemSelectionModel) + 16u) + QObject (0x0x7f95edee8180) 0 + primary-for QItemSelectionModel (0x0x7f95ee160820) + +Class QItemSelection + size=8 align=8 + base size=8 base align=8 +QItemSelection (0x0x7f95ee160958) 0 + QList (0x0x7f95edee83c0) 0 + +Class QSortFilterProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSortFilterProxyModel::QPrivateSignal (0x0x7f95edee8480) 0 empty + +Vtable for QSortFilterProxyModel +QSortFilterProxyModel::_ZTV21QSortFilterProxyModel: 56u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QSortFilterProxyModel) +16 (int (*)(...))QSortFilterProxyModel::metaObject +24 (int (*)(...))QSortFilterProxyModel::qt_metacast +32 (int (*)(...))QSortFilterProxyModel::qt_metacall +40 (int (*)(...))QSortFilterProxyModel::~QSortFilterProxyModel +48 (int (*)(...))QSortFilterProxyModel::~QSortFilterProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSortFilterProxyModel::index +120 (int (*)(...))QSortFilterProxyModel::parent +128 (int (*)(...))QSortFilterProxyModel::sibling +136 (int (*)(...))QSortFilterProxyModel::rowCount +144 (int (*)(...))QSortFilterProxyModel::columnCount +152 (int (*)(...))QSortFilterProxyModel::hasChildren +160 (int (*)(...))QSortFilterProxyModel::data +168 (int (*)(...))QSortFilterProxyModel::setData +176 (int (*)(...))QSortFilterProxyModel::headerData +184 (int (*)(...))QSortFilterProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QSortFilterProxyModel::mimeTypes +216 (int (*)(...))QSortFilterProxyModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QSortFilterProxyModel::dropMimeData +240 (int (*)(...))QSortFilterProxyModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QSortFilterProxyModel::insertRows +264 (int (*)(...))QSortFilterProxyModel::insertColumns +272 (int (*)(...))QSortFilterProxyModel::removeRows +280 (int (*)(...))QSortFilterProxyModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QSortFilterProxyModel::fetchMore +312 (int (*)(...))QSortFilterProxyModel::canFetchMore +320 (int (*)(...))QSortFilterProxyModel::flags +328 (int (*)(...))QSortFilterProxyModel::sort +336 (int (*)(...))QSortFilterProxyModel::buddy +344 (int (*)(...))QSortFilterProxyModel::match +352 (int (*)(...))QSortFilterProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QSortFilterProxyModel::setSourceModel +392 (int (*)(...))QSortFilterProxyModel::mapToSource +400 (int (*)(...))QSortFilterProxyModel::mapFromSource +408 (int (*)(...))QSortFilterProxyModel::mapSelectionToSource +416 (int (*)(...))QSortFilterProxyModel::mapSelectionFromSource +424 (int (*)(...))QSortFilterProxyModel::filterAcceptsRow +432 (int (*)(...))QSortFilterProxyModel::filterAcceptsColumn +440 (int (*)(...))QSortFilterProxyModel::lessThan + +Class QSortFilterProxyModel + size=16 align=8 + base size=16 base align=8 +QSortFilterProxyModel (0x0x7f95ee1609c0) 0 + vptr=((& QSortFilterProxyModel::_ZTV21QSortFilterProxyModel) + 16u) + QAbstractProxyModel (0x0x7f95ee160a28) 0 + primary-for QSortFilterProxyModel (0x0x7f95ee1609c0) + QAbstractItemModel (0x0x7f95ee160a90) 0 + primary-for QAbstractProxyModel (0x0x7f95ee160a28) + QObject (0x0x7f95edee8420) 0 + primary-for QAbstractItemModel (0x0x7f95ee160a90) + +Class QStringListModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QStringListModel::QPrivateSignal (0x0x7f95edee8540) 0 empty + +Vtable for QStringListModel +QStringListModel::_ZTV16QStringListModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QStringListModel) +16 (int (*)(...))QStringListModel::metaObject +24 (int (*)(...))QStringListModel::qt_metacast +32 (int (*)(...))QStringListModel::qt_metacall +40 (int (*)(...))QStringListModel::~QStringListModel +48 (int (*)(...))QStringListModel::~QStringListModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractListModel::index +120 (int (*)(...))QAbstractListModel::parent +128 (int (*)(...))QStringListModel::sibling +136 (int (*)(...))QStringListModel::rowCount +144 (int (*)(...))QAbstractListModel::columnCount +152 (int (*)(...))QAbstractListModel::hasChildren +160 (int (*)(...))QStringListModel::data +168 (int (*)(...))QStringListModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractListModel::dropMimeData +240 (int (*)(...))QStringListModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QStringListModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QStringListModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QStringListModel::flags +328 (int (*)(...))QStringListModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QStringListModel + size=24 align=8 + base size=24 base align=8 +QStringListModel (0x0x7f95ee160af8) 0 + vptr=((& QStringListModel::_ZTV16QStringListModel) + 16u) + QAbstractListModel (0x0x7f95ee160b60) 0 + primary-for QStringListModel (0x0x7f95ee160af8) + QAbstractItemModel (0x0x7f95ee160bc8) 0 + primary-for QAbstractListModel (0x0x7f95ee160b60) + QObject (0x0x7f95edee84e0) 0 + primary-for QAbstractItemModel (0x0x7f95ee160bc8) + +Class QJsonValue + size=24 align=8 + base size=20 base align=8 +QJsonValue (0x0x7f95edee85a0) 0 + +Class QJsonValueRef + size=16 align=8 + base size=12 base align=8 +QJsonValueRef (0x0x7f95edee8660) 0 + +Class QJsonArray::iterator + size=16 align=8 + base size=12 base align=8 +QJsonArray::iterator (0x0x7f95edee8780) 0 + +Class QJsonArray::const_iterator + size=16 align=8 + base size=12 base align=8 +QJsonArray::const_iterator (0x0x7f95edee87e0) 0 + +Class QJsonArray + size=16 align=8 + base size=16 base align=8 +QJsonArray (0x0x7f95edee8720) 0 + +Class QJsonParseError + size=8 align=4 + base size=8 base align=4 +QJsonParseError (0x0x7f95edee8840) 0 + +Class QJsonDocument + size=8 align=8 + base size=8 base align=8 +QJsonDocument (0x0x7f95edee88a0) 0 + +Class QJsonObject::iterator + size=16 align=8 + base size=12 base align=8 +QJsonObject::iterator (0x0x7f95edee8960) 0 + +Class QJsonObject::const_iterator + size=16 align=8 + base size=12 base align=8 +QJsonObject::const_iterator (0x0x7f95edee89c0) 0 + +Class QJsonObject + size=16 align=8 + base size=16 base align=8 +QJsonObject (0x0x7f95edee8900) 0 + +Class QEventLoop::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QEventLoop::QPrivateSignal (0x0x7f95edee8ae0) 0 empty + +Vtable for QEventLoop +QEventLoop::_ZTV10QEventLoop: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QEventLoop) +16 (int (*)(...))QEventLoop::metaObject +24 (int (*)(...))QEventLoop::qt_metacast +32 (int (*)(...))QEventLoop::qt_metacall +40 (int (*)(...))QEventLoop::~QEventLoop +48 (int (*)(...))QEventLoop::~QEventLoop +56 (int (*)(...))QEventLoop::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QEventLoop + size=16 align=8 + base size=16 base align=8 +QEventLoop (0x0x7f95ee160c30) 0 + vptr=((& QEventLoop::_ZTV10QEventLoop) + 16u) + QObject (0x0x7f95edee8a80) 0 + primary-for QEventLoop (0x0x7f95ee160c30) + +Class QEventLoopLocker + size=8 align=8 + base size=8 base align=8 +QEventLoopLocker (0x0x7f95edee8c00) 0 + +Class QAbstractEventDispatcher::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractEventDispatcher::QPrivateSignal (0x0x7f95edee8cc0) 0 empty + +Class QAbstractEventDispatcher::TimerInfo + size=12 align=4 + base size=12 base align=4 +QAbstractEventDispatcher::TimerInfo (0x0x7f95edee8d20) 0 + +Vtable for QAbstractEventDispatcher +QAbstractEventDispatcher::_ZTV24QAbstractEventDispatcher: 28u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI24QAbstractEventDispatcher) +16 (int (*)(...))QAbstractEventDispatcher::metaObject +24 (int (*)(...))QAbstractEventDispatcher::qt_metacast +32 (int (*)(...))QAbstractEventDispatcher::qt_metacall +40 (int (*)(...))QAbstractEventDispatcher::~QAbstractEventDispatcher +48 (int (*)(...))QAbstractEventDispatcher::~QAbstractEventDispatcher +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual +184 (int (*)(...))__cxa_pure_virtual +192 (int (*)(...))__cxa_pure_virtual +200 (int (*)(...))__cxa_pure_virtual +208 (int (*)(...))QAbstractEventDispatcher::startingUp +216 (int (*)(...))QAbstractEventDispatcher::closingDown + +Class QAbstractEventDispatcher + size=16 align=8 + base size=16 base align=8 +QAbstractEventDispatcher (0x0x7f95ee160d68) 0 + vptr=((& QAbstractEventDispatcher::_ZTV24QAbstractEventDispatcher) + 16u) + QObject (0x0x7f95edee8c60) 0 + primary-for QAbstractEventDispatcher (0x0x7f95ee160d68) + +Vtable for QAbstractNativeEventFilter +QAbstractNativeEventFilter::_ZTV26QAbstractNativeEventFilter: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI26QAbstractNativeEventFilter) +16 (int (*)(...))QAbstractNativeEventFilter::~QAbstractNativeEventFilter +24 (int (*)(...))QAbstractNativeEventFilter::~QAbstractNativeEventFilter +32 (int (*)(...))__cxa_pure_virtual + +Class QAbstractNativeEventFilter + size=16 align=8 + base size=16 base align=8 +QAbstractNativeEventFilter (0x0x7f95edee8d80) 0 + vptr=((& QAbstractNativeEventFilter::_ZTV26QAbstractNativeEventFilter) + 16u) + +Class QBasicTimer + size=4 align=4 + base size=4 base align=4 +QBasicTimer (0x0x7f95edee8de0) 0 + +Vtable for QEvent +QEvent::_ZTV6QEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QEvent) +16 (int (*)(...))QEvent::~QEvent +24 (int (*)(...))QEvent::~QEvent + +Class QEvent + size=24 align=8 + base size=20 base align=8 +QEvent (0x0x7f95edee8f00) 0 + vptr=((& QEvent::_ZTV6QEvent) + 16u) + +Vtable for QTimerEvent +QTimerEvent::_ZTV11QTimerEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTimerEvent) +16 (int (*)(...))QTimerEvent::~QTimerEvent +24 (int (*)(...))QTimerEvent::~QTimerEvent + +Class QTimerEvent + size=24 align=8 + base size=24 base align=8 +QTimerEvent (0x0x7f95ee160e38) 0 + vptr=((& QTimerEvent::_ZTV11QTimerEvent) + 16u) + QEvent (0x0x7f95edee8f60) 0 + primary-for QTimerEvent (0x0x7f95ee160e38) + +Vtable for QChildEvent +QChildEvent::_ZTV11QChildEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QChildEvent) +16 (int (*)(...))QChildEvent::~QChildEvent +24 (int (*)(...))QChildEvent::~QChildEvent + +Class QChildEvent + size=32 align=8 + base size=32 base align=8 +QChildEvent (0x0x7f95ee160ea0) 0 + vptr=((& QChildEvent::_ZTV11QChildEvent) + 16u) + QEvent (0x0x7f95edd19000) 0 + primary-for QChildEvent (0x0x7f95ee160ea0) + +Vtable for QDynamicPropertyChangeEvent +QDynamicPropertyChangeEvent::_ZTV27QDynamicPropertyChangeEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI27QDynamicPropertyChangeEvent) +16 (int (*)(...))QDynamicPropertyChangeEvent::~QDynamicPropertyChangeEvent +24 (int (*)(...))QDynamicPropertyChangeEvent::~QDynamicPropertyChangeEvent + +Class QDynamicPropertyChangeEvent + size=32 align=8 + base size=32 base align=8 +QDynamicPropertyChangeEvent (0x0x7f95ee160f08) 0 + vptr=((& QDynamicPropertyChangeEvent::_ZTV27QDynamicPropertyChangeEvent) + 16u) + QEvent (0x0x7f95edd19060) 0 + primary-for QDynamicPropertyChangeEvent (0x0x7f95ee160f08) + +Vtable for QDeferredDeleteEvent +QDeferredDeleteEvent::_ZTV20QDeferredDeleteEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI20QDeferredDeleteEvent) +16 (int (*)(...))QDeferredDeleteEvent::~QDeferredDeleteEvent +24 (int (*)(...))QDeferredDeleteEvent::~QDeferredDeleteEvent + +Class QDeferredDeleteEvent + size=24 align=8 + base size=24 base align=8 +QDeferredDeleteEvent (0x0x7f95ee160f70) 0 + vptr=((& QDeferredDeleteEvent::_ZTV20QDeferredDeleteEvent) + 16u) + QEvent (0x0x7f95edd190c0) 0 + primary-for QDeferredDeleteEvent (0x0x7f95ee160f70) + +Class QCoreApplication::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QCoreApplication::QPrivateSignal (0x0x7f95edd19180) 0 empty + +Vtable for QCoreApplication +QCoreApplication::_ZTV16QCoreApplication: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QCoreApplication) +16 (int (*)(...))QCoreApplication::metaObject +24 (int (*)(...))QCoreApplication::qt_metacast +32 (int (*)(...))QCoreApplication::qt_metacall +40 (int (*)(...))QCoreApplication::~QCoreApplication +48 (int (*)(...))QCoreApplication::~QCoreApplication +56 (int (*)(...))QCoreApplication::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QCoreApplication::notify +120 (int (*)(...))QCoreApplication::compressEvent + +Class QCoreApplication + size=16 align=8 + base size=16 base align=8 +QCoreApplication (0x0x7f95edd27000) 0 + vptr=((& QCoreApplication::_ZTV16QCoreApplication) + 16u) + QObject (0x0x7f95edd19120) 0 + primary-for QCoreApplication (0x0x7f95edd27000) + +Class __exception + size=40 align=8 + base size=40 base align=8 +__exception (0x0x7f95edd191e0) 0 + +Class QMetaMethod + size=16 align=8 + base size=12 base align=8 +QMetaMethod (0x0x7f95edd19240) 0 + +Class QMetaEnum + size=16 align=8 + base size=12 base align=8 +QMetaEnum (0x0x7f95edd19360) 0 + +Class QMetaProperty + size=32 align=8 + base size=32 base align=8 +QMetaProperty (0x0x7f95edd19480) 0 + +Class QMetaClassInfo + size=16 align=8 + base size=12 base align=8 +QMetaClassInfo (0x0x7f95edd194e0) 0 + +Class QMimeData::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QMimeData::QPrivateSignal (0x0x7f95edd19660) 0 empty + +Vtable for QMimeData +QMimeData::_ZTV9QMimeData: 17u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QMimeData) +16 (int (*)(...))QMimeData::metaObject +24 (int (*)(...))QMimeData::qt_metacast +32 (int (*)(...))QMimeData::qt_metacall +40 (int (*)(...))QMimeData::~QMimeData +48 (int (*)(...))QMimeData::~QMimeData +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QMimeData::hasFormat +120 (int (*)(...))QMimeData::formats +128 (int (*)(...))QMimeData::retrieveData + +Class QMimeData + size=16 align=8 + base size=16 base align=8 +QMimeData (0x0x7f95edd27270) 0 + vptr=((& QMimeData::_ZTV9QMimeData) + 16u) + QObject (0x0x7f95edd19600) 0 + primary-for QMimeData (0x0x7f95edd27270) + +Class QObjectCleanupHandler::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QObjectCleanupHandler::QPrivateSignal (0x0x7f95edd19720) 0 empty + +Vtable for QObjectCleanupHandler +QObjectCleanupHandler::_ZTV21QObjectCleanupHandler: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QObjectCleanupHandler) +16 (int (*)(...))QObjectCleanupHandler::metaObject +24 (int (*)(...))QObjectCleanupHandler::qt_metacast +32 (int (*)(...))QObjectCleanupHandler::qt_metacall +40 (int (*)(...))QObjectCleanupHandler::~QObjectCleanupHandler +48 (int (*)(...))QObjectCleanupHandler::~QObjectCleanupHandler +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QObjectCleanupHandler + size=24 align=8 + base size=24 base align=8 +QObjectCleanupHandler (0x0x7f95edd272d8) 0 + vptr=((& QObjectCleanupHandler::_ZTV21QObjectCleanupHandler) + 16u) + QObject (0x0x7f95edd196c0) 0 + primary-for QObjectCleanupHandler (0x0x7f95edd272d8) + +Class QSharedMemory::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSharedMemory::QPrivateSignal (0x0x7f95edd19960) 0 empty + +Vtable for QSharedMemory +QSharedMemory::_ZTV13QSharedMemory: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QSharedMemory) +16 (int (*)(...))QSharedMemory::metaObject +24 (int (*)(...))QSharedMemory::qt_metacast +32 (int (*)(...))QSharedMemory::qt_metacall +40 (int (*)(...))QSharedMemory::~QSharedMemory +48 (int (*)(...))QSharedMemory::~QSharedMemory +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSharedMemory + size=16 align=8 + base size=16 base align=8 +QSharedMemory (0x0x7f95edd27340) 0 + vptr=((& QSharedMemory::_ZTV13QSharedMemory) + 16u) + QObject (0x0x7f95edd19900) 0 + primary-for QSharedMemory (0x0x7f95edd27340) + +Class QSignalMapper::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSignalMapper::QPrivateSignal (0x0x7f95edd19a20) 0 empty + +Vtable for QSignalMapper +QSignalMapper::_ZTV13QSignalMapper: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QSignalMapper) +16 (int (*)(...))QSignalMapper::metaObject +24 (int (*)(...))QSignalMapper::qt_metacast +32 (int (*)(...))QSignalMapper::qt_metacall +40 (int (*)(...))QSignalMapper::~QSignalMapper +48 (int (*)(...))QSignalMapper::~QSignalMapper +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSignalMapper + size=16 align=8 + base size=16 base align=8 +QSignalMapper (0x0x7f95edd273a8) 0 + vptr=((& QSignalMapper::_ZTV13QSignalMapper) + 16u) + QObject (0x0x7f95edd199c0) 0 + primary-for QSignalMapper (0x0x7f95edd273a8) + +Class QSocketNotifier::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSocketNotifier::QPrivateSignal (0x0x7f95edd19ae0) 0 empty + +Vtable for QSocketNotifier +QSocketNotifier::_ZTV15QSocketNotifier: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QSocketNotifier) +16 (int (*)(...))QSocketNotifier::metaObject +24 (int (*)(...))QSocketNotifier::qt_metacast +32 (int (*)(...))QSocketNotifier::qt_metacall +40 (int (*)(...))QSocketNotifier::~QSocketNotifier +48 (int (*)(...))QSocketNotifier::~QSocketNotifier +56 (int (*)(...))QSocketNotifier::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSocketNotifier + size=16 align=8 + base size=16 base align=8 +QSocketNotifier (0x0x7f95edd27410) 0 + vptr=((& QSocketNotifier::_ZTV15QSocketNotifier) + 16u) + QObject (0x0x7f95edd19a80) 0 + primary-for QSocketNotifier (0x0x7f95edd27410) + +Class QSystemSemaphore + size=8 align=8 + base size=8 base align=8 +QSystemSemaphore (0x0x7f95edd19b40) 0 + +Class QTimer::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTimer::QPrivateSignal (0x0x7f95edd19c60) 0 empty + +Vtable for QTimer +QTimer::_ZTV6QTimer: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QTimer) +16 (int (*)(...))QTimer::metaObject +24 (int (*)(...))QTimer::qt_metacast +32 (int (*)(...))QTimer::qt_metacall +40 (int (*)(...))QTimer::~QTimer +48 (int (*)(...))QTimer::~QTimer +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QTimer::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QTimer + size=32 align=8 + base size=29 base align=8 +QTimer (0x0x7f95edd27478) 0 + vptr=((& QTimer::_ZTV6QTimer) + 16u) + QObject (0x0x7f95edd19c00) 0 + primary-for QTimer (0x0x7f95edd27478) + +Class QTranslator::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTranslator::QPrivateSignal (0x0x7f95edd19d80) 0 empty + +Vtable for QTranslator +QTranslator::_ZTV11QTranslator: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTranslator) +16 (int (*)(...))QTranslator::metaObject +24 (int (*)(...))QTranslator::qt_metacast +32 (int (*)(...))QTranslator::qt_metacall +40 (int (*)(...))QTranslator::~QTranslator +48 (int (*)(...))QTranslator::~QTranslator +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QTranslator::translate +120 (int (*)(...))QTranslator::isEmpty + +Class QTranslator + size=16 align=8 + base size=16 base align=8 +QTranslator (0x0x7f95edd274e0) 0 + vptr=((& QTranslator::_ZTV11QTranslator) + 16u) + QObject (0x0x7f95edd19d20) 0 + primary-for QTranslator (0x0x7f95edd274e0) + +Class QMimeType + size=8 align=8 + base size=8 base align=8 +QMimeType (0x0x7f95edd19de0) 0 + +Class QMimeDatabase + size=8 align=8 + base size=8 base align=8 +QMimeDatabase (0x0x7f95edd19f60) 0 + +Vtable for QFactoryInterface +QFactoryInterface::_ZTV17QFactoryInterface: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QFactoryInterface) +16 (int (*)(...))QFactoryInterface::~QFactoryInterface +24 (int (*)(...))QFactoryInterface::~QFactoryInterface +32 (int (*)(...))__cxa_pure_virtual + +Class QFactoryInterface + size=8 align=8 + base size=8 base align=8 +QFactoryInterface (0x0x7f95edead000) 0 nearly-empty + vptr=((& QFactoryInterface::_ZTV17QFactoryInterface) + 16u) + +Class QLibrary::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QLibrary::QPrivateSignal (0x0x7f95edead120) 0 empty + +Vtable for QLibrary +QLibrary::_ZTV8QLibrary: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI8QLibrary) +16 (int (*)(...))QLibrary::metaObject +24 (int (*)(...))QLibrary::qt_metacast +32 (int (*)(...))QLibrary::qt_metacall +40 (int (*)(...))QLibrary::~QLibrary +48 (int (*)(...))QLibrary::~QLibrary +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QLibrary + size=32 align=8 + base size=25 base align=8 +QLibrary (0x0x7f95edd275b0) 0 + vptr=((& QLibrary::_ZTV8QLibrary) + 16u) + QObject (0x0x7f95edead0c0) 0 + primary-for QLibrary (0x0x7f95edd275b0) + +Class QStaticPlugin + size=16 align=8 + base size=16 base align=8 +QStaticPlugin (0x0x7f95edead240) 0 + +Class QPluginLoader::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPluginLoader::QPrivateSignal (0x0x7f95edead3c0) 0 empty + +Vtable for QPluginLoader +QPluginLoader::_ZTV13QPluginLoader: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QPluginLoader) +16 (int (*)(...))QPluginLoader::metaObject +24 (int (*)(...))QPluginLoader::qt_metacast +32 (int (*)(...))QPluginLoader::qt_metacall +40 (int (*)(...))QPluginLoader::~QPluginLoader +48 (int (*)(...))QPluginLoader::~QPluginLoader +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QPluginLoader + size=32 align=8 + base size=25 base align=8 +QPluginLoader (0x0x7f95edd27750) 0 + vptr=((& QPluginLoader::_ZTV13QPluginLoader) + 16u) + QObject (0x0x7f95edead360) 0 + primary-for QPluginLoader (0x0x7f95edd27750) + +Class QUuid + size=16 align=4 + base size=16 base align=4 +QUuid (0x0x7f95edead420) 0 + +Class QAbstractState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractState::QPrivateSignal (0x0x7f95edead5a0) 0 empty + +Vtable for QAbstractState +QAbstractState::_ZTV14QAbstractState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI14QAbstractState) +16 (int (*)(...))QAbstractState::metaObject +24 (int (*)(...))QAbstractState::qt_metacast +32 (int (*)(...))QAbstractState::qt_metacall +40 (int (*)(...))QAbstractState::~QAbstractState +48 (int (*)(...))QAbstractState::~QAbstractState +56 (int (*)(...))QAbstractState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QAbstractState + size=16 align=8 + base size=16 base align=8 +QAbstractState (0x0x7f95edd27820) 0 + vptr=((& QAbstractState::_ZTV14QAbstractState) + 16u) + QObject (0x0x7f95edead540) 0 + primary-for QAbstractState (0x0x7f95edd27820) + +Class QAbstractTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractTransition::QPrivateSignal (0x0x7f95edead660) 0 empty + +Vtable for QAbstractTransition +QAbstractTransition::_ZTV19QAbstractTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractTransition) +16 (int (*)(...))QAbstractTransition::metaObject +24 (int (*)(...))QAbstractTransition::qt_metacast +32 (int (*)(...))QAbstractTransition::qt_metacall +40 (int (*)(...))QAbstractTransition::~QAbstractTransition +48 (int (*)(...))QAbstractTransition::~QAbstractTransition +56 (int (*)(...))QAbstractTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QAbstractTransition + size=16 align=8 + base size=16 base align=8 +QAbstractTransition (0x0x7f95edd27888) 0 + vptr=((& QAbstractTransition::_ZTV19QAbstractTransition) + 16u) + QObject (0x0x7f95edead600) 0 + primary-for QAbstractTransition (0x0x7f95edd27888) + +Class QEventTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QEventTransition::QPrivateSignal (0x0x7f95edead720) 0 empty + +Vtable for QEventTransition +QEventTransition::_ZTV16QEventTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QEventTransition) +16 (int (*)(...))QEventTransition::metaObject +24 (int (*)(...))QEventTransition::qt_metacast +32 (int (*)(...))QEventTransition::qt_metacall +40 (int (*)(...))QEventTransition::~QEventTransition +48 (int (*)(...))QEventTransition::~QEventTransition +56 (int (*)(...))QEventTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QEventTransition::eventTest +120 (int (*)(...))QEventTransition::onTransition + +Class QEventTransition + size=16 align=8 + base size=16 base align=8 +QEventTransition (0x0x7f95edd278f0) 0 + vptr=((& QEventTransition::_ZTV16QEventTransition) + 16u) + QAbstractTransition (0x0x7f95edd27958) 0 + primary-for QEventTransition (0x0x7f95edd278f0) + QObject (0x0x7f95edead6c0) 0 + primary-for QAbstractTransition (0x0x7f95edd27958) + +Class QFinalState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFinalState::QPrivateSignal (0x0x7f95edead7e0) 0 empty + +Vtable for QFinalState +QFinalState::_ZTV11QFinalState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QFinalState) +16 (int (*)(...))QFinalState::metaObject +24 (int (*)(...))QFinalState::qt_metacast +32 (int (*)(...))QFinalState::qt_metacall +40 (int (*)(...))QFinalState::~QFinalState +48 (int (*)(...))QFinalState::~QFinalState +56 (int (*)(...))QFinalState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFinalState::onEntry +120 (int (*)(...))QFinalState::onExit + +Class QFinalState + size=16 align=8 + base size=16 base align=8 +QFinalState (0x0x7f95edd279c0) 0 + vptr=((& QFinalState::_ZTV11QFinalState) + 16u) + QAbstractState (0x0x7f95edd27a28) 0 + primary-for QFinalState (0x0x7f95edd279c0) + QObject (0x0x7f95edead780) 0 + primary-for QAbstractState (0x0x7f95edd27a28) + +Class QHistoryState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QHistoryState::QPrivateSignal (0x0x7f95edead8a0) 0 empty + +Vtable for QHistoryState +QHistoryState::_ZTV13QHistoryState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QHistoryState) +16 (int (*)(...))QHistoryState::metaObject +24 (int (*)(...))QHistoryState::qt_metacast +32 (int (*)(...))QHistoryState::qt_metacall +40 (int (*)(...))QHistoryState::~QHistoryState +48 (int (*)(...))QHistoryState::~QHistoryState +56 (int (*)(...))QHistoryState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QHistoryState::onEntry +120 (int (*)(...))QHistoryState::onExit + +Class QHistoryState + size=16 align=8 + base size=16 base align=8 +QHistoryState (0x0x7f95edd27a90) 0 + vptr=((& QHistoryState::_ZTV13QHistoryState) + 16u) + QAbstractState (0x0x7f95edd27af8) 0 + primary-for QHistoryState (0x0x7f95edd27a90) + QObject (0x0x7f95edead840) 0 + primary-for QAbstractState (0x0x7f95edd27af8) + +Class QSignalTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSignalTransition::QPrivateSignal (0x0x7f95edead960) 0 empty + +Vtable for QSignalTransition +QSignalTransition::_ZTV17QSignalTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QSignalTransition) +16 (int (*)(...))QSignalTransition::metaObject +24 (int (*)(...))QSignalTransition::qt_metacast +32 (int (*)(...))QSignalTransition::qt_metacall +40 (int (*)(...))QSignalTransition::~QSignalTransition +48 (int (*)(...))QSignalTransition::~QSignalTransition +56 (int (*)(...))QSignalTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSignalTransition::eventTest +120 (int (*)(...))QSignalTransition::onTransition + +Class QSignalTransition + size=16 align=8 + base size=16 base align=8 +QSignalTransition (0x0x7f95edd27b60) 0 + vptr=((& QSignalTransition::_ZTV17QSignalTransition) + 16u) + QAbstractTransition (0x0x7f95edd27bc8) 0 + primary-for QSignalTransition (0x0x7f95edd27b60) + QObject (0x0x7f95edead900) 0 + primary-for QAbstractTransition (0x0x7f95edd27bc8) + +Class QState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QState::QPrivateSignal (0x0x7f95edeada20) 0 empty + +Vtable for QState +QState::_ZTV6QState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QState) +16 (int (*)(...))QState::metaObject +24 (int (*)(...))QState::qt_metacast +32 (int (*)(...))QState::qt_metacall +40 (int (*)(...))QState::~QState +48 (int (*)(...))QState::~QState +56 (int (*)(...))QState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QState::onEntry +120 (int (*)(...))QState::onExit + +Class QState + size=16 align=8 + base size=16 base align=8 +QState (0x0x7f95edd27c30) 0 + vptr=((& QState::_ZTV6QState) + 16u) + QAbstractState (0x0x7f95edd27c98) 0 + primary-for QState (0x0x7f95edd27c30) + QObject (0x0x7f95edead9c0) 0 + primary-for QAbstractState (0x0x7f95edd27c98) + +Class QStateMachine::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QStateMachine::QPrivateSignal (0x0x7f95edeadb40) 0 empty + +Vtable for QStateMachine::SignalEvent +QStateMachine::SignalEvent::_ZTVN13QStateMachine11SignalEventE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN13QStateMachine11SignalEventE) +16 (int (*)(...))QStateMachine::SignalEvent::~SignalEvent +24 (int (*)(...))QStateMachine::SignalEvent::~SignalEvent + +Class QStateMachine::SignalEvent + size=48 align=8 + base size=48 base align=8 +QStateMachine::SignalEvent (0x0x7f95edd27e38) 0 + vptr=((& QStateMachine::SignalEvent::_ZTVN13QStateMachine11SignalEventE) + 16u) + QEvent (0x0x7f95edeadba0) 0 + primary-for QStateMachine::SignalEvent (0x0x7f95edd27e38) + +Vtable for QStateMachine::WrappedEvent +QStateMachine::WrappedEvent::_ZTVN13QStateMachine12WrappedEventE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN13QStateMachine12WrappedEventE) +16 (int (*)(...))QStateMachine::WrappedEvent::~WrappedEvent +24 (int (*)(...))QStateMachine::WrappedEvent::~WrappedEvent + +Class QStateMachine::WrappedEvent + size=40 align=8 + base size=40 base align=8 +QStateMachine::WrappedEvent (0x0x7f95edd27ea0) 0 + vptr=((& QStateMachine::WrappedEvent::_ZTVN13QStateMachine12WrappedEventE) + 16u) + QEvent (0x0x7f95edeadc00) 0 + primary-for QStateMachine::WrappedEvent (0x0x7f95edd27ea0) + +Vtable for QStateMachine +QStateMachine::_ZTV13QStateMachine: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QStateMachine) +16 (int (*)(...))QStateMachine::metaObject +24 (int (*)(...))QStateMachine::qt_metacast +32 (int (*)(...))QStateMachine::qt_metacall +40 (int (*)(...))QStateMachine::~QStateMachine +48 (int (*)(...))QStateMachine::~QStateMachine +56 (int (*)(...))QStateMachine::event +64 (int (*)(...))QStateMachine::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QStateMachine::onEntry +120 (int (*)(...))QStateMachine::onExit +128 (int (*)(...))QStateMachine::beginSelectTransitions +136 (int (*)(...))QStateMachine::endSelectTransitions +144 (int (*)(...))QStateMachine::beginMicrostep +152 (int (*)(...))QStateMachine::endMicrostep + +Class QStateMachine + size=16 align=8 + base size=16 base align=8 +QStateMachine (0x0x7f95edd27d00) 0 + vptr=((& QStateMachine::_ZTV13QStateMachine) + 16u) + QState (0x0x7f95edd27d68) 0 + primary-for QStateMachine (0x0x7f95edd27d00) + QAbstractState (0x0x7f95edd27dd0) 0 + primary-for QState (0x0x7f95edd27d68) + QObject (0x0x7f95edeadae0) 0 + primary-for QAbstractState (0x0x7f95edd27dd0) + +Vtable for QException +QException::_ZTV10QException: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QException) +16 (int (*)(...))QException::~QException +24 (int (*)(...))QException::~QException +32 (int (*)(...))std::exception::what +40 (int (*)(...))QException::raise +48 (int (*)(...))QException::clone + +Class QException + size=8 align=8 + base size=8 base align=8 +QException (0x0x7f95edd27f08) 0 nearly-empty + vptr=((& QException::_ZTV10QException) + 16u) + std::exception (0x0x7f95edeadc60) 0 nearly-empty + primary-for QException (0x0x7f95edd27f08) + +Vtable for QUnhandledException +QUnhandledException::_ZTV19QUnhandledException: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QUnhandledException) +16 (int (*)(...))QUnhandledException::~QUnhandledException +24 (int (*)(...))QUnhandledException::~QUnhandledException +32 (int (*)(...))std::exception::what +40 (int (*)(...))QUnhandledException::raise +48 (int (*)(...))QUnhandledException::clone + +Class QUnhandledException + size=8 align=8 + base size=8 base align=8 +QUnhandledException (0x0x7f95edd27f70) 0 nearly-empty + vptr=((& QUnhandledException::_ZTV19QUnhandledException) + 16u) + QException (0x0x7f95edbb5000) 0 nearly-empty + primary-for QUnhandledException (0x0x7f95edd27f70) + std::exception (0x0x7f95edeadcc0) 0 nearly-empty + primary-for QException (0x0x7f95edbb5000) + +Class QtPrivate::ExceptionHolder + size=8 align=8 + base size=8 base align=8 +QtPrivate::ExceptionHolder (0x0x7f95edeadd20) 0 + +Class QtPrivate::ExceptionStore + size=8 align=8 + base size=8 base align=8 +QtPrivate::ExceptionStore (0x0x7f95edeadde0) 0 + +Vtable for QRunnable +QRunnable::_ZTV9QRunnable: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QRunnable) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))QRunnable::~QRunnable +32 (int (*)(...))QRunnable::~QRunnable + +Class QRunnable + size=16 align=8 + base size=12 base align=8 +QRunnable (0x0x7f95edeade40) 0 + vptr=((& QRunnable::_ZTV9QRunnable) + 16u) + +Class QBasicMutex + size=8 align=8 + base size=8 base align=8 +QBasicMutex (0x0x7f95edeadea0) 0 + +Class QMutex + size=8 align=8 + base size=8 base align=8 +QMutex (0x0x7f95edbb51a0) 0 + QBasicMutex (0x0x7f95edbed000) 0 + +Class QMutexLocker + size=8 align=8 + base size=8 base align=8 +QMutexLocker (0x0x7f95edbed060) 0 + +Class QtPrivate::ResultItem + size=16 align=8 + base size=16 base align=8 +QtPrivate::ResultItem (0x0x7f95edbed0c0) 0 + +Class QtPrivate::ResultIteratorBase + size=16 align=8 + base size=12 base align=8 +QtPrivate::ResultIteratorBase (0x0x7f95edbed120) 0 + +Vtable for QtPrivate::ResultStoreBase +QtPrivate::ResultStoreBase::_ZTVN9QtPrivate15ResultStoreBaseE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN9QtPrivate15ResultStoreBaseE) +16 (int (*)(...))QtPrivate::ResultStoreBase::~ResultStoreBase +24 (int (*)(...))QtPrivate::ResultStoreBase::~ResultStoreBase + +Class QtPrivate::ResultStoreBase + size=48 align=8 + base size=44 base align=8 +QtPrivate::ResultStoreBase (0x0x7f95edbed2a0) 0 + vptr=((& QtPrivate::ResultStoreBase::_ZTVN9QtPrivate15ResultStoreBaseE) + 16u) + +Vtable for QFutureInterfaceBase +QFutureInterfaceBase::_ZTV20QFutureInterfaceBase: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI20QFutureInterfaceBase) +16 (int (*)(...))QFutureInterfaceBase::~QFutureInterfaceBase +24 (int (*)(...))QFutureInterfaceBase::~QFutureInterfaceBase + +Class QFutureInterfaceBase + size=16 align=8 + base size=16 base align=8 +QFutureInterfaceBase (0x0x7f95edbed360) 0 + vptr=((& QFutureInterfaceBase::_ZTV20QFutureInterfaceBase) + 16u) + +Class QFutureWatcherBase::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFutureWatcherBase::QPrivateSignal (0x0x7f95edbed6c0) 0 empty + +Vtable for QFutureWatcherBase +QFutureWatcherBase::_ZTV18QFutureWatcherBase: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QFutureWatcherBase) +16 (int (*)(...))QFutureWatcherBase::metaObject +24 (int (*)(...))QFutureWatcherBase::qt_metacast +32 (int (*)(...))QFutureWatcherBase::qt_metacall +40 (int (*)(...))QFutureWatcherBase::~QFutureWatcherBase +48 (int (*)(...))QFutureWatcherBase::~QFutureWatcherBase +56 (int (*)(...))QFutureWatcherBase::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QFutureWatcherBase::connectNotify +104 (int (*)(...))QFutureWatcherBase::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QFutureWatcherBase + size=16 align=8 + base size=16 base align=8 +QFutureWatcherBase (0x0x7f95edbb5a90) 0 + vptr=((& QFutureWatcherBase::_ZTV18QFutureWatcherBase) + 16u) + QObject (0x0x7f95edbed660) 0 + primary-for QFutureWatcherBase (0x0x7f95edbb5a90) + +Class QReadWriteLock + size=8 align=8 + base size=8 base align=8 +QReadWriteLock (0x0x7f95edbed7e0) 0 + +Class QReadLocker + size=8 align=8 + base size=8 base align=8 +QReadLocker (0x0x7f95edbed840) 0 + +Class QWriteLocker + size=8 align=8 + base size=8 base align=8 +QWriteLocker (0x0x7f95edbed8a0) 0 + +Class QSemaphore + size=8 align=8 + base size=8 base align=8 +QSemaphore (0x0x7f95edbed900) 0 + +Class QThread::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QThread::QPrivateSignal (0x0x7f95edbed9c0) 0 empty + +Vtable for QThread +QThread::_ZTV7QThread: 15u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QThread) +16 (int (*)(...))QThread::metaObject +24 (int (*)(...))QThread::qt_metacast +32 (int (*)(...))QThread::qt_metacall +40 (int (*)(...))QThread::~QThread +48 (int (*)(...))QThread::~QThread +56 (int (*)(...))QThread::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QThread::run + +Class QThread + size=16 align=8 + base size=16 base align=8 +QThread (0x0x7f95edbb5e38) 0 + vptr=((& QThread::_ZTV7QThread) + 16u) + QObject (0x0x7f95edbed960) 0 + primary-for QThread (0x0x7f95edbb5e38) + +Class QThreadPool::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QThreadPool::QPrivateSignal (0x0x7f95edbeda80) 0 empty + +Vtable for QThreadPool +QThreadPool::_ZTV11QThreadPool: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QThreadPool) +16 (int (*)(...))QThreadPool::metaObject +24 (int (*)(...))QThreadPool::qt_metacast +32 (int (*)(...))QThreadPool::qt_metacall +40 (int (*)(...))QThreadPool::~QThreadPool +48 (int (*)(...))QThreadPool::~QThreadPool +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QThreadPool + size=16 align=8 + base size=16 base align=8 +QThreadPool (0x0x7f95edbb5ea0) 0 + vptr=((& QThreadPool::_ZTV11QThreadPool) + 16u) + QObject (0x0x7f95edbeda20) 0 + primary-for QThreadPool (0x0x7f95edbb5ea0) + +Class QThreadStorageData + size=4 align=4 + base size=4 base align=4 +QThreadStorageData (0x0x7f95edbedae0) 0 + +Class QWaitCondition + size=8 align=8 + base size=8 base align=8 +QWaitCondition (0x0x7f95edbedba0) 0 + +Class QBitArray + size=8 align=8 + base size=8 base align=8 +QBitArray (0x0x7f95ed9a7180) 0 + +Class QBitRef + size=16 align=8 + base size=12 base align=8 +QBitRef (0x0x7f95ed9a71e0) 0 + +Class QByteArrayMatcher::Data + size=272 align=8 + base size=272 base align=8 +QByteArrayMatcher::Data (0x0x7f95ed9a7360) 0 + +Class QByteArrayMatcher + size=1040 align=8 + base size=1040 base align=8 +QByteArrayMatcher (0x0x7f95ed9a7300) 0 + +Class QCollatorSortKey + size=8 align=8 + base size=8 base align=8 +QCollatorSortKey (0x0x7f95ed9a74e0) 0 + +Class QCollator + size=8 align=8 + base size=8 base align=8 +QCollator (0x0x7f95ed9a75a0) 0 + +Class QCommandLineOption + size=8 align=8 + base size=8 base align=8 +QCommandLineOption (0x0x7f95ed9a7780) 0 + +Class QCommandLineParser + size=8 align=8 + base size=8 base align=8 +QCommandLineParser (0x0x7f95ed9a7900) 0 + +Class QCryptographicHash + size=8 align=8 + base size=8 base align=8 +QCryptographicHash (0x0x7f95ed9a7960) 0 + +Class QElapsedTimer + size=16 align=8 + base size=16 base align=8 +QElapsedTimer (0x0x7f95ed9a79c0) 0 + +Class QPoint + size=8 align=4 + base size=8 base align=4 +QPoint (0x0x7f95ed9a7a20) 0 + +Class QPointF + size=16 align=8 + base size=16 base align=8 +QPointF (0x0x7f95ed9a7b40) 0 + +Class QLine + size=16 align=4 + base size=16 base align=4 +QLine (0x0x7f95ed9a7c60) 0 + +Class QLineF + size=32 align=8 + base size=32 base align=8 +QLineF (0x0x7f95ed9a7d80) 0 + +Class QLinkedListData + size=32 align=8 + base size=32 base align=8 +QLinkedListData (0x0x7f95ed9a7ea0) 0 + +Class QMargins + size=16 align=4 + base size=16 base align=4 +QMargins (0x0x7f95ed75a240) 0 + +Class QMarginsF + size=32 align=8 + base size=32 base align=8 +QMarginsF (0x0x7f95ed75a360) 0 + +Class QMessageAuthenticationCode + size=8 align=8 + base size=8 base align=8 +QMessageAuthenticationCode (0x0x7f95ed75a480) 0 + +Class QSize + size=8 align=4 + base size=8 base align=4 +QSize (0x0x7f95ed75a540) 0 + +Class QSizeF + size=16 align=8 + base size=16 base align=8 +QSizeF (0x0x7f95ed75a660) 0 + +Class QRect + size=16 align=4 + base size=16 base align=4 +QRect (0x0x7f95ed75a780) 0 + +Class QRectF + size=32 align=8 + base size=32 base align=8 +QRectF (0x0x7f95ed75a8a0) 0 + +Class QRegularExpression + size=8 align=8 + base size=8 base align=8 +QRegularExpression (0x0x7f95ed75a9c0) 0 + +Class QRegularExpressionMatch + size=8 align=8 + base size=8 base align=8 +QRegularExpressionMatch (0x0x7f95ed75acc0) 0 + +Class QRegularExpressionMatchIterator + size=8 align=8 + base size=8 base align=8 +QRegularExpressionMatchIterator (0x0x7f95ed75ae40) 0 + +Class QAbstractConcatenable + size=1 align=1 + base size=0 base align=1 +QAbstractConcatenable (0x0x7f95ed63f0c0) 0 empty + +Class QTextBoundaryFinder + size=48 align=8 + base size=48 base align=8 +QTextBoundaryFinder (0x0x7f95ed63fae0) 0 + +Class QTimeLine::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTimeLine::QPrivateSignal (0x0x7f95ed63fc60) 0 empty + +Vtable for QTimeLine +QTimeLine::_ZTV9QTimeLine: 15u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QTimeLine) +16 (int (*)(...))QTimeLine::metaObject +24 (int (*)(...))QTimeLine::qt_metacast +32 (int (*)(...))QTimeLine::qt_metacall +40 (int (*)(...))QTimeLine::~QTimeLine +48 (int (*)(...))QTimeLine::~QTimeLine +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QTimeLine::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QTimeLine::valueForTime + +Class QTimeLine + size=16 align=8 + base size=16 base align=8 +QTimeLine (0x0x7f95ed6657b8) 0 + vptr=((& QTimeLine::_ZTV9QTimeLine) + 16u) + QObject (0x0x7f95ed63fc00) 0 + primary-for QTimeLine (0x0x7f95ed6657b8) + +Class QTimeZone::OffsetData + size=32 align=8 + base size=28 base align=8 +QTimeZone::OffsetData (0x0x7f95ed63fd20) 0 + +Class QTimeZone + size=8 align=8 + base size=8 base align=8 +QTimeZone (0x0x7f95ed63fcc0) 0 + +Class QXmlStreamStringRef + size=16 align=8 + base size=16 base align=8 +QXmlStreamStringRef (0x0x7f95ed63ff60) 0 + +Class QXmlStreamAttribute + size=80 align=8 + base size=73 base align=8 +QXmlStreamAttribute (0x0x7f95ed337000) 0 + +Class QXmlStreamAttributes + size=8 align=8 + base size=8 base align=8 +QXmlStreamAttributes (0x0x7f95ed665a28) 0 + QVector (0x0x7f95ed3371e0) 0 + +Class QXmlStreamNamespaceDeclaration + size=40 align=8 + base size=40 base align=8 +QXmlStreamNamespaceDeclaration (0x0x7f95ed337240) 0 + +Class QXmlStreamNotationDeclaration + size=56 align=8 + base size=56 base align=8 +QXmlStreamNotationDeclaration (0x0x7f95ed337360) 0 + +Class QXmlStreamEntityDeclaration + size=88 align=8 + base size=88 base align=8 +QXmlStreamEntityDeclaration (0x0x7f95ed337480) 0 + +Vtable for QXmlStreamEntityResolver +QXmlStreamEntityResolver::_ZTV24QXmlStreamEntityResolver: 6u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI24QXmlStreamEntityResolver) +16 (int (*)(...))QXmlStreamEntityResolver::~QXmlStreamEntityResolver +24 (int (*)(...))QXmlStreamEntityResolver::~QXmlStreamEntityResolver +32 (int (*)(...))QXmlStreamEntityResolver::resolveEntity +40 (int (*)(...))QXmlStreamEntityResolver::resolveUndeclaredEntity + +Class QXmlStreamEntityResolver + size=8 align=8 + base size=8 base align=8 +QXmlStreamEntityResolver (0x0x7f95ed3375a0) 0 nearly-empty + vptr=((& QXmlStreamEntityResolver::_ZTV24QXmlStreamEntityResolver) + 16u) + +Class QXmlStreamReader + size=8 align=8 + base size=8 base align=8 +QXmlStreamReader (0x0x7f95ed337600) 0 + +Class QXmlStreamWriter + size=8 align=8 + base size=8 base align=8 +QXmlStreamWriter (0x0x7f95ed337720) 0 + +Class QGeoAddress + size=8 align=8 + base size=8 base align=8 +QGeoAddress (0x0x7f95ed337840) 0 + +Class QGeoCoordinate + size=8 align=8 + base size=8 base align=8 +QGeoCoordinate (0x0x7f95ed337ae0) 0 + +Class QGeoShape + size=8 align=8 + base size=8 base align=8 +QGeoShape (0x0x7f95ed337d80) 0 + +Class QGeoAreaMonitorInfo + size=8 align=8 + base size=8 base align=8 +QGeoAreaMonitorInfo (0x0x7f95ed447060) 0 + +Class QGeoPositionInfo + size=8 align=8 + base size=8 base align=8 +QGeoPositionInfo (0x0x7f95ed447120) 0 + +Class QGeoPositionInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoPositionInfoSource::QPrivateSignal (0x0x7f95ed4471e0) 0 empty + +Vtable for QGeoPositionInfoSource +QGeoPositionInfoSource::_ZTV22QGeoPositionInfoSource: 23u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI22QGeoPositionInfoSource) +16 (int (*)(...))QGeoPositionInfoSource::metaObject +24 (int (*)(...))QGeoPositionInfoSource::qt_metacast +32 (int (*)(...))QGeoPositionInfoSource::qt_metacall +40 (int (*)(...))QGeoPositionInfoSource::~QGeoPositionInfoSource +48 (int (*)(...))QGeoPositionInfoSource::~QGeoPositionInfoSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoPositionInfoSource::setUpdateInterval +120 (int (*)(...))QGeoPositionInfoSource::setPreferredPositioningMethods +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual + +Class QGeoPositionInfoSource + size=24 align=8 + base size=24 base align=8 +QGeoPositionInfoSource (0x0x7f95ed665d00) 0 + vptr=((& QGeoPositionInfoSource::_ZTV22QGeoPositionInfoSource) + 16u) + QObject (0x0x7f95ed447180) 0 + primary-for QGeoPositionInfoSource (0x0x7f95ed665d00) + +Class QGeoAreaMonitorSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoAreaMonitorSource::QPrivateSignal (0x0x7f95ed447360) 0 empty + +Vtable for QGeoAreaMonitorSource +QGeoAreaMonitorSource::_ZTV21QGeoAreaMonitorSource: 23u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QGeoAreaMonitorSource) +16 (int (*)(...))QGeoAreaMonitorSource::metaObject +24 (int (*)(...))QGeoAreaMonitorSource::qt_metacast +32 (int (*)(...))QGeoAreaMonitorSource::qt_metacall +40 (int (*)(...))QGeoAreaMonitorSource::~QGeoAreaMonitorSource +48 (int (*)(...))QGeoAreaMonitorSource::~QGeoAreaMonitorSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoAreaMonitorSource::setPositionInfoSource +120 (int (*)(...))QGeoAreaMonitorSource::positionInfoSource +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual + +Class QGeoAreaMonitorSource + size=24 align=8 + base size=24 base align=8 +QGeoAreaMonitorSource (0x0x7f95ed665e38) 0 + vptr=((& QGeoAreaMonitorSource::_ZTV21QGeoAreaMonitorSource) + 16u) + QObject (0x0x7f95ed447300) 0 + primary-for QGeoAreaMonitorSource (0x0x7f95ed665e38) + +Class QGeoCircle + size=8 align=8 + base size=8 base align=8 +QGeoCircle (0x0x7f95ed665ea0) 0 + QGeoShape (0x0x7f95ed4473c0) 0 + +Class QGeoLocation + size=8 align=8 + base size=8 base align=8 +QGeoLocation (0x0x7f95ed447600) 0 + +Class QGeoSatelliteInfo + size=8 align=8 + base size=8 base align=8 +QGeoSatelliteInfo (0x0x7f95ed4478a0) 0 + +Class QGeoSatelliteInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoSatelliteInfoSource::QPrivateSignal (0x0x7f95ed447960) 0 empty + +Vtable for QGeoSatelliteInfoSource +QGeoSatelliteInfoSource::_ZTV23QGeoSatelliteInfoSource: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QGeoSatelliteInfoSource) +16 (int (*)(...))QGeoSatelliteInfoSource::metaObject +24 (int (*)(...))QGeoSatelliteInfoSource::qt_metacast +32 (int (*)(...))QGeoSatelliteInfoSource::qt_metacall +40 (int (*)(...))QGeoSatelliteInfoSource::~QGeoSatelliteInfoSource +48 (int (*)(...))QGeoSatelliteInfoSource::~QGeoSatelliteInfoSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoSatelliteInfoSource::setUpdateInterval +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual + +Class QGeoSatelliteInfoSource + size=24 align=8 + base size=24 base align=8 +QGeoSatelliteInfoSource (0x0x7f95ed060000) 0 + vptr=((& QGeoSatelliteInfoSource::_ZTV23QGeoSatelliteInfoSource) + 16u) + QObject (0x0x7f95ed447900) 0 + primary-for QGeoSatelliteInfoSource (0x0x7f95ed060000) + +Vtable for QGeoPositionInfoSourceFactory +QGeoPositionInfoSourceFactory::_ZTV29QGeoPositionInfoSourceFactory: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI29QGeoPositionInfoSourceFactory) +16 (int (*)(...))QGeoPositionInfoSourceFactory::~QGeoPositionInfoSourceFactory +24 (int (*)(...))QGeoPositionInfoSourceFactory::~QGeoPositionInfoSourceFactory +32 (int (*)(...))__cxa_pure_virtual +40 (int (*)(...))__cxa_pure_virtual +48 (int (*)(...))__cxa_pure_virtual + +Class QGeoPositionInfoSourceFactory + size=8 align=8 + base size=8 base align=8 +QGeoPositionInfoSourceFactory (0x0x7f95ed447a20) 0 nearly-empty + vptr=((& QGeoPositionInfoSourceFactory::_ZTV29QGeoPositionInfoSourceFactory) + 16u) + +Class QGeoRectangle + size=8 align=8 + base size=8 base align=8 +QGeoRectangle (0x0x7f95ed060068) 0 + QGeoShape (0x0x7f95ed447ae0) 0 + +Class QNmeaPositionInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QNmeaPositionInfoSource::QPrivateSignal (0x0x7f95ed447e40) 0 empty + +Vtable for QNmeaPositionInfoSource +QNmeaPositionInfoSource::_ZTV23QNmeaPositionInfoSource: 24u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QNmeaPositionInfoSource) +16 (int (*)(...))QNmeaPositionInfoSource::metaObject +24 (int (*)(...))QNmeaPositionInfoSource::qt_metacast +32 (int (*)(...))QNmeaPositionInfoSource::qt_metacall +40 (int (*)(...))QNmeaPositionInfoSource::~QNmeaPositionInfoSource +48 (int (*)(...))QNmeaPositionInfoSource::~QNmeaPositionInfoSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QNmeaPositionInfoSource::setUpdateInterval +120 (int (*)(...))QGeoPositionInfoSource::setPreferredPositioningMethods +128 (int (*)(...))QNmeaPositionInfoSource::lastKnownPosition +136 (int (*)(...))QNmeaPositionInfoSource::supportedPositioningMethods +144 (int (*)(...))QNmeaPositionInfoSource::minimumUpdateInterval +152 (int (*)(...))QNmeaPositionInfoSource::error +160 (int (*)(...))QNmeaPositionInfoSource::startUpdates +168 (int (*)(...))QNmeaPositionInfoSource::stopUpdates +176 (int (*)(...))QNmeaPositionInfoSource::requestUpdate +184 (int (*)(...))QNmeaPositionInfoSource::parsePosInfoFromNmeaData + +Class QNmeaPositionInfoSource + size=32 align=8 + base size=32 base align=8 +QNmeaPositionInfoSource (0x0x7f95ed060138) 0 + vptr=((& QNmeaPositionInfoSource::_ZTV23QNmeaPositionInfoSource) + 16u) + QGeoPositionInfoSource (0x0x7f95ed0601a0) 0 + primary-for QNmeaPositionInfoSource (0x0x7f95ed060138) + QObject (0x0x7f95ed447de0) 0 + primary-for QGeoPositionInfoSource (0x0x7f95ed0601a0) + diff --git a/tests/auto/bic/data/QtPositioning.5.4.0.linux-gcc-amd64.txt b/tests/auto/bic/data/QtPositioning.5.4.0.linux-gcc-amd64.txt new file mode 100644 index 0000000..3746cf7 --- /dev/null +++ b/tests/auto/bic/data/QtPositioning.5.4.0.linux-gcc-amd64.txt @@ -0,0 +1,3854 @@ +Class std::__true_type + size=1 align=1 + base size=0 base align=1 +std::__true_type (0x0x7fb610c07060) 0 empty + +Class std::__false_type + size=1 align=1 + base size=0 base align=1 +std::__false_type (0x0x7fb610c070c0) 0 empty + +Class std::input_iterator_tag + size=1 align=1 + base size=0 base align=1 +std::input_iterator_tag (0x0x7fb610c48c60) 0 empty + +Class std::output_iterator_tag + size=1 align=1 + base size=0 base align=1 +std::output_iterator_tag (0x0x7fb610c48cc0) 0 empty + +Class std::forward_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::forward_iterator_tag (0x0x7fb610bdc958) 0 empty + std::input_iterator_tag (0x0x7fb610c48d20) 0 empty + +Class std::bidirectional_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::bidirectional_iterator_tag (0x0x7fb610bdc9c0) 0 empty + std::forward_iterator_tag (0x0x7fb610bdca28) 0 empty + std::input_iterator_tag (0x0x7fb610c48d80) 0 empty + +Class std::random_access_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::random_access_iterator_tag (0x0x7fb610bdca90) 0 empty + std::bidirectional_iterator_tag (0x0x7fb610bdcaf8) 0 empty + std::forward_iterator_tag (0x0x7fb610bdcb60) 0 empty + std::input_iterator_tag (0x0x7fb610c48de0) 0 empty + +Class wait + size=4 align=4 + base size=4 base align=4 +wait (0x0x7fb610c79960) 0 + +Class __locale_struct + size=232 align=8 + base size=232 base align=8 +__locale_struct (0x0x7fb610c79ba0) 0 + +Class timespec + size=16 align=8 + base size=16 base align=8 +timespec (0x0x7fb610c79c60) 0 + +Class timeval + size=16 align=8 + base size=16 base align=8 +timeval (0x0x7fb610c79cc0) 0 + +Class pthread_attr_t + size=56 align=8 + base size=56 base align=8 +pthread_attr_t (0x0x7fb610c79d80) 0 + +Class __pthread_internal_list + size=16 align=8 + base size=16 base align=8 +__pthread_internal_list (0x0x7fb610c79de0) 0 + +Class random_data + size=48 align=8 + base size=48 base align=8 +random_data (0x0x7fb610d642a0) 0 + +Class drand48_data + size=24 align=8 + base size=24 base align=8 +drand48_data (0x0x7fb610d64300) 0 + +Vtable for std::exception +std::exception::_ZTVSt9exception: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt9exception) +16 (int (*)(...))std::exception::~exception +24 (int (*)(...))std::exception::~exception +32 (int (*)(...))std::exception::what + +Class std::exception + size=8 align=8 + base size=8 base align=8 +std::exception (0x0x7fb610d64360) 0 nearly-empty + vptr=((& std::exception::_ZTVSt9exception) + 16u) + +Vtable for std::bad_exception +std::bad_exception::_ZTVSt13bad_exception: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt13bad_exception) +16 (int (*)(...))std::bad_exception::~bad_exception +24 (int (*)(...))std::bad_exception::~bad_exception +32 (int (*)(...))std::bad_exception::what + +Class std::bad_exception + size=8 align=8 + base size=8 base align=8 +std::bad_exception (0x0x7fb610bdcea0) 0 nearly-empty + vptr=((& std::bad_exception::_ZTVSt13bad_exception) + 16u) + std::exception (0x0x7fb610d643c0) 0 nearly-empty + primary-for std::bad_exception (0x0x7fb610bdcea0) + +Vtable for std::bad_alloc +std::bad_alloc::_ZTVSt9bad_alloc: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt9bad_alloc) +16 (int (*)(...))std::bad_alloc::~bad_alloc +24 (int (*)(...))std::bad_alloc::~bad_alloc +32 (int (*)(...))std::bad_alloc::what + +Class std::bad_alloc + size=8 align=8 + base size=8 base align=8 +std::bad_alloc (0x0x7fb610bdcf08) 0 nearly-empty + vptr=((& std::bad_alloc::_ZTVSt9bad_alloc) + 16u) + std::exception (0x0x7fb610d64420) 0 nearly-empty + primary-for std::bad_alloc (0x0x7fb610bdcf08) + +Class std::nothrow_t + size=1 align=1 + base size=0 base align=1 +std::nothrow_t (0x0x7fb610d64480) 0 empty + +Class qIsNull(double)::U + size=8 align=8 + base size=8 base align=8 +qIsNull(double)::U (0x0x7fb60fb587e0) 0 + +Class qIsNull(float)::U + size=4 align=4 + base size=4 base align=4 +qIsNull(float)::U (0x0x7fb60fb58840) 0 + +Class QtPrivate::big_ + size=2 align=1 + base size=2 base align=1 +QtPrivate::big_ (0x0x7fb60fb58a20) 0 + +Class QSysInfo + size=1 align=1 + base size=0 base align=1 +QSysInfo (0x0x7fb60f8c8300) 0 empty + +Class QMessageLogContext + size=32 align=8 + base size=32 base align=8 +QMessageLogContext (0x0x7fb60f8c8360) 0 + +Class QMessageLogger + size=32 align=8 + base size=32 base align=8 +QMessageLogger (0x0x7fb60f8c83c0) 0 + +Class QFlag + size=4 align=4 + base size=4 base align=4 +QFlag (0x0x7fb60f8c8420) 0 + +Class QIncompatibleFlag + size=4 align=4 + base size=4 base align=4 +QIncompatibleFlag (0x0x7fb60f8c8540) 0 + +Class QAtomicInt + size=4 align=4 + base size=4 base align=4 +QAtomicInt (0x0x7fb60f8cd680) 0 + QAtomicInteger (0x0x7fb60f8cd6e8) 0 + QBasicAtomicInteger (0x0x7fb60f70e060) 0 + +Class QInternal + size=1 align=1 + base size=0 base align=1 +QInternal (0x0x7fb60f4e4180) 0 empty + +Class QGenericArgument + size=16 align=8 + base size=16 base align=8 +QGenericArgument (0x0x7fb60f1ae0c0) 0 + +Class QGenericReturnArgument + size=16 align=8 + base size=16 base align=8 +QGenericReturnArgument (0x0x7fb60f452820) 0 + QGenericArgument (0x0x7fb60f1ae120) 0 + +Class QMetaObject + size=48 align=8 + base size=48 base align=8 +QMetaObject (0x0x7fb60f1ae2a0) 0 + +Class QMetaObject::Connection + size=8 align=8 + base size=8 base align=8 +QMetaObject::Connection (0x0x7fb60f1ae3c0) 0 + +Class QLatin1Char + size=1 align=1 + base size=1 base align=1 +QLatin1Char (0x0x7fb60f1ae600) 0 + +Class QChar + size=2 align=2 + base size=2 base align=2 +QChar (0x0x7fb60f1ae660) 0 + +Class QtPrivate::RefCount + size=4 align=4 + base size=4 base align=4 +QtPrivate::RefCount (0x0x7fb60f1ae780) 0 + +Class QArrayData + size=24 align=8 + base size=24 base align=8 +QArrayData (0x0x7fb60f1ae7e0) 0 + +Class QtPrivate::QContainerImplHelper + size=1 align=1 + base size=0 base align=1 +QtPrivate::QContainerImplHelper (0x0x7fb60f1aeae0) 0 empty + +Class lconv + size=96 align=8 + base size=96 base align=8 +lconv (0x0x7fb60f1aee40) 0 + +Vtable for __cxxabiv1::__forced_unwind +__cxxabiv1::__forced_unwind::_ZTVN10__cxxabiv115__forced_unwindE: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN10__cxxabiv115__forced_unwindE) +16 (int (*)(...))__cxxabiv1::__forced_unwind::~__forced_unwind +24 (int (*)(...))__cxxabiv1::__forced_unwind::~__forced_unwind +32 (int (*)(...))__cxa_pure_virtual + +Class __cxxabiv1::__forced_unwind + size=8 align=8 + base size=8 base align=8 +__cxxabiv1::__forced_unwind (0x0x7fb60f1aeea0) 0 nearly-empty + vptr=((& __cxxabiv1::__forced_unwind::_ZTVN10__cxxabiv115__forced_unwindE) + 16u) + +Class sched_param + size=4 align=4 + base size=4 base align=4 +sched_param (0x0x7fb60efe4960) 0 + +Class __sched_param + size=4 align=4 + base size=4 base align=4 +__sched_param (0x0x7fb60efe49c0) 0 + +Class timex + size=208 align=8 + base size=208 base align=8 +timex (0x0x7fb60efe4a80) 0 + +Class tm + size=56 align=8 + base size=56 base align=8 +tm (0x0x7fb60efe4ae0) 0 + +Class itimerspec + size=32 align=8 + base size=32 base align=8 +itimerspec (0x0x7fb60efe4b40) 0 + +Class _pthread_cleanup_buffer + size=32 align=8 + base size=32 base align=8 +_pthread_cleanup_buffer (0x0x7fb60efe4ba0) 0 + +Class __pthread_cleanup_frame + size=24 align=8 + base size=24 base align=8 +__pthread_cleanup_frame (0x0x7fb60efe4cc0) 0 + +Class __pthread_cleanup_class + size=24 align=8 + base size=24 base align=8 +__pthread_cleanup_class (0x0x7fb60efe4d20) 0 + +Class QByteArrayDataPtr + size=8 align=8 + base size=8 base align=8 +QByteArrayDataPtr (0x0x7fb60f0ed4e0) 0 + +Class QByteArray + size=8 align=8 + base size=8 base align=8 +QByteArray (0x0x7fb60f0ed540) 0 + +Class QByteRef + size=16 align=8 + base size=12 base align=8 +QByteRef (0x0x7fb60f0ed6c0) 0 + +Class QLatin1String + size=16 align=8 + base size=16 base align=8 +QLatin1String (0x0x7fb60f0ed7e0) 0 + +Class QStringDataPtr + size=8 align=8 + base size=8 base align=8 +QStringDataPtr (0x0x7fb60f0ed960) 0 + +Class QString::Null + size=1 align=1 + base size=0 base align=1 +QString::Null (0x0x7fb60f0eda20) 0 empty + +Class QString + size=8 align=8 + base size=8 base align=8 +QString (0x0x7fb60f0ed9c0) 0 + +Class QCharRef + size=16 align=8 + base size=12 base align=8 +QCharRef (0x0x7fb60f0edba0) 0 + +Class QStringRef + size=16 align=8 + base size=16 base align=8 +QStringRef (0x0x7fb60f0ede40) 0 + +Class std::locale + size=8 align=8 + base size=8 base align=8 +std::locale (0x0x7fb60ea19060) 0 + +Vtable for std::locale::facet +std::locale::facet::_ZTVNSt6locale5facetE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt6locale5facetE) +16 (int (*)(...))std::locale::facet::~facet +24 (int (*)(...))std::locale::facet::~facet + +Class std::locale::facet + size=16 align=8 + base size=12 base align=8 +std::locale::facet (0x0x7fb60ea190c0) 0 + vptr=((& std::locale::facet::_ZTVNSt6locale5facetE) + 16u) + +Class std::locale::id + size=8 align=8 + base size=8 base align=8 +std::locale::id (0x0x7fb60ea19120) 0 + +Class std::locale::_Impl + size=40 align=8 + base size=40 base align=8 +std::locale::_Impl (0x0x7fb60ea19180) 0 + +Vtable for std::ios_base::failure +std::ios_base::failure::_ZTVNSt8ios_base7failureE: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt8ios_base7failureE) +16 (int (*)(...))std::ios_base::failure::~failure +24 (int (*)(...))std::ios_base::failure::~failure +32 (int (*)(...))std::ios_base::failure::what + +Class std::ios_base::failure + size=16 align=8 + base size=16 base align=8 +std::ios_base::failure (0x0x7fb60ead6000) 0 + vptr=((& std::ios_base::failure::_ZTVNSt8ios_base7failureE) + 16u) + std::exception (0x0x7fb60ea195a0) 0 nearly-empty + primary-for std::ios_base::failure (0x0x7fb60ead6000) + +Class std::ios_base::_Callback_list + size=24 align=8 + base size=24 base align=8 +std::ios_base::_Callback_list (0x0x7fb60ea19600) 0 + +Class std::ios_base::_Words + size=16 align=8 + base size=16 base align=8 +std::ios_base::_Words (0x0x7fb60ea19660) 0 + +Class std::ios_base::Init + size=1 align=1 + base size=0 base align=1 +std::ios_base::Init (0x0x7fb60ea196c0) 0 empty + +Vtable for std::ios_base +std::ios_base::_ZTVSt8ios_base: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt8ios_base) +16 (int (*)(...))std::ios_base::~ios_base +24 (int (*)(...))std::ios_base::~ios_base + +Class std::ios_base + size=216 align=8 + base size=216 base align=8 +std::ios_base (0x0x7fb60ea19540) 0 + vptr=((& std::ios_base::_ZTVSt8ios_base) + 16u) + +Class std::ctype_base + size=1 align=1 + base size=0 base align=1 +std::ctype_base (0x0x7fb60ea19840) 0 empty + +Class std::__num_base + size=1 align=1 + base size=0 base align=1 +std::__num_base (0x0x7fb60ea19f00) 0 empty + +VTT for std::basic_ostream +std::basic_ostream::_ZTTSo: 2u entries +0 ((& std::basic_ostream::_ZTVSo) + 24u) +8 ((& std::basic_ostream::_ZTVSo) + 64u) + +VTT for std::basic_ostream +std::basic_ostream::_ZTTSt13basic_ostreamIwSt11char_traitsIwEE: 2u entries +0 ((& std::basic_ostream::_ZTVSt13basic_ostreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_ostream::_ZTVSt13basic_ostreamIwSt11char_traitsIwEE) + 64u) + +VTT for std::basic_istream +std::basic_istream::_ZTTSi: 2u entries +0 ((& std::basic_istream::_ZTVSi) + 24u) +8 ((& std::basic_istream::_ZTVSi) + 64u) + +VTT for std::basic_istream +std::basic_istream::_ZTTSt13basic_istreamIwSt11char_traitsIwEE: 2u entries +0 ((& std::basic_istream::_ZTVSt13basic_istreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_istream::_ZTVSt13basic_istreamIwSt11char_traitsIwEE) + 64u) + +Construction vtable for std::basic_istream (0x0x7fb60e693e38 instance) in std::basic_iostream +std::basic_iostream::_ZTCSd0_Si: 10u entries +0 24u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISi) +24 (int (*)(...))std::basic_istream<_CharT, _Traits>::~basic_istream > +32 (int (*)(...))std::basic_istream<_CharT, _Traits>::~basic_istream > +40 18446744073709551592u +48 (int (*)(...))-24 +56 (int (*)(...))(& _ZTISi) +64 (int (*)(...))std::basic_istream::_ZTv0_n24_NSiD1Ev +72 (int (*)(...))std::basic_istream::_ZTv0_n24_NSiD0Ev + +Construction vtable for std::basic_ostream (0x0x7fb60e693f08 instance) in std::basic_iostream +std::basic_iostream::_ZTCSd16_So: 10u entries +0 8u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISo) +24 (int (*)(...))std::basic_ostream<_CharT, _Traits>::~basic_ostream > +32 (int (*)(...))std::basic_ostream<_CharT, _Traits>::~basic_ostream > +40 18446744073709551608u +48 (int (*)(...))-8 +56 (int (*)(...))(& _ZTISo) +64 (int (*)(...))std::basic_ostream::_ZTv0_n24_NSoD1Ev +72 (int (*)(...))std::basic_ostream::_ZTv0_n24_NSoD0Ev + +VTT for std::basic_iostream +std::basic_iostream::_ZTTSd: 7u entries +0 ((& std::basic_iostream::_ZTVSd) + 24u) +8 ((& std::basic_iostream::_ZTCSd0_Si) + 24u) +16 ((& std::basic_iostream::_ZTCSd0_Si) + 64u) +24 ((& std::basic_iostream::_ZTCSd16_So) + 24u) +32 ((& std::basic_iostream::_ZTCSd16_So) + 64u) +40 ((& std::basic_iostream::_ZTVSd) + 104u) +48 ((& std::basic_iostream::_ZTVSd) + 64u) + +Construction vtable for std::basic_istream (0x0x7fb60e6933a8 instance) in std::basic_iostream +std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E: 10u entries +0 24u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISt13basic_istreamIwSt11char_traitsIwEE) +24 (int (*)(...))std::basic_istream<_CharT, _Traits>::~basic_istream > +32 (int (*)(...))std::basic_istream<_CharT, _Traits>::~basic_istream > +40 18446744073709551592u +48 (int (*)(...))-24 +56 (int (*)(...))(& _ZTISt13basic_istreamIwSt11char_traitsIwEE) +64 (int (*)(...))std::basic_istream::_ZTv0_n24_NSt13basic_istreamIwSt11char_traitsIwEED1Ev +72 (int (*)(...))std::basic_istream::_ZTv0_n24_NSt13basic_istreamIwSt11char_traitsIwEED0Ev + +Construction vtable for std::basic_ostream (0x0x7fb60e6934e0 instance) in std::basic_iostream +std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E: 10u entries +0 8u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISt13basic_ostreamIwSt11char_traitsIwEE) +24 (int (*)(...))std::basic_ostream<_CharT, _Traits>::~basic_ostream > +32 (int (*)(...))std::basic_ostream<_CharT, _Traits>::~basic_ostream > +40 18446744073709551608u +48 (int (*)(...))-8 +56 (int (*)(...))(& _ZTISt13basic_ostreamIwSt11char_traitsIwEE) +64 (int (*)(...))std::basic_ostream::_ZTv0_n24_NSt13basic_ostreamIwSt11char_traitsIwEED1Ev +72 (int (*)(...))std::basic_ostream::_ZTv0_n24_NSt13basic_ostreamIwSt11char_traitsIwEED0Ev + +VTT for std::basic_iostream +std::basic_iostream::_ZTTSt14basic_iostreamIwSt11char_traitsIwEE: 7u entries +0 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E) + 24u) +16 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E) + 64u) +24 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E) + 24u) +32 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E) + 64u) +40 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 104u) +48 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 64u) + +Class std::__detail::_List_node_base + size=16 align=8 + base size=16 base align=8 +std::__detail::_List_node_base (0x0x7fb60e6e1300) 0 + +Class QListData::Data + size=24 align=8 + base size=24 base align=8 +QListData::Data (0x0x7fb60e6e1660) 0 + +Class QListData + size=8 align=8 + base size=8 base align=8 +QListData (0x0x7fb60e6e1600) 0 + +Class QScopedPointerPodDeleter + size=1 align=1 + base size=0 base align=1 +QScopedPointerPodDeleter (0x0x7fb60e6e1b40) 0 empty + +Class std::_Bit_reference + size=16 align=8 + base size=16 base align=8 +std::_Bit_reference (0x0x7fb60e5678a0) 0 + +Class std::_Bit_iterator_base + size=16 align=8 + base size=12 base align=8 +std::_Bit_iterator_base (0x0x7fb60e766750) 0 + std::iterator (0x0x7fb60e567960) 0 empty + +Class std::_Bit_iterator + size=16 align=8 + base size=12 base align=8 +std::_Bit_iterator (0x0x7fb60e7667b8) 0 + std::_Bit_iterator_base (0x0x7fb60e766820) 0 + std::iterator (0x0x7fb60e5679c0) 0 empty + +Class std::_Bit_const_iterator + size=16 align=8 + base size=12 base align=8 +std::_Bit_const_iterator (0x0x7fb60e766888) 0 + std::_Bit_iterator_base (0x0x7fb60e7668f0) 0 + std::iterator (0x0x7fb60e567a20) 0 empty + +Class std::_Rb_tree_node_base + size=32 align=8 + base size=32 base align=8 +std::_Rb_tree_node_base (0x0x7fb60e567de0) 0 + +Class QtPrivate::AbstractDebugStreamFunction + size=16 align=8 + base size=16 base align=8 +QtPrivate::AbstractDebugStreamFunction (0x0x7fb60e34d240) 0 + +Class QtPrivate::AbstractComparatorFunction + size=24 align=8 + base size=24 base align=8 +QtPrivate::AbstractComparatorFunction (0x0x7fb60e34d300) 0 + +Class QtPrivate::AbstractConverterFunction + size=8 align=8 + base size=8 base align=8 +QtPrivate::AbstractConverterFunction (0x0x7fb60e34d3c0) 0 + +Class QMetaType + size=80 align=8 + base size=80 base align=8 +QMetaType (0x0x7fb60e34d7e0) 0 + +Class QtMetaTypePrivate::VariantData + size=24 align=8 + base size=20 base align=8 +QtMetaTypePrivate::VariantData (0x0x7fb60e34db40) 0 + +Class QtMetaTypePrivate::VectorBoolElements + size=1 align=1 + base size=0 base align=1 +QtMetaTypePrivate::VectorBoolElements (0x0x7fb60e34dc60) 0 empty + +Class QtMetaTypePrivate::QSequentialIterableImpl + size=104 align=8 + base size=104 base align=8 +QtMetaTypePrivate::QSequentialIterableImpl (0x0x7fb60e146480) 0 + +Class QtMetaTypePrivate::QAssociativeIterableImpl + size=112 align=8 + base size=112 base align=8 +QtMetaTypePrivate::QAssociativeIterableImpl (0x0x7fb60e146660) 0 + +Class QtMetaTypePrivate::QPairVariantInterfaceImpl + size=40 align=8 + base size=40 base align=8 +QtMetaTypePrivate::QPairVariantInterfaceImpl (0x0x7fb60e146720) 0 + +Class QtPrivate::QSlotObjectBase + size=16 align=8 + base size=16 base align=8 +QtPrivate::QSlotObjectBase (0x0x7fb60df0ba80) 0 + +Vtable for QObjectData +QObjectData::_ZTV11QObjectData: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QObjectData) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))__cxa_pure_virtual + +Class QObjectData + size=48 align=8 + base size=48 base align=8 +QObjectData (0x0x7fb60df0bc00) 0 + vptr=((& QObjectData::_ZTV11QObjectData) + 16u) + +Class QObject::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QObject::QPrivateSignal (0x0x7fb60df0bde0) 0 empty + +Vtable for QObject +QObject::_ZTV7QObject: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QObject) +16 (int (*)(...))QObject::metaObject +24 (int (*)(...))QObject::qt_metacast +32 (int (*)(...))QObject::qt_metacall +40 (int (*)(...))QObject::~QObject +48 (int (*)(...))QObject::~QObject +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QObject + size=16 align=8 + base size=16 base align=8 +QObject (0x0x7fb60df0bd80) 0 + vptr=((& QObject::_ZTV7QObject) + 16u) + +Vtable for QObjectUserData +QObjectUserData::_ZTV15QObjectUserData: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QObjectUserData) +16 (int (*)(...))QObjectUserData::~QObjectUserData +24 (int (*)(...))QObjectUserData::~QObjectUserData + +Class QObjectUserData + size=8 align=8 + base size=8 base align=8 +QObjectUserData (0x0x7fb60dbea120) 0 nearly-empty + vptr=((& QObjectUserData::_ZTV15QObjectUserData) + 16u) + +Class QSignalBlocker + size=16 align=8 + base size=10 base align=8 +QSignalBlocker (0x0x7fb60dbea180) 0 + +Class QAbstractAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractAnimation::QPrivateSignal (0x0x7fb60dbea240) 0 empty + +Vtable for QAbstractAnimation +QAbstractAnimation::_ZTV18QAbstractAnimation: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractAnimation) +16 (int (*)(...))QAbstractAnimation::metaObject +24 (int (*)(...))QAbstractAnimation::qt_metacast +32 (int (*)(...))QAbstractAnimation::qt_metacall +40 (int (*)(...))QAbstractAnimation::~QAbstractAnimation +48 (int (*)(...))QAbstractAnimation::~QAbstractAnimation +56 (int (*)(...))QAbstractAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QAbstractAnimation + size=16 align=8 + base size=16 base align=8 +QAbstractAnimation (0x0x7fb60df5f5b0) 0 + vptr=((& QAbstractAnimation::_ZTV18QAbstractAnimation) + 16u) + QObject (0x0x7fb60dbea1e0) 0 + primary-for QAbstractAnimation (0x0x7fb60df5f5b0) + +Class QAnimationDriver::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAnimationDriver::QPrivateSignal (0x0x7fb60dbea300) 0 empty + +Vtable for QAnimationDriver +QAnimationDriver::_ZTV16QAnimationDriver: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QAnimationDriver) +16 (int (*)(...))QAnimationDriver::metaObject +24 (int (*)(...))QAnimationDriver::qt_metacast +32 (int (*)(...))QAnimationDriver::qt_metacall +40 (int (*)(...))QAnimationDriver::~QAnimationDriver +48 (int (*)(...))QAnimationDriver::~QAnimationDriver +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAnimationDriver::advance +120 (int (*)(...))QAnimationDriver::elapsed +128 (int (*)(...))QAnimationDriver::start +136 (int (*)(...))QAnimationDriver::stop + +Class QAnimationDriver + size=16 align=8 + base size=16 base align=8 +QAnimationDriver (0x0x7fb60df5f618) 0 + vptr=((& QAnimationDriver::_ZTV16QAnimationDriver) + 16u) + QObject (0x0x7fb60dbea2a0) 0 + primary-for QAnimationDriver (0x0x7fb60df5f618) + +Class QAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAnimationGroup::QPrivateSignal (0x0x7fb60dbea3c0) 0 empty + +Vtable for QAnimationGroup +QAnimationGroup::_ZTV15QAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QAnimationGroup) +16 (int (*)(...))QAnimationGroup::metaObject +24 (int (*)(...))QAnimationGroup::qt_metacast +32 (int (*)(...))QAnimationGroup::qt_metacall +40 (int (*)(...))QAnimationGroup::~QAnimationGroup +48 (int (*)(...))QAnimationGroup::~QAnimationGroup +56 (int (*)(...))QAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QAnimationGroup + size=16 align=8 + base size=16 base align=8 +QAnimationGroup (0x0x7fb60df5f680) 0 + vptr=((& QAnimationGroup::_ZTV15QAnimationGroup) + 16u) + QAbstractAnimation (0x0x7fb60df5f6e8) 0 + primary-for QAnimationGroup (0x0x7fb60df5f680) + QObject (0x0x7fb60dbea360) 0 + primary-for QAbstractAnimation (0x0x7fb60df5f6e8) + +Class QParallelAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QParallelAnimationGroup::QPrivateSignal (0x0x7fb60dbea480) 0 empty + +Vtable for QParallelAnimationGroup +QParallelAnimationGroup::_ZTV23QParallelAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QParallelAnimationGroup) +16 (int (*)(...))QParallelAnimationGroup::metaObject +24 (int (*)(...))QParallelAnimationGroup::qt_metacast +32 (int (*)(...))QParallelAnimationGroup::qt_metacall +40 (int (*)(...))QParallelAnimationGroup::~QParallelAnimationGroup +48 (int (*)(...))QParallelAnimationGroup::~QParallelAnimationGroup +56 (int (*)(...))QParallelAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QParallelAnimationGroup::duration +120 (int (*)(...))QParallelAnimationGroup::updateCurrentTime +128 (int (*)(...))QParallelAnimationGroup::updateState +136 (int (*)(...))QParallelAnimationGroup::updateDirection + +Class QParallelAnimationGroup + size=16 align=8 + base size=16 base align=8 +QParallelAnimationGroup (0x0x7fb60df5f750) 0 + vptr=((& QParallelAnimationGroup::_ZTV23QParallelAnimationGroup) + 16u) + QAnimationGroup (0x0x7fb60df5f7b8) 0 + primary-for QParallelAnimationGroup (0x0x7fb60df5f750) + QAbstractAnimation (0x0x7fb60df5f820) 0 + primary-for QAnimationGroup (0x0x7fb60df5f7b8) + QObject (0x0x7fb60dbea420) 0 + primary-for QAbstractAnimation (0x0x7fb60df5f820) + +Class QPauseAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPauseAnimation::QPrivateSignal (0x0x7fb60dbea540) 0 empty + +Vtable for QPauseAnimation +QPauseAnimation::_ZTV15QPauseAnimation: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QPauseAnimation) +16 (int (*)(...))QPauseAnimation::metaObject +24 (int (*)(...))QPauseAnimation::qt_metacast +32 (int (*)(...))QPauseAnimation::qt_metacall +40 (int (*)(...))QPauseAnimation::~QPauseAnimation +48 (int (*)(...))QPauseAnimation::~QPauseAnimation +56 (int (*)(...))QPauseAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QPauseAnimation::duration +120 (int (*)(...))QPauseAnimation::updateCurrentTime +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QPauseAnimation + size=16 align=8 + base size=16 base align=8 +QPauseAnimation (0x0x7fb60df5f888) 0 + vptr=((& QPauseAnimation::_ZTV15QPauseAnimation) + 16u) + QAbstractAnimation (0x0x7fb60df5f8f0) 0 + primary-for QPauseAnimation (0x0x7fb60df5f888) + QObject (0x0x7fb60dbea4e0) 0 + primary-for QAbstractAnimation (0x0x7fb60df5f8f0) + +Class QEasingCurve + size=8 align=8 + base size=8 base align=8 +QEasingCurve (0x0x7fb60dbea720) 0 + +Class QMapNodeBase + size=24 align=8 + base size=24 base align=8 +QMapNodeBase (0x0x7fb60dbea900) 0 + +Class QMapDataBase + size=40 align=8 + base size=40 base align=8 +QMapDataBase (0x0x7fb60dbea9c0) 0 + +Class QHashData::Node + size=16 align=8 + base size=16 base align=8 +QHashData::Node (0x0x7fb60dbead20) 0 + +Class QHashData + size=48 align=8 + base size=48 base align=8 +QHashData (0x0x7fb60dbeacc0) 0 + +Class QHashDummyValue + size=1 align=1 + base size=0 base align=1 +QHashDummyValue (0x0x7fb60dbead80) 0 empty + +Class QIODevice::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QIODevice::QPrivateSignal (0x0x7fb60da72300) 0 empty + +Vtable for QIODevice +QIODevice::_ZTV9QIODevice: 30u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QIODevice) +16 (int (*)(...))QIODevice::metaObject +24 (int (*)(...))QIODevice::qt_metacast +32 (int (*)(...))QIODevice::qt_metacall +40 (int (*)(...))QIODevice::~QIODevice +48 (int (*)(...))QIODevice::~QIODevice +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QIODevice::isSequential +120 (int (*)(...))QIODevice::open +128 (int (*)(...))QIODevice::close +136 (int (*)(...))QIODevice::pos +144 (int (*)(...))QIODevice::size +152 (int (*)(...))QIODevice::seek +160 (int (*)(...))QIODevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))__cxa_pure_virtual +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))__cxa_pure_virtual + +Class QIODevice + size=16 align=8 + base size=16 base align=8 +QIODevice (0x0x7fb60dacc138) 0 + vptr=((& QIODevice::_ZTV9QIODevice) + 16u) + QObject (0x0x7fb60da722a0) 0 + primary-for QIODevice (0x0x7fb60dacc138) + +Class QDataStream + size=32 align=8 + base size=32 base align=8 +QDataStream (0x0x7fb60da72420) 0 + +Class QRegExp + size=8 align=8 + base size=8 base align=8 +QRegExp (0x0x7fb60da724e0) 0 + +Class QStringMatcher::Data + size=272 align=8 + base size=272 base align=8 +QStringMatcher::Data (0x0x7fb60da72660) 0 + +Class QStringMatcher + size=1048 align=8 + base size=1048 base align=8 +QStringMatcher (0x0x7fb60da72600) 0 + +Class QStringList + size=8 align=8 + base size=8 base align=8 +QStringList (0x0x7fb60dacc340) 0 + QList (0x0x7fb60dacc3a8) 0 + QListSpecialMethods (0x0x7fb60da72840) 0 empty + +Class QVariant::PrivateShared + size=16 align=8 + base size=12 base align=8 +QVariant::PrivateShared (0x0x7fb60da72b40) 0 + +Class QVariant::Private::Data + size=8 align=8 + base size=8 base align=8 +QVariant::Private::Data (0x0x7fb60da72c00) 0 + +Class QVariant::Private + size=16 align=8 + base size=12 base align=8 +QVariant::Private (0x0x7fb60da72ba0) 0 + +Class QVariant::Handler + size=72 align=8 + base size=72 base align=8 +QVariant::Handler (0x0x7fb60da72c60) 0 + +Class QVariant + size=16 align=8 + base size=16 base align=8 +QVariant (0x0x7fb60da72ae0) 0 + +Class QVariantComparisonHelper + size=8 align=8 + base size=8 base align=8 +QVariantComparisonHelper (0x0x7fb60da72f60) 0 + +Class QSequentialIterable::const_iterator + size=112 align=8 + base size=112 base align=8 +QSequentialIterable::const_iterator (0x0x7fb60d8f6060) 0 + +Class QSequentialIterable + size=104 align=8 + base size=104 base align=8 +QSequentialIterable (0x0x7fb60d8f6000) 0 + +Class QAssociativeIterable::const_iterator + size=120 align=8 + base size=120 base align=8 +QAssociativeIterable::const_iterator (0x0x7fb60d8f6120) 0 + +Class QAssociativeIterable + size=112 align=8 + base size=112 base align=8 +QAssociativeIterable (0x0x7fb60d8f60c0) 0 + +Class QVariantAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QVariantAnimation::QPrivateSignal (0x0x7fb60d8f6cc0) 0 empty + +Vtable for QVariantAnimation +QVariantAnimation::_ZTV17QVariantAnimation: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QVariantAnimation) +16 (int (*)(...))QVariantAnimation::metaObject +24 (int (*)(...))QVariantAnimation::qt_metacast +32 (int (*)(...))QVariantAnimation::qt_metacall +40 (int (*)(...))QVariantAnimation::~QVariantAnimation +48 (int (*)(...))QVariantAnimation::~QVariantAnimation +56 (int (*)(...))QVariantAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QVariantAnimation::duration +120 (int (*)(...))QVariantAnimation::updateCurrentTime +128 (int (*)(...))QVariantAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection +144 (int (*)(...))QVariantAnimation::updateCurrentValue +152 (int (*)(...))QVariantAnimation::interpolated + +Class QVariantAnimation + size=16 align=8 + base size=16 base align=8 +QVariantAnimation (0x0x7fb60dacce38) 0 + vptr=((& QVariantAnimation::_ZTV17QVariantAnimation) + 16u) + QAbstractAnimation (0x0x7fb60daccea0) 0 + primary-for QVariantAnimation (0x0x7fb60dacce38) + QObject (0x0x7fb60d8f6c60) 0 + primary-for QAbstractAnimation (0x0x7fb60daccea0) + +Class QPropertyAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPropertyAnimation::QPrivateSignal (0x0x7fb60d8f6d80) 0 empty + +Vtable for QPropertyAnimation +QPropertyAnimation::_ZTV18QPropertyAnimation: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QPropertyAnimation) +16 (int (*)(...))QPropertyAnimation::metaObject +24 (int (*)(...))QPropertyAnimation::qt_metacast +32 (int (*)(...))QPropertyAnimation::qt_metacall +40 (int (*)(...))QPropertyAnimation::~QPropertyAnimation +48 (int (*)(...))QPropertyAnimation::~QPropertyAnimation +56 (int (*)(...))QPropertyAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QVariantAnimation::duration +120 (int (*)(...))QVariantAnimation::updateCurrentTime +128 (int (*)(...))QPropertyAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection +144 (int (*)(...))QPropertyAnimation::updateCurrentValue +152 (int (*)(...))QVariantAnimation::interpolated + +Class QPropertyAnimation + size=16 align=8 + base size=16 base align=8 +QPropertyAnimation (0x0x7fb60daccf70) 0 + vptr=((& QPropertyAnimation::_ZTV18QPropertyAnimation) + 16u) + QVariantAnimation (0x0x7fb60dacc068) 0 + primary-for QPropertyAnimation (0x0x7fb60daccf70) + QAbstractAnimation (0x0x7fb60daccc98) 0 + primary-for QVariantAnimation (0x0x7fb60dacc068) + QObject (0x0x7fb60d8f6d20) 0 + primary-for QAbstractAnimation (0x0x7fb60daccc98) + +Class QSequentialAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSequentialAnimationGroup::QPrivateSignal (0x0x7fb60d8f6e40) 0 empty + +Vtable for QSequentialAnimationGroup +QSequentialAnimationGroup::_ZTV25QSequentialAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI25QSequentialAnimationGroup) +16 (int (*)(...))QSequentialAnimationGroup::metaObject +24 (int (*)(...))QSequentialAnimationGroup::qt_metacast +32 (int (*)(...))QSequentialAnimationGroup::qt_metacall +40 (int (*)(...))QSequentialAnimationGroup::~QSequentialAnimationGroup +48 (int (*)(...))QSequentialAnimationGroup::~QSequentialAnimationGroup +56 (int (*)(...))QSequentialAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSequentialAnimationGroup::duration +120 (int (*)(...))QSequentialAnimationGroup::updateCurrentTime +128 (int (*)(...))QSequentialAnimationGroup::updateState +136 (int (*)(...))QSequentialAnimationGroup::updateDirection + +Class QSequentialAnimationGroup + size=16 align=8 + base size=16 base align=8 +QSequentialAnimationGroup (0x0x7fb60daccd00) 0 + vptr=((& QSequentialAnimationGroup::_ZTV25QSequentialAnimationGroup) + 16u) + QAnimationGroup (0x0x7fb60daccd68) 0 + primary-for QSequentialAnimationGroup (0x0x7fb60daccd00) + QAbstractAnimation (0x0x7fb60d5a6000) 0 + primary-for QAnimationGroup (0x0x7fb60daccd68) + QObject (0x0x7fb60d8f6de0) 0 + primary-for QAbstractAnimation (0x0x7fb60d5a6000) + +Class QTextCodec::ConverterState + size=32 align=8 + base size=32 base align=8 +QTextCodec::ConverterState (0x0x7fb60d8f6f00) 0 + +Vtable for QTextCodec +QTextCodec::_ZTV10QTextCodec: 9u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QTextCodec) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))QTextCodec::aliases +32 (int (*)(...))__cxa_pure_virtual +40 (int (*)(...))__cxa_pure_virtual +48 (int (*)(...))__cxa_pure_virtual +56 (int (*)(...))QTextCodec::~QTextCodec +64 (int (*)(...))QTextCodec::~QTextCodec + +Class QTextCodec + size=8 align=8 + base size=8 base align=8 +QTextCodec (0x0x7fb60d8f6ea0) 0 nearly-empty + vptr=((& QTextCodec::_ZTV10QTextCodec) + 16u) + +Class QTextEncoder + size=40 align=8 + base size=40 base align=8 +QTextEncoder (0x0x7fb60d5c2060) 0 + +Class QTextDecoder + size=40 align=8 + base size=40 base align=8 +QTextDecoder (0x0x7fb60d5c20c0) 0 + +Class QSharedData + size=4 align=4 + base size=4 base align=4 +QSharedData (0x0x7fb60d5c2120) 0 + +Class std::__numeric_limits_base + size=1 align=1 + base size=0 base align=1 +std::__numeric_limits_base (0x0x7fb60d5c2300) 0 empty + +Class QDate + size=8 align=8 + base size=8 base align=8 +QDate (0x0x7fb60d5c2a80) 0 + +Class QTime + size=4 align=4 + base size=4 base align=4 +QTime (0x0x7fb60d5c2ba0) 0 + +Class QDateTime + size=8 align=8 + base size=8 base align=8 +QDateTime (0x0x7fb60d5c2cc0) 0 + +Class QLibraryInfo + size=1 align=1 + base size=0 base align=1 +QLibraryInfo (0x0x7fb60d5c2e40) 0 empty + +Class QBuffer::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QBuffer::QPrivateSignal (0x0x7fb60d5c2f00) 0 empty + +Vtable for QBuffer +QBuffer::_ZTV7QBuffer: 30u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QBuffer) +16 (int (*)(...))QBuffer::metaObject +24 (int (*)(...))QBuffer::qt_metacast +32 (int (*)(...))QBuffer::qt_metacall +40 (int (*)(...))QBuffer::~QBuffer +48 (int (*)(...))QBuffer::~QBuffer +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QBuffer::connectNotify +104 (int (*)(...))QBuffer::disconnectNotify +112 (int (*)(...))QIODevice::isSequential +120 (int (*)(...))QBuffer::open +128 (int (*)(...))QBuffer::close +136 (int (*)(...))QBuffer::pos +144 (int (*)(...))QBuffer::size +152 (int (*)(...))QBuffer::seek +160 (int (*)(...))QBuffer::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QBuffer::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QBuffer::readData +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))QBuffer::writeData + +Class QBuffer + size=16 align=8 + base size=16 base align=8 +QBuffer (0x0x7fb60d5a62d8) 0 + vptr=((& QBuffer::_ZTV7QBuffer) + 16u) + QIODevice (0x0x7fb60d5a6340) 0 + primary-for QBuffer (0x0x7fb60d5a62d8) + QObject (0x0x7fb60d5c2ea0) 0 + primary-for QIODevice (0x0x7fb60d5a6340) + +Class QLocale + size=8 align=8 + base size=8 base align=8 +QLocale (0x0x7fb60d5c2f60) 0 + +Class _IO_marker + size=24 align=8 + base size=24 base align=8 +_IO_marker (0x0x7fb60d3c92a0) 0 + +Class _IO_FILE + size=216 align=8 + base size=216 base align=8 +_IO_FILE (0x0x7fb60d3c9300) 0 + +Vtable for QTextStream +QTextStream::_ZTV11QTextStream: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTextStream) +16 (int (*)(...))QTextStream::~QTextStream +24 (int (*)(...))QTextStream::~QTextStream + +Class QTextStream + size=16 align=8 + base size=16 base align=8 +QTextStream (0x0x7fb60d3c93c0) 0 + vptr=((& QTextStream::_ZTV11QTextStream) + 16u) + +Class QTextStreamManipulator + size=40 align=8 + base size=38 base align=8 +QTextStreamManipulator (0x0x7fb60d3c9660) 0 + +Class QContiguousCacheData + size=24 align=4 + base size=24 base align=4 +QContiguousCacheData (0x0x7fb60d3c98a0) 0 + +Class QDebug::Stream + size=80 align=8 + base size=76 base align=8 +QDebug::Stream (0x0x7fb60d3c9f00) 0 + +Class QDebug + size=8 align=8 + base size=8 base align=8 +QDebug (0x0x7fb60d3c9ea0) 0 + +Class QDebugStateSaver + size=8 align=8 + base size=8 base align=8 +QDebugStateSaver (0x0x7fb60d18a060) 0 + +Class QNoDebug + size=1 align=1 + base size=0 base align=1 +QNoDebug (0x0x7fb60d18a120) 0 empty + +Class QFileDevice::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileDevice::QPrivateSignal (0x0x7fb60d18a1e0) 0 empty + +Vtable for QFileDevice +QFileDevice::_ZTV11QFileDevice: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QFileDevice) +16 (int (*)(...))QFileDevice::metaObject +24 (int (*)(...))QFileDevice::qt_metacast +32 (int (*)(...))QFileDevice::qt_metacall +40 (int (*)(...))QFileDevice::~QFileDevice +48 (int (*)(...))QFileDevice::~QFileDevice +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QIODevice::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFileDevice::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QFileDevice::fileName +248 (int (*)(...))QFileDevice::resize +256 (int (*)(...))QFileDevice::permissions +264 (int (*)(...))QFileDevice::setPermissions + +Class QFileDevice + size=16 align=8 + base size=16 base align=8 +QFileDevice (0x0x7fb60d5a67b8) 0 + vptr=((& QFileDevice::_ZTV11QFileDevice) + 16u) + QIODevice (0x0x7fb60d5a6820) 0 + primary-for QFileDevice (0x0x7fb60d5a67b8) + QObject (0x0x7fb60d18a180) 0 + primary-for QIODevice (0x0x7fb60d5a6820) + +Class QFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFile::QPrivateSignal (0x0x7fb60d18a360) 0 empty + +Vtable for QFile +QFile::_ZTV5QFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI5QFile) +16 (int (*)(...))QFile::metaObject +24 (int (*)(...))QFile::qt_metacast +32 (int (*)(...))QFile::qt_metacall +40 (int (*)(...))QFile::~QFile +48 (int (*)(...))QFile::~QFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QFile::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFile::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QFile::fileName +248 (int (*)(...))QFile::resize +256 (int (*)(...))QFile::permissions +264 (int (*)(...))QFile::setPermissions + +Class QFile + size=16 align=8 + base size=16 base align=8 +QFile (0x0x7fb60d5a6958) 0 + vptr=((& QFile::_ZTV5QFile) + 16u) + QFileDevice (0x0x7fb60d5a69c0) 0 + primary-for QFile (0x0x7fb60d5a6958) + QIODevice (0x0x7fb60d5a6a28) 0 + primary-for QFileDevice (0x0x7fb60d5a69c0) + QObject (0x0x7fb60d18a300) 0 + primary-for QIODevice (0x0x7fb60d5a6a28) + +Class QFileInfo + size=8 align=8 + base size=8 base align=8 +QFileInfo (0x0x7fb60d18a480) 0 + +Class QDir + size=8 align=8 + base size=8 base align=8 +QDir (0x0x7fb60d18a720) 0 + +Class QDirIterator + size=8 align=8 + base size=8 base align=8 +QDirIterator (0x0x7fb60d18aa20) 0 + +Class QFileSelector::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileSelector::QPrivateSignal (0x0x7fb60d18ac00) 0 empty + +Vtable for QFileSelector +QFileSelector::_ZTV13QFileSelector: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QFileSelector) +16 (int (*)(...))QFileSelector::metaObject +24 (int (*)(...))QFileSelector::qt_metacast +32 (int (*)(...))QFileSelector::qt_metacall +40 (int (*)(...))QFileSelector::~QFileSelector +48 (int (*)(...))QFileSelector::~QFileSelector +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QFileSelector + size=16 align=8 + base size=16 base align=8 +QFileSelector (0x0x7fb60d5a6f08) 0 + vptr=((& QFileSelector::_ZTV13QFileSelector) + 16u) + QObject (0x0x7fb60d18aba0) 0 + primary-for QFileSelector (0x0x7fb60d5a6f08) + +Class QFileSystemWatcher::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileSystemWatcher::QPrivateSignal (0x0x7fb60d18acc0) 0 empty + +Vtable for QFileSystemWatcher +QFileSystemWatcher::_ZTV18QFileSystemWatcher: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QFileSystemWatcher) +16 (int (*)(...))QFileSystemWatcher::metaObject +24 (int (*)(...))QFileSystemWatcher::qt_metacast +32 (int (*)(...))QFileSystemWatcher::qt_metacall +40 (int (*)(...))QFileSystemWatcher::~QFileSystemWatcher +48 (int (*)(...))QFileSystemWatcher::~QFileSystemWatcher +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QFileSystemWatcher + size=16 align=8 + base size=16 base align=8 +QFileSystemWatcher (0x0x7fb60d5a6f70) 0 + vptr=((& QFileSystemWatcher::_ZTV18QFileSystemWatcher) + 16u) + QObject (0x0x7fb60d18ac60) 0 + primary-for QFileSystemWatcher (0x0x7fb60d5a6f70) + +Class QLockFile + size=8 align=8 + base size=8 base align=8 +QLockFile (0x0x7fb60d18ad20) 0 + +Class QLoggingCategory::AtomicBools + size=3 align=1 + base size=3 base align=1 +QLoggingCategory::AtomicBools (0x0x7fb60d18aea0) 0 + +Class QLoggingCategory + size=24 align=8 + base size=24 base align=8 +QLoggingCategory (0x0x7fb60d18ae40) 0 + +Class QProcessEnvironment + size=8 align=8 + base size=8 base align=8 +QProcessEnvironment (0x0x7fb60d318060) 0 + +Class QProcess::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QProcess::QPrivateSignal (0x0x7fb60d318240) 0 empty + +Vtable for QProcess +QProcess::_ZTV8QProcess: 31u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI8QProcess) +16 (int (*)(...))QProcess::metaObject +24 (int (*)(...))QProcess::qt_metacast +32 (int (*)(...))QProcess::qt_metacall +40 (int (*)(...))QProcess::~QProcess +48 (int (*)(...))QProcess::~QProcess +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QProcess::isSequential +120 (int (*)(...))QProcess::open +128 (int (*)(...))QProcess::close +136 (int (*)(...))QIODevice::pos +144 (int (*)(...))QIODevice::size +152 (int (*)(...))QIODevice::seek +160 (int (*)(...))QProcess::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QProcess::bytesAvailable +184 (int (*)(...))QProcess::bytesToWrite +192 (int (*)(...))QProcess::canReadLine +200 (int (*)(...))QProcess::waitForReadyRead +208 (int (*)(...))QProcess::waitForBytesWritten +216 (int (*)(...))QProcess::readData +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))QProcess::writeData +240 (int (*)(...))QProcess::setupChildProcess + +Class QProcess + size=16 align=8 + base size=16 base align=8 +QProcess (0x0x7fb60d306138) 0 + vptr=((& QProcess::_ZTV8QProcess) + 16u) + QIODevice (0x0x7fb60d3061a0) 0 + primary-for QProcess (0x0x7fb60d306138) + QObject (0x0x7fb60d3181e0) 0 + primary-for QIODevice (0x0x7fb60d3061a0) + +Class QResource + size=8 align=8 + base size=8 base align=8 +QResource (0x0x7fb60d3182a0) 0 + +Class QSaveFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSaveFile::QPrivateSignal (0x0x7fb60d318420) 0 empty + +Vtable for QSaveFile +QSaveFile::_ZTV9QSaveFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QSaveFile) +16 (int (*)(...))QSaveFile::metaObject +24 (int (*)(...))QSaveFile::qt_metacast +32 (int (*)(...))QSaveFile::qt_metacall +40 (int (*)(...))QSaveFile::~QSaveFile +48 (int (*)(...))QSaveFile::~QSaveFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QSaveFile::open +128 (int (*)(...))QSaveFile::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFileDevice::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QSaveFile::writeData +240 (int (*)(...))QSaveFile::fileName +248 (int (*)(...))QFileDevice::resize +256 (int (*)(...))QFileDevice::permissions +264 (int (*)(...))QFileDevice::setPermissions + +Class QSaveFile + size=16 align=8 + base size=16 base align=8 +QSaveFile (0x0x7fb60d306208) 0 + vptr=((& QSaveFile::_ZTV9QSaveFile) + 16u) + QFileDevice (0x0x7fb60d306270) 0 + primary-for QSaveFile (0x0x7fb60d306208) + QIODevice (0x0x7fb60d3062d8) 0 + primary-for QFileDevice (0x0x7fb60d306270) + QObject (0x0x7fb60d3183c0) 0 + primary-for QIODevice (0x0x7fb60d3062d8) + +Class QSettings::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSettings::QPrivateSignal (0x0x7fb60d3184e0) 0 empty + +Vtable for QSettings +QSettings::_ZTV9QSettings: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QSettings) +16 (int (*)(...))QSettings::metaObject +24 (int (*)(...))QSettings::qt_metacast +32 (int (*)(...))QSettings::qt_metacall +40 (int (*)(...))QSettings::~QSettings +48 (int (*)(...))QSettings::~QSettings +56 (int (*)(...))QSettings::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSettings + size=16 align=8 + base size=16 base align=8 +QSettings (0x0x7fb60d306340) 0 + vptr=((& QSettings::_ZTV9QSettings) + 16u) + QObject (0x0x7fb60d318480) 0 + primary-for QSettings (0x0x7fb60d306340) + +Class QStandardPaths + size=1 align=1 + base size=0 base align=1 +QStandardPaths (0x0x7fb60d318540) 0 empty + +Class QStorageInfo + size=8 align=8 + base size=8 base align=8 +QStorageInfo (0x0x7fb60d318660) 0 + +Class QTemporaryDir + size=8 align=8 + base size=8 base align=8 +QTemporaryDir (0x0x7fb60d318900) 0 + +Class QTemporaryFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTemporaryFile::QPrivateSignal (0x0x7fb60d318a20) 0 empty + +Vtable for QTemporaryFile +QTemporaryFile::_ZTV14QTemporaryFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI14QTemporaryFile) +16 (int (*)(...))QTemporaryFile::metaObject +24 (int (*)(...))QTemporaryFile::qt_metacast +32 (int (*)(...))QTemporaryFile::qt_metacall +40 (int (*)(...))QTemporaryFile::~QTemporaryFile +48 (int (*)(...))QTemporaryFile::~QTemporaryFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QTemporaryFile::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFile::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QTemporaryFile::fileName +248 (int (*)(...))QFile::resize +256 (int (*)(...))QFile::permissions +264 (int (*)(...))QFile::setPermissions + +Class QTemporaryFile + size=16 align=8 + base size=16 base align=8 +QTemporaryFile (0x0x7fb60d3064e0) 0 + vptr=((& QTemporaryFile::_ZTV14QTemporaryFile) + 16u) + QFile (0x0x7fb60d306548) 0 + primary-for QTemporaryFile (0x0x7fb60d3064e0) + QFileDevice (0x0x7fb60d3065b0) 0 + primary-for QFile (0x0x7fb60d306548) + QIODevice (0x0x7fb60d306618) 0 + primary-for QFileDevice (0x0x7fb60d3065b0) + QObject (0x0x7fb60d3189c0) 0 + primary-for QIODevice (0x0x7fb60d306618) + +Class QUrl + size=8 align=8 + base size=8 base align=8 +QUrl (0x0x7fb60d318b40) 0 + +Class QUrlQuery + size=8 align=8 + base size=8 base align=8 +QUrlQuery (0x0x7fb60d0bd060) 0 + +Class QModelIndex + size=24 align=8 + base size=24 base align=8 +QModelIndex (0x0x7fb60d0bd1e0) 0 + +Class QPersistentModelIndex + size=8 align=8 + base size=8 base align=8 +QPersistentModelIndex (0x0x7fb60d0bd300) 0 + +Class QAbstractItemModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractItemModel::QPrivateSignal (0x0x7fb60d0bd480) 0 empty + +Vtable for QAbstractItemModel +QAbstractItemModel::_ZTV18QAbstractItemModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractItemModel) +16 (int (*)(...))QAbstractItemModel::metaObject +24 (int (*)(...))QAbstractItemModel::qt_metacast +32 (int (*)(...))QAbstractItemModel::qt_metacall +40 (int (*)(...))QAbstractItemModel::~QAbstractItemModel +48 (int (*)(...))QAbstractItemModel::~QAbstractItemModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractItemModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractItemModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractItemModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractItemModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractItemModel + size=16 align=8 + base size=16 base align=8 +QAbstractItemModel (0x0x7fb60d306af8) 0 + vptr=((& QAbstractItemModel::_ZTV18QAbstractItemModel) + 16u) + QObject (0x0x7fb60d0bd420) 0 + primary-for QAbstractItemModel (0x0x7fb60d306af8) + +Class QAbstractTableModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractTableModel::QPrivateSignal (0x0x7fb60d0bd7e0) 0 empty + +Vtable for QAbstractTableModel +QAbstractTableModel::_ZTV19QAbstractTableModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractTableModel) +16 (int (*)(...))QAbstractTableModel::metaObject +24 (int (*)(...))QAbstractTableModel::qt_metacast +32 (int (*)(...))QAbstractTableModel::qt_metacall +40 (int (*)(...))QAbstractTableModel::~QAbstractTableModel +48 (int (*)(...))QAbstractTableModel::~QAbstractTableModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractTableModel::index +120 (int (*)(...))QAbstractTableModel::parent +128 (int (*)(...))QAbstractItemModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractTableModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractTableModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractTableModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractTableModel + size=16 align=8 + base size=16 base align=8 +QAbstractTableModel (0x0x7fb60d306c98) 0 + vptr=((& QAbstractTableModel::_ZTV19QAbstractTableModel) + 16u) + QAbstractItemModel (0x0x7fb60d306d00) 0 + primary-for QAbstractTableModel (0x0x7fb60d306c98) + QObject (0x0x7fb60d0bd780) 0 + primary-for QAbstractItemModel (0x0x7fb60d306d00) + +Class QAbstractListModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractListModel::QPrivateSignal (0x0x7fb60d0bd8a0) 0 empty + +Vtable for QAbstractListModel +QAbstractListModel::_ZTV18QAbstractListModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractListModel) +16 (int (*)(...))QAbstractListModel::metaObject +24 (int (*)(...))QAbstractListModel::qt_metacast +32 (int (*)(...))QAbstractListModel::qt_metacall +40 (int (*)(...))QAbstractListModel::~QAbstractListModel +48 (int (*)(...))QAbstractListModel::~QAbstractListModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractListModel::index +120 (int (*)(...))QAbstractListModel::parent +128 (int (*)(...))QAbstractItemModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))QAbstractListModel::columnCount +152 (int (*)(...))QAbstractListModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractListModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractListModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractListModel + size=16 align=8 + base size=16 base align=8 +QAbstractListModel (0x0x7fb60d306d68) 0 + vptr=((& QAbstractListModel::_ZTV18QAbstractListModel) + 16u) + QAbstractItemModel (0x0x7fb60d306dd0) 0 + primary-for QAbstractListModel (0x0x7fb60d306d68) + QObject (0x0x7fb60d0bd840) 0 + primary-for QAbstractItemModel (0x0x7fb60d306dd0) + +Class QAbstractProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractProxyModel::QPrivateSignal (0x0x7fb60d0bd960) 0 empty + +Vtable for QAbstractProxyModel +QAbstractProxyModel::_ZTV19QAbstractProxyModel: 53u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractProxyModel) +16 (int (*)(...))QAbstractProxyModel::metaObject +24 (int (*)(...))QAbstractProxyModel::qt_metacast +32 (int (*)(...))QAbstractProxyModel::qt_metacall +40 (int (*)(...))QAbstractProxyModel::~QAbstractProxyModel +48 (int (*)(...))QAbstractProxyModel::~QAbstractProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractProxyModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractProxyModel::hasChildren +160 (int (*)(...))QAbstractProxyModel::data +168 (int (*)(...))QAbstractProxyModel::setData +176 (int (*)(...))QAbstractProxyModel::headerData +184 (int (*)(...))QAbstractProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QAbstractProxyModel::mimeTypes +216 (int (*)(...))QAbstractProxyModel::mimeData +224 (int (*)(...))QAbstractProxyModel::canDropMimeData +232 (int (*)(...))QAbstractProxyModel::dropMimeData +240 (int (*)(...))QAbstractProxyModel::supportedDropActions +248 (int (*)(...))QAbstractProxyModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractProxyModel::fetchMore +312 (int (*)(...))QAbstractProxyModel::canFetchMore +320 (int (*)(...))QAbstractProxyModel::flags +328 (int (*)(...))QAbstractProxyModel::sort +336 (int (*)(...))QAbstractProxyModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QAbstractProxyModel::setSourceModel +392 (int (*)(...))__cxa_pure_virtual +400 (int (*)(...))__cxa_pure_virtual +408 (int (*)(...))QAbstractProxyModel::mapSelectionToSource +416 (int (*)(...))QAbstractProxyModel::mapSelectionFromSource + +Class QAbstractProxyModel + size=16 align=8 + base size=16 base align=8 +QAbstractProxyModel (0x0x7fb60d306e38) 0 + vptr=((& QAbstractProxyModel::_ZTV19QAbstractProxyModel) + 16u) + QAbstractItemModel (0x0x7fb60d306ea0) 0 + primary-for QAbstractProxyModel (0x0x7fb60d306e38) + QObject (0x0x7fb60d0bd900) 0 + primary-for QAbstractItemModel (0x0x7fb60d306ea0) + +Class QIdentityProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QIdentityProxyModel::QPrivateSignal (0x0x7fb60d0bda20) 0 empty + +Vtable for QIdentityProxyModel +QIdentityProxyModel::_ZTV19QIdentityProxyModel: 53u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QIdentityProxyModel) +16 (int (*)(...))QIdentityProxyModel::metaObject +24 (int (*)(...))QIdentityProxyModel::qt_metacast +32 (int (*)(...))QIdentityProxyModel::qt_metacall +40 (int (*)(...))QIdentityProxyModel::~QIdentityProxyModel +48 (int (*)(...))QIdentityProxyModel::~QIdentityProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QIdentityProxyModel::index +120 (int (*)(...))QIdentityProxyModel::parent +128 (int (*)(...))QIdentityProxyModel::sibling +136 (int (*)(...))QIdentityProxyModel::rowCount +144 (int (*)(...))QIdentityProxyModel::columnCount +152 (int (*)(...))QAbstractProxyModel::hasChildren +160 (int (*)(...))QAbstractProxyModel::data +168 (int (*)(...))QAbstractProxyModel::setData +176 (int (*)(...))QIdentityProxyModel::headerData +184 (int (*)(...))QAbstractProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QAbstractProxyModel::mimeTypes +216 (int (*)(...))QAbstractProxyModel::mimeData +224 (int (*)(...))QAbstractProxyModel::canDropMimeData +232 (int (*)(...))QIdentityProxyModel::dropMimeData +240 (int (*)(...))QAbstractProxyModel::supportedDropActions +248 (int (*)(...))QAbstractProxyModel::supportedDragActions +256 (int (*)(...))QIdentityProxyModel::insertRows +264 (int (*)(...))QIdentityProxyModel::insertColumns +272 (int (*)(...))QIdentityProxyModel::removeRows +280 (int (*)(...))QIdentityProxyModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractProxyModel::fetchMore +312 (int (*)(...))QAbstractProxyModel::canFetchMore +320 (int (*)(...))QAbstractProxyModel::flags +328 (int (*)(...))QAbstractProxyModel::sort +336 (int (*)(...))QAbstractProxyModel::buddy +344 (int (*)(...))QIdentityProxyModel::match +352 (int (*)(...))QAbstractProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QIdentityProxyModel::setSourceModel +392 (int (*)(...))QIdentityProxyModel::mapToSource +400 (int (*)(...))QIdentityProxyModel::mapFromSource +408 (int (*)(...))QIdentityProxyModel::mapSelectionToSource +416 (int (*)(...))QIdentityProxyModel::mapSelectionFromSource + +Class QIdentityProxyModel + size=16 align=8 + base size=16 base align=8 +QIdentityProxyModel (0x0x7fb60d306f08) 0 + vptr=((& QIdentityProxyModel::_ZTV19QIdentityProxyModel) + 16u) + QAbstractProxyModel (0x0x7fb60d306f70) 0 + primary-for QIdentityProxyModel (0x0x7fb60d306f08) + QAbstractItemModel (0x0x7fb60cdee000) 0 + primary-for QAbstractProxyModel (0x0x7fb60d306f70) + QObject (0x0x7fb60d0bd9c0) 0 + primary-for QAbstractItemModel (0x0x7fb60cdee000) + +Class QItemSelectionRange + size=16 align=8 + base size=16 base align=8 +QItemSelectionRange (0x0x7fb60d0bda80) 0 + +Class QItemSelectionModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QItemSelectionModel::QPrivateSignal (0x0x7fb60d0bdc00) 0 empty + +Vtable for QItemSelectionModel +QItemSelectionModel::_ZTV19QItemSelectionModel: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QItemSelectionModel) +16 (int (*)(...))QItemSelectionModel::metaObject +24 (int (*)(...))QItemSelectionModel::qt_metacast +32 (int (*)(...))QItemSelectionModel::qt_metacall +40 (int (*)(...))QItemSelectionModel::~QItemSelectionModel +48 (int (*)(...))QItemSelectionModel::~QItemSelectionModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QItemSelectionModel::setCurrentIndex +120 (int (*)(...))QItemSelectionModel::select +128 (int (*)(...))QItemSelectionModel::select +136 (int (*)(...))QItemSelectionModel::clear +144 (int (*)(...))QItemSelectionModel::reset +152 (int (*)(...))QItemSelectionModel::clearCurrentIndex + +Class QItemSelectionModel + size=16 align=8 + base size=16 base align=8 +QItemSelectionModel (0x0x7fb60cdee0d0) 0 + vptr=((& QItemSelectionModel::_ZTV19QItemSelectionModel) + 16u) + QObject (0x0x7fb60d0bdba0) 0 + primary-for QItemSelectionModel (0x0x7fb60cdee0d0) + +Class QItemSelection + size=8 align=8 + base size=8 base align=8 +QItemSelection (0x0x7fb60cdee270) 0 + QList (0x0x7fb60cdee2d8) 0 + QListSpecialMethods (0x0x7fb60d0bde40) 0 empty + +Class QSortFilterProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSortFilterProxyModel::QPrivateSignal (0x0x7fb60d0bdf00) 0 empty + +Vtable for QSortFilterProxyModel +QSortFilterProxyModel::_ZTV21QSortFilterProxyModel: 56u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QSortFilterProxyModel) +16 (int (*)(...))QSortFilterProxyModel::metaObject +24 (int (*)(...))QSortFilterProxyModel::qt_metacast +32 (int (*)(...))QSortFilterProxyModel::qt_metacall +40 (int (*)(...))QSortFilterProxyModel::~QSortFilterProxyModel +48 (int (*)(...))QSortFilterProxyModel::~QSortFilterProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSortFilterProxyModel::index +120 (int (*)(...))QSortFilterProxyModel::parent +128 (int (*)(...))QSortFilterProxyModel::sibling +136 (int (*)(...))QSortFilterProxyModel::rowCount +144 (int (*)(...))QSortFilterProxyModel::columnCount +152 (int (*)(...))QSortFilterProxyModel::hasChildren +160 (int (*)(...))QSortFilterProxyModel::data +168 (int (*)(...))QSortFilterProxyModel::setData +176 (int (*)(...))QSortFilterProxyModel::headerData +184 (int (*)(...))QSortFilterProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QSortFilterProxyModel::mimeTypes +216 (int (*)(...))QSortFilterProxyModel::mimeData +224 (int (*)(...))QAbstractProxyModel::canDropMimeData +232 (int (*)(...))QSortFilterProxyModel::dropMimeData +240 (int (*)(...))QSortFilterProxyModel::supportedDropActions +248 (int (*)(...))QAbstractProxyModel::supportedDragActions +256 (int (*)(...))QSortFilterProxyModel::insertRows +264 (int (*)(...))QSortFilterProxyModel::insertColumns +272 (int (*)(...))QSortFilterProxyModel::removeRows +280 (int (*)(...))QSortFilterProxyModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QSortFilterProxyModel::fetchMore +312 (int (*)(...))QSortFilterProxyModel::canFetchMore +320 (int (*)(...))QSortFilterProxyModel::flags +328 (int (*)(...))QSortFilterProxyModel::sort +336 (int (*)(...))QSortFilterProxyModel::buddy +344 (int (*)(...))QSortFilterProxyModel::match +352 (int (*)(...))QSortFilterProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QSortFilterProxyModel::setSourceModel +392 (int (*)(...))QSortFilterProxyModel::mapToSource +400 (int (*)(...))QSortFilterProxyModel::mapFromSource +408 (int (*)(...))QSortFilterProxyModel::mapSelectionToSource +416 (int (*)(...))QSortFilterProxyModel::mapSelectionFromSource +424 (int (*)(...))QSortFilterProxyModel::filterAcceptsRow +432 (int (*)(...))QSortFilterProxyModel::filterAcceptsColumn +440 (int (*)(...))QSortFilterProxyModel::lessThan + +Class QSortFilterProxyModel + size=16 align=8 + base size=16 base align=8 +QSortFilterProxyModel (0x0x7fb60cdee340) 0 + vptr=((& QSortFilterProxyModel::_ZTV21QSortFilterProxyModel) + 16u) + QAbstractProxyModel (0x0x7fb60cdee3a8) 0 + primary-for QSortFilterProxyModel (0x0x7fb60cdee340) + QAbstractItemModel (0x0x7fb60cdee410) 0 + primary-for QAbstractProxyModel (0x0x7fb60cdee3a8) + QObject (0x0x7fb60d0bdea0) 0 + primary-for QAbstractItemModel (0x0x7fb60cdee410) + +Class QStringListModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QStringListModel::QPrivateSignal (0x0x7fb60ceb8000) 0 empty + +Vtable for QStringListModel +QStringListModel::_ZTV16QStringListModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QStringListModel) +16 (int (*)(...))QStringListModel::metaObject +24 (int (*)(...))QStringListModel::qt_metacast +32 (int (*)(...))QStringListModel::qt_metacall +40 (int (*)(...))QStringListModel::~QStringListModel +48 (int (*)(...))QStringListModel::~QStringListModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractListModel::index +120 (int (*)(...))QAbstractListModel::parent +128 (int (*)(...))QStringListModel::sibling +136 (int (*)(...))QStringListModel::rowCount +144 (int (*)(...))QAbstractListModel::columnCount +152 (int (*)(...))QAbstractListModel::hasChildren +160 (int (*)(...))QStringListModel::data +168 (int (*)(...))QStringListModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractListModel::dropMimeData +240 (int (*)(...))QStringListModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QStringListModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QStringListModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QStringListModel::flags +328 (int (*)(...))QStringListModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QStringListModel + size=24 align=8 + base size=24 base align=8 +QStringListModel (0x0x7fb60cdee478) 0 + vptr=((& QStringListModel::_ZTV16QStringListModel) + 16u) + QAbstractListModel (0x0x7fb60cdee4e0) 0 + primary-for QStringListModel (0x0x7fb60cdee478) + QAbstractItemModel (0x0x7fb60cdee548) 0 + primary-for QAbstractListModel (0x0x7fb60cdee4e0) + QObject (0x0x7fb60d0bdf60) 0 + primary-for QAbstractItemModel (0x0x7fb60cdee548) + +Class QJsonValue + size=24 align=8 + base size=20 base align=8 +QJsonValue (0x0x7fb60ceb8060) 0 + +Class QJsonValueRef + size=16 align=8 + base size=12 base align=8 +QJsonValueRef (0x0x7fb60ceb8120) 0 + +Class QJsonValuePtr + size=24 align=8 + base size=24 base align=8 +QJsonValuePtr (0x0x7fb60ceb81e0) 0 + +Class QJsonValueRefPtr + size=16 align=8 + base size=16 base align=8 +QJsonValueRefPtr (0x0x7fb60ceb8240) 0 + +Class QJsonArray::iterator + size=16 align=8 + base size=12 base align=8 +QJsonArray::iterator (0x0x7fb60ceb8300) 0 + +Class QJsonArray::const_iterator + size=16 align=8 + base size=12 base align=8 +QJsonArray::const_iterator (0x0x7fb60ceb8360) 0 + +Class QJsonArray + size=16 align=8 + base size=16 base align=8 +QJsonArray (0x0x7fb60ceb82a0) 0 + +Class QJsonParseError + size=8 align=4 + base size=8 base align=4 +QJsonParseError (0x0x7fb60ceb83c0) 0 + +Class QJsonDocument + size=8 align=8 + base size=8 base align=8 +QJsonDocument (0x0x7fb60ceb8420) 0 + +Class QJsonObject::iterator + size=16 align=8 + base size=12 base align=8 +QJsonObject::iterator (0x0x7fb60ceb84e0) 0 + +Class QJsonObject::const_iterator + size=16 align=8 + base size=12 base align=8 +QJsonObject::const_iterator (0x0x7fb60ceb8540) 0 + +Class QJsonObject + size=16 align=8 + base size=16 base align=8 +QJsonObject (0x0x7fb60ceb8480) 0 + +Class QEventLoop::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QEventLoop::QPrivateSignal (0x0x7fb60ceb8660) 0 empty + +Vtable for QEventLoop +QEventLoop::_ZTV10QEventLoop: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QEventLoop) +16 (int (*)(...))QEventLoop::metaObject +24 (int (*)(...))QEventLoop::qt_metacast +32 (int (*)(...))QEventLoop::qt_metacall +40 (int (*)(...))QEventLoop::~QEventLoop +48 (int (*)(...))QEventLoop::~QEventLoop +56 (int (*)(...))QEventLoop::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QEventLoop + size=16 align=8 + base size=16 base align=8 +QEventLoop (0x0x7fb60cdee5b0) 0 + vptr=((& QEventLoop::_ZTV10QEventLoop) + 16u) + QObject (0x0x7fb60ceb8600) 0 + primary-for QEventLoop (0x0x7fb60cdee5b0) + +Class QEventLoopLocker + size=8 align=8 + base size=8 base align=8 +QEventLoopLocker (0x0x7fb60ceb8780) 0 + +Class QAbstractEventDispatcher::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractEventDispatcher::QPrivateSignal (0x0x7fb60ceb8840) 0 empty + +Class QAbstractEventDispatcher::TimerInfo + size=12 align=4 + base size=12 base align=4 +QAbstractEventDispatcher::TimerInfo (0x0x7fb60ceb88a0) 0 + +Vtable for QAbstractEventDispatcher +QAbstractEventDispatcher::_ZTV24QAbstractEventDispatcher: 28u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI24QAbstractEventDispatcher) +16 (int (*)(...))QAbstractEventDispatcher::metaObject +24 (int (*)(...))QAbstractEventDispatcher::qt_metacast +32 (int (*)(...))QAbstractEventDispatcher::qt_metacall +40 (int (*)(...))QAbstractEventDispatcher::~QAbstractEventDispatcher +48 (int (*)(...))QAbstractEventDispatcher::~QAbstractEventDispatcher +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual +184 (int (*)(...))__cxa_pure_virtual +192 (int (*)(...))__cxa_pure_virtual +200 (int (*)(...))__cxa_pure_virtual +208 (int (*)(...))QAbstractEventDispatcher::startingUp +216 (int (*)(...))QAbstractEventDispatcher::closingDown + +Class QAbstractEventDispatcher + size=16 align=8 + base size=16 base align=8 +QAbstractEventDispatcher (0x0x7fb60cdee6e8) 0 + vptr=((& QAbstractEventDispatcher::_ZTV24QAbstractEventDispatcher) + 16u) + QObject (0x0x7fb60ceb87e0) 0 + primary-for QAbstractEventDispatcher (0x0x7fb60cdee6e8) + +Vtable for QAbstractNativeEventFilter +QAbstractNativeEventFilter::_ZTV26QAbstractNativeEventFilter: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI26QAbstractNativeEventFilter) +16 (int (*)(...))QAbstractNativeEventFilter::~QAbstractNativeEventFilter +24 (int (*)(...))QAbstractNativeEventFilter::~QAbstractNativeEventFilter +32 (int (*)(...))__cxa_pure_virtual + +Class QAbstractNativeEventFilter + size=16 align=8 + base size=16 base align=8 +QAbstractNativeEventFilter (0x0x7fb60ceb8900) 0 + vptr=((& QAbstractNativeEventFilter::_ZTV26QAbstractNativeEventFilter) + 16u) + +Class QBasicTimer + size=4 align=4 + base size=4 base align=4 +QBasicTimer (0x0x7fb60ceb8960) 0 + +Vtable for QEvent +QEvent::_ZTV6QEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QEvent) +16 (int (*)(...))QEvent::~QEvent +24 (int (*)(...))QEvent::~QEvent + +Class QEvent + size=24 align=8 + base size=20 base align=8 +QEvent (0x0x7fb60ceb8a80) 0 + vptr=((& QEvent::_ZTV6QEvent) + 16u) + +Vtable for QTimerEvent +QTimerEvent::_ZTV11QTimerEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTimerEvent) +16 (int (*)(...))QTimerEvent::~QTimerEvent +24 (int (*)(...))QTimerEvent::~QTimerEvent + +Class QTimerEvent + size=24 align=8 + base size=24 base align=8 +QTimerEvent (0x0x7fb60cdee7b8) 0 + vptr=((& QTimerEvent::_ZTV11QTimerEvent) + 16u) + QEvent (0x0x7fb60ceb8ae0) 0 + primary-for QTimerEvent (0x0x7fb60cdee7b8) + +Vtable for QChildEvent +QChildEvent::_ZTV11QChildEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QChildEvent) +16 (int (*)(...))QChildEvent::~QChildEvent +24 (int (*)(...))QChildEvent::~QChildEvent + +Class QChildEvent + size=32 align=8 + base size=32 base align=8 +QChildEvent (0x0x7fb60cdee820) 0 + vptr=((& QChildEvent::_ZTV11QChildEvent) + 16u) + QEvent (0x0x7fb60ceb8b40) 0 + primary-for QChildEvent (0x0x7fb60cdee820) + +Vtable for QDynamicPropertyChangeEvent +QDynamicPropertyChangeEvent::_ZTV27QDynamicPropertyChangeEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI27QDynamicPropertyChangeEvent) +16 (int (*)(...))QDynamicPropertyChangeEvent::~QDynamicPropertyChangeEvent +24 (int (*)(...))QDynamicPropertyChangeEvent::~QDynamicPropertyChangeEvent + +Class QDynamicPropertyChangeEvent + size=32 align=8 + base size=32 base align=8 +QDynamicPropertyChangeEvent (0x0x7fb60cdee888) 0 + vptr=((& QDynamicPropertyChangeEvent::_ZTV27QDynamicPropertyChangeEvent) + 16u) + QEvent (0x0x7fb60ceb8ba0) 0 + primary-for QDynamicPropertyChangeEvent (0x0x7fb60cdee888) + +Vtable for QDeferredDeleteEvent +QDeferredDeleteEvent::_ZTV20QDeferredDeleteEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI20QDeferredDeleteEvent) +16 (int (*)(...))QDeferredDeleteEvent::~QDeferredDeleteEvent +24 (int (*)(...))QDeferredDeleteEvent::~QDeferredDeleteEvent + +Class QDeferredDeleteEvent + size=24 align=8 + base size=24 base align=8 +QDeferredDeleteEvent (0x0x7fb60cdee8f0) 0 + vptr=((& QDeferredDeleteEvent::_ZTV20QDeferredDeleteEvent) + 16u) + QEvent (0x0x7fb60ceb8c00) 0 + primary-for QDeferredDeleteEvent (0x0x7fb60cdee8f0) + +Class QCoreApplication::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QCoreApplication::QPrivateSignal (0x0x7fb60ceb8cc0) 0 empty + +Vtable for QCoreApplication +QCoreApplication::_ZTV16QCoreApplication: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QCoreApplication) +16 (int (*)(...))QCoreApplication::metaObject +24 (int (*)(...))QCoreApplication::qt_metacast +32 (int (*)(...))QCoreApplication::qt_metacall +40 (int (*)(...))QCoreApplication::~QCoreApplication +48 (int (*)(...))QCoreApplication::~QCoreApplication +56 (int (*)(...))QCoreApplication::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QCoreApplication::notify +120 (int (*)(...))QCoreApplication::compressEvent + +Class QCoreApplication + size=16 align=8 + base size=16 base align=8 +QCoreApplication (0x0x7fb60cdee958) 0 + vptr=((& QCoreApplication::_ZTV16QCoreApplication) + 16u) + QObject (0x0x7fb60ceb8c60) 0 + primary-for QCoreApplication (0x0x7fb60cdee958) + +Class __exception + size=40 align=8 + base size=40 base align=8 +__exception (0x0x7fb60ceb8d20) 0 + +Class QMetaMethod + size=16 align=8 + base size=12 base align=8 +QMetaMethod (0x0x7fb60ceb8d80) 0 + +Class QMetaEnum + size=16 align=8 + base size=12 base align=8 +QMetaEnum (0x0x7fb60ceb8ea0) 0 + +Class QMetaProperty + size=32 align=8 + base size=32 base align=8 +QMetaProperty (0x0x7fb60ccff000) 0 + +Class QMetaClassInfo + size=16 align=8 + base size=12 base align=8 +QMetaClassInfo (0x0x7fb60ccff060) 0 + +Class QMimeData::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QMimeData::QPrivateSignal (0x0x7fb60ccff1e0) 0 empty + +Vtable for QMimeData +QMimeData::_ZTV9QMimeData: 17u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QMimeData) +16 (int (*)(...))QMimeData::metaObject +24 (int (*)(...))QMimeData::qt_metacast +32 (int (*)(...))QMimeData::qt_metacall +40 (int (*)(...))QMimeData::~QMimeData +48 (int (*)(...))QMimeData::~QMimeData +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QMimeData::hasFormat +120 (int (*)(...))QMimeData::formats +128 (int (*)(...))QMimeData::retrieveData + +Class QMimeData + size=16 align=8 + base size=16 base align=8 +QMimeData (0x0x7fb60cdeebc8) 0 + vptr=((& QMimeData::_ZTV9QMimeData) + 16u) + QObject (0x0x7fb60ccff180) 0 + primary-for QMimeData (0x0x7fb60cdeebc8) + +Class QObjectCleanupHandler::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QObjectCleanupHandler::QPrivateSignal (0x0x7fb60ccff2a0) 0 empty + +Vtable for QObjectCleanupHandler +QObjectCleanupHandler::_ZTV21QObjectCleanupHandler: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QObjectCleanupHandler) +16 (int (*)(...))QObjectCleanupHandler::metaObject +24 (int (*)(...))QObjectCleanupHandler::qt_metacast +32 (int (*)(...))QObjectCleanupHandler::qt_metacall +40 (int (*)(...))QObjectCleanupHandler::~QObjectCleanupHandler +48 (int (*)(...))QObjectCleanupHandler::~QObjectCleanupHandler +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QObjectCleanupHandler + size=24 align=8 + base size=24 base align=8 +QObjectCleanupHandler (0x0x7fb60cdeec30) 0 + vptr=((& QObjectCleanupHandler::_ZTV21QObjectCleanupHandler) + 16u) + QObject (0x0x7fb60ccff240) 0 + primary-for QObjectCleanupHandler (0x0x7fb60cdeec30) + +Class QtSharedPointer::NormalDeleter + size=1 align=1 + base size=0 base align=1 +QtSharedPointer::NormalDeleter (0x0x7fb60ccff3c0) 0 empty + +Class QtSharedPointer::ExternalRefCountData + size=16 align=8 + base size=16 base align=8 +QtSharedPointer::ExternalRefCountData (0x0x7fb60ccff540) 0 + +Class QSharedMemory::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSharedMemory::QPrivateSignal (0x0x7fb60ccffc00) 0 empty + +Vtable for QSharedMemory +QSharedMemory::_ZTV13QSharedMemory: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QSharedMemory) +16 (int (*)(...))QSharedMemory::metaObject +24 (int (*)(...))QSharedMemory::qt_metacast +32 (int (*)(...))QSharedMemory::qt_metacall +40 (int (*)(...))QSharedMemory::~QSharedMemory +48 (int (*)(...))QSharedMemory::~QSharedMemory +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSharedMemory + size=16 align=8 + base size=16 base align=8 +QSharedMemory (0x0x7fb60cdeee38) 0 + vptr=((& QSharedMemory::_ZTV13QSharedMemory) + 16u) + QObject (0x0x7fb60ccffba0) 0 + primary-for QSharedMemory (0x0x7fb60cdeee38) + +Class QSignalMapper::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSignalMapper::QPrivateSignal (0x0x7fb60ccffcc0) 0 empty + +Vtable for QSignalMapper +QSignalMapper::_ZTV13QSignalMapper: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QSignalMapper) +16 (int (*)(...))QSignalMapper::metaObject +24 (int (*)(...))QSignalMapper::qt_metacast +32 (int (*)(...))QSignalMapper::qt_metacall +40 (int (*)(...))QSignalMapper::~QSignalMapper +48 (int (*)(...))QSignalMapper::~QSignalMapper +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSignalMapper + size=16 align=8 + base size=16 base align=8 +QSignalMapper (0x0x7fb60cdeeea0) 0 + vptr=((& QSignalMapper::_ZTV13QSignalMapper) + 16u) + QObject (0x0x7fb60ccffc60) 0 + primary-for QSignalMapper (0x0x7fb60cdeeea0) + +Class QSocketNotifier::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSocketNotifier::QPrivateSignal (0x0x7fb60ccffd80) 0 empty + +Vtable for QSocketNotifier +QSocketNotifier::_ZTV15QSocketNotifier: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QSocketNotifier) +16 (int (*)(...))QSocketNotifier::metaObject +24 (int (*)(...))QSocketNotifier::qt_metacast +32 (int (*)(...))QSocketNotifier::qt_metacall +40 (int (*)(...))QSocketNotifier::~QSocketNotifier +48 (int (*)(...))QSocketNotifier::~QSocketNotifier +56 (int (*)(...))QSocketNotifier::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSocketNotifier + size=16 align=8 + base size=16 base align=8 +QSocketNotifier (0x0x7fb60cdeef08) 0 + vptr=((& QSocketNotifier::_ZTV15QSocketNotifier) + 16u) + QObject (0x0x7fb60ccffd20) 0 + primary-for QSocketNotifier (0x0x7fb60cdeef08) + +Class QSystemSemaphore + size=8 align=8 + base size=8 base align=8 +QSystemSemaphore (0x0x7fb60ccffde0) 0 + +Class QTimer::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTimer::QPrivateSignal (0x0x7fb60ccfff00) 0 empty + +Vtable for QTimer +QTimer::_ZTV6QTimer: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QTimer) +16 (int (*)(...))QTimer::metaObject +24 (int (*)(...))QTimer::qt_metacast +32 (int (*)(...))QTimer::qt_metacall +40 (int (*)(...))QTimer::~QTimer +48 (int (*)(...))QTimer::~QTimer +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QTimer::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QTimer + size=32 align=8 + base size=29 base align=8 +QTimer (0x0x7fb60cdeef70) 0 + vptr=((& QTimer::_ZTV6QTimer) + 16u) + QObject (0x0x7fb60ccffea0) 0 + primary-for QTimer (0x0x7fb60cdeef70) + +Class QTranslator::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTranslator::QPrivateSignal (0x0x7fb60ca600c0) 0 empty + +Vtable for QTranslator +QTranslator::_ZTV11QTranslator: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTranslator) +16 (int (*)(...))QTranslator::metaObject +24 (int (*)(...))QTranslator::qt_metacast +32 (int (*)(...))QTranslator::qt_metacall +40 (int (*)(...))QTranslator::~QTranslator +48 (int (*)(...))QTranslator::~QTranslator +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QTranslator::translate +120 (int (*)(...))QTranslator::isEmpty + +Class QTranslator + size=16 align=8 + base size=16 base align=8 +QTranslator (0x0x7fb60ca62068) 0 + vptr=((& QTranslator::_ZTV11QTranslator) + 16u) + QObject (0x0x7fb60ca60060) 0 + primary-for QTranslator (0x0x7fb60ca62068) + +Class QMimeType + size=8 align=8 + base size=8 base align=8 +QMimeType (0x0x7fb60ca60120) 0 + +Class QMimeDatabase + size=8 align=8 + base size=8 base align=8 +QMimeDatabase (0x0x7fb60ca602a0) 0 + +Vtable for QFactoryInterface +QFactoryInterface::_ZTV17QFactoryInterface: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QFactoryInterface) +16 (int (*)(...))QFactoryInterface::~QFactoryInterface +24 (int (*)(...))QFactoryInterface::~QFactoryInterface +32 (int (*)(...))__cxa_pure_virtual + +Class QFactoryInterface + size=8 align=8 + base size=8 base align=8 +QFactoryInterface (0x0x7fb60ca60300) 0 nearly-empty + vptr=((& QFactoryInterface::_ZTV17QFactoryInterface) + 16u) + +Class QLibrary::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QLibrary::QPrivateSignal (0x0x7fb60ca60420) 0 empty + +Vtable for QLibrary +QLibrary::_ZTV8QLibrary: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI8QLibrary) +16 (int (*)(...))QLibrary::metaObject +24 (int (*)(...))QLibrary::qt_metacast +32 (int (*)(...))QLibrary::qt_metacall +40 (int (*)(...))QLibrary::~QLibrary +48 (int (*)(...))QLibrary::~QLibrary +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QLibrary + size=32 align=8 + base size=25 base align=8 +QLibrary (0x0x7fb60ca62138) 0 + vptr=((& QLibrary::_ZTV8QLibrary) + 16u) + QObject (0x0x7fb60ca603c0) 0 + primary-for QLibrary (0x0x7fb60ca62138) + +Class QStaticPlugin + size=16 align=8 + base size=16 base align=8 +QStaticPlugin (0x0x7fb60ca60540) 0 + +Class QPluginLoader::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPluginLoader::QPrivateSignal (0x0x7fb60ca606c0) 0 empty + +Vtable for QPluginLoader +QPluginLoader::_ZTV13QPluginLoader: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QPluginLoader) +16 (int (*)(...))QPluginLoader::metaObject +24 (int (*)(...))QPluginLoader::qt_metacast +32 (int (*)(...))QPluginLoader::qt_metacall +40 (int (*)(...))QPluginLoader::~QPluginLoader +48 (int (*)(...))QPluginLoader::~QPluginLoader +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QPluginLoader + size=32 align=8 + base size=25 base align=8 +QPluginLoader (0x0x7fb60ca622d8) 0 + vptr=((& QPluginLoader::_ZTV13QPluginLoader) + 16u) + QObject (0x0x7fb60ca60660) 0 + primary-for QPluginLoader (0x0x7fb60ca622d8) + +Class QUuid + size=16 align=4 + base size=16 base align=4 +QUuid (0x0x7fb60ca60720) 0 + +Class QAbstractState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractState::QPrivateSignal (0x0x7fb60ca608a0) 0 empty + +Vtable for QAbstractState +QAbstractState::_ZTV14QAbstractState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI14QAbstractState) +16 (int (*)(...))QAbstractState::metaObject +24 (int (*)(...))QAbstractState::qt_metacast +32 (int (*)(...))QAbstractState::qt_metacall +40 (int (*)(...))QAbstractState::~QAbstractState +48 (int (*)(...))QAbstractState::~QAbstractState +56 (int (*)(...))QAbstractState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QAbstractState + size=16 align=8 + base size=16 base align=8 +QAbstractState (0x0x7fb60ca623a8) 0 + vptr=((& QAbstractState::_ZTV14QAbstractState) + 16u) + QObject (0x0x7fb60ca60840) 0 + primary-for QAbstractState (0x0x7fb60ca623a8) + +Class QAbstractTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractTransition::QPrivateSignal (0x0x7fb60ca60960) 0 empty + +Vtable for QAbstractTransition +QAbstractTransition::_ZTV19QAbstractTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractTransition) +16 (int (*)(...))QAbstractTransition::metaObject +24 (int (*)(...))QAbstractTransition::qt_metacast +32 (int (*)(...))QAbstractTransition::qt_metacall +40 (int (*)(...))QAbstractTransition::~QAbstractTransition +48 (int (*)(...))QAbstractTransition::~QAbstractTransition +56 (int (*)(...))QAbstractTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QAbstractTransition + size=16 align=8 + base size=16 base align=8 +QAbstractTransition (0x0x7fb60ca62410) 0 + vptr=((& QAbstractTransition::_ZTV19QAbstractTransition) + 16u) + QObject (0x0x7fb60ca60900) 0 + primary-for QAbstractTransition (0x0x7fb60ca62410) + +Class QEventTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QEventTransition::QPrivateSignal (0x0x7fb60ca60a20) 0 empty + +Vtable for QEventTransition +QEventTransition::_ZTV16QEventTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QEventTransition) +16 (int (*)(...))QEventTransition::metaObject +24 (int (*)(...))QEventTransition::qt_metacast +32 (int (*)(...))QEventTransition::qt_metacall +40 (int (*)(...))QEventTransition::~QEventTransition +48 (int (*)(...))QEventTransition::~QEventTransition +56 (int (*)(...))QEventTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QEventTransition::eventTest +120 (int (*)(...))QEventTransition::onTransition + +Class QEventTransition + size=16 align=8 + base size=16 base align=8 +QEventTransition (0x0x7fb60ca62478) 0 + vptr=((& QEventTransition::_ZTV16QEventTransition) + 16u) + QAbstractTransition (0x0x7fb60ca624e0) 0 + primary-for QEventTransition (0x0x7fb60ca62478) + QObject (0x0x7fb60ca609c0) 0 + primary-for QAbstractTransition (0x0x7fb60ca624e0) + +Class QFinalState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFinalState::QPrivateSignal (0x0x7fb60ca60ae0) 0 empty + +Vtable for QFinalState +QFinalState::_ZTV11QFinalState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QFinalState) +16 (int (*)(...))QFinalState::metaObject +24 (int (*)(...))QFinalState::qt_metacast +32 (int (*)(...))QFinalState::qt_metacall +40 (int (*)(...))QFinalState::~QFinalState +48 (int (*)(...))QFinalState::~QFinalState +56 (int (*)(...))QFinalState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFinalState::onEntry +120 (int (*)(...))QFinalState::onExit + +Class QFinalState + size=16 align=8 + base size=16 base align=8 +QFinalState (0x0x7fb60ca62548) 0 + vptr=((& QFinalState::_ZTV11QFinalState) + 16u) + QAbstractState (0x0x7fb60ca625b0) 0 + primary-for QFinalState (0x0x7fb60ca62548) + QObject (0x0x7fb60ca60a80) 0 + primary-for QAbstractState (0x0x7fb60ca625b0) + +Class QHistoryState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QHistoryState::QPrivateSignal (0x0x7fb60ca60ba0) 0 empty + +Vtable for QHistoryState +QHistoryState::_ZTV13QHistoryState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QHistoryState) +16 (int (*)(...))QHistoryState::metaObject +24 (int (*)(...))QHistoryState::qt_metacast +32 (int (*)(...))QHistoryState::qt_metacall +40 (int (*)(...))QHistoryState::~QHistoryState +48 (int (*)(...))QHistoryState::~QHistoryState +56 (int (*)(...))QHistoryState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QHistoryState::onEntry +120 (int (*)(...))QHistoryState::onExit + +Class QHistoryState + size=16 align=8 + base size=16 base align=8 +QHistoryState (0x0x7fb60ca62618) 0 + vptr=((& QHistoryState::_ZTV13QHistoryState) + 16u) + QAbstractState (0x0x7fb60ca62680) 0 + primary-for QHistoryState (0x0x7fb60ca62618) + QObject (0x0x7fb60ca60b40) 0 + primary-for QAbstractState (0x0x7fb60ca62680) + +Class QSignalTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSignalTransition::QPrivateSignal (0x0x7fb60ca60c60) 0 empty + +Vtable for QSignalTransition +QSignalTransition::_ZTV17QSignalTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QSignalTransition) +16 (int (*)(...))QSignalTransition::metaObject +24 (int (*)(...))QSignalTransition::qt_metacast +32 (int (*)(...))QSignalTransition::qt_metacall +40 (int (*)(...))QSignalTransition::~QSignalTransition +48 (int (*)(...))QSignalTransition::~QSignalTransition +56 (int (*)(...))QSignalTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSignalTransition::eventTest +120 (int (*)(...))QSignalTransition::onTransition + +Class QSignalTransition + size=16 align=8 + base size=16 base align=8 +QSignalTransition (0x0x7fb60ca626e8) 0 + vptr=((& QSignalTransition::_ZTV17QSignalTransition) + 16u) + QAbstractTransition (0x0x7fb60ca62750) 0 + primary-for QSignalTransition (0x0x7fb60ca626e8) + QObject (0x0x7fb60ca60c00) 0 + primary-for QAbstractTransition (0x0x7fb60ca62750) + +Class QState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QState::QPrivateSignal (0x0x7fb60ca60d20) 0 empty + +Vtable for QState +QState::_ZTV6QState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QState) +16 (int (*)(...))QState::metaObject +24 (int (*)(...))QState::qt_metacast +32 (int (*)(...))QState::qt_metacall +40 (int (*)(...))QState::~QState +48 (int (*)(...))QState::~QState +56 (int (*)(...))QState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QState::onEntry +120 (int (*)(...))QState::onExit + +Class QState + size=16 align=8 + base size=16 base align=8 +QState (0x0x7fb60ca627b8) 0 + vptr=((& QState::_ZTV6QState) + 16u) + QAbstractState (0x0x7fb60ca62820) 0 + primary-for QState (0x0x7fb60ca627b8) + QObject (0x0x7fb60ca60cc0) 0 + primary-for QAbstractState (0x0x7fb60ca62820) + +Class QStateMachine::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QStateMachine::QPrivateSignal (0x0x7fb60ca60e40) 0 empty + +Vtable for QStateMachine::SignalEvent +QStateMachine::SignalEvent::_ZTVN13QStateMachine11SignalEventE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN13QStateMachine11SignalEventE) +16 (int (*)(...))QStateMachine::SignalEvent::~SignalEvent +24 (int (*)(...))QStateMachine::SignalEvent::~SignalEvent + +Class QStateMachine::SignalEvent + size=48 align=8 + base size=48 base align=8 +QStateMachine::SignalEvent (0x0x7fb60ca629c0) 0 + vptr=((& QStateMachine::SignalEvent::_ZTVN13QStateMachine11SignalEventE) + 16u) + QEvent (0x0x7fb60ca60ea0) 0 + primary-for QStateMachine::SignalEvent (0x0x7fb60ca629c0) + +Vtable for QStateMachine::WrappedEvent +QStateMachine::WrappedEvent::_ZTVN13QStateMachine12WrappedEventE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN13QStateMachine12WrappedEventE) +16 (int (*)(...))QStateMachine::WrappedEvent::~WrappedEvent +24 (int (*)(...))QStateMachine::WrappedEvent::~WrappedEvent + +Class QStateMachine::WrappedEvent + size=40 align=8 + base size=40 base align=8 +QStateMachine::WrappedEvent (0x0x7fb60ca62a28) 0 + vptr=((& QStateMachine::WrappedEvent::_ZTVN13QStateMachine12WrappedEventE) + 16u) + QEvent (0x0x7fb60ca60f00) 0 + primary-for QStateMachine::WrappedEvent (0x0x7fb60ca62a28) + +Vtable for QStateMachine +QStateMachine::_ZTV13QStateMachine: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QStateMachine) +16 (int (*)(...))QStateMachine::metaObject +24 (int (*)(...))QStateMachine::qt_metacast +32 (int (*)(...))QStateMachine::qt_metacall +40 (int (*)(...))QStateMachine::~QStateMachine +48 (int (*)(...))QStateMachine::~QStateMachine +56 (int (*)(...))QStateMachine::event +64 (int (*)(...))QStateMachine::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QStateMachine::onEntry +120 (int (*)(...))QStateMachine::onExit +128 (int (*)(...))QStateMachine::beginSelectTransitions +136 (int (*)(...))QStateMachine::endSelectTransitions +144 (int (*)(...))QStateMachine::beginMicrostep +152 (int (*)(...))QStateMachine::endMicrostep + +Class QStateMachine + size=16 align=8 + base size=16 base align=8 +QStateMachine (0x0x7fb60ca62888) 0 + vptr=((& QStateMachine::_ZTV13QStateMachine) + 16u) + QState (0x0x7fb60ca628f0) 0 + primary-for QStateMachine (0x0x7fb60ca62888) + QAbstractState (0x0x7fb60ca62958) 0 + primary-for QState (0x0x7fb60ca628f0) + QObject (0x0x7fb60ca60de0) 0 + primary-for QAbstractState (0x0x7fb60ca62958) + +Vtable for QException +QException::_ZTV10QException: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QException) +16 (int (*)(...))QException::~QException +24 (int (*)(...))QException::~QException +32 (int (*)(...))std::exception::what +40 (int (*)(...))QException::raise +48 (int (*)(...))QException::clone + +Class QException + size=8 align=8 + base size=8 base align=8 +QException (0x0x7fb60ca62a90) 0 nearly-empty + vptr=((& QException::_ZTV10QException) + 16u) + std::exception (0x0x7fb60ca60f60) 0 nearly-empty + primary-for QException (0x0x7fb60ca62a90) + +Vtable for QUnhandledException +QUnhandledException::_ZTV19QUnhandledException: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QUnhandledException) +16 (int (*)(...))QUnhandledException::~QUnhandledException +24 (int (*)(...))QUnhandledException::~QUnhandledException +32 (int (*)(...))std::exception::what +40 (int (*)(...))QUnhandledException::raise +48 (int (*)(...))QUnhandledException::clone + +Class QUnhandledException + size=8 align=8 + base size=8 base align=8 +QUnhandledException (0x0x7fb60ca62af8) 0 nearly-empty + vptr=((& QUnhandledException::_ZTV19QUnhandledException) + 16u) + QException (0x0x7fb60ca62b60) 0 nearly-empty + primary-for QUnhandledException (0x0x7fb60ca62af8) + std::exception (0x0x7fb60c7a9000) 0 nearly-empty + primary-for QException (0x0x7fb60ca62b60) + +Class QtPrivate::ExceptionHolder + size=8 align=8 + base size=8 base align=8 +QtPrivate::ExceptionHolder (0x0x7fb60c7a9060) 0 + +Class QtPrivate::ExceptionStore + size=8 align=8 + base size=8 base align=8 +QtPrivate::ExceptionStore (0x0x7fb60c7a9120) 0 + +Vtable for QRunnable +QRunnable::_ZTV9QRunnable: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QRunnable) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))QRunnable::~QRunnable +32 (int (*)(...))QRunnable::~QRunnable + +Class QRunnable + size=16 align=8 + base size=12 base align=8 +QRunnable (0x0x7fb60c7a9180) 0 + vptr=((& QRunnable::_ZTV9QRunnable) + 16u) + +Class QBasicMutex + size=8 align=8 + base size=8 base align=8 +QBasicMutex (0x0x7fb60c7a91e0) 0 + +Class QMutex + size=8 align=8 + base size=8 base align=8 +QMutex (0x0x7fb60ca62d00) 0 + QBasicMutex (0x0x7fb60c7a9300) 0 + +Class QMutexLocker + size=8 align=8 + base size=8 base align=8 +QMutexLocker (0x0x7fb60c7a9360) 0 + +Class QtPrivate::ResultItem + size=16 align=8 + base size=16 base align=8 +QtPrivate::ResultItem (0x0x7fb60c7a93c0) 0 + +Class QtPrivate::ResultIteratorBase + size=16 align=8 + base size=12 base align=8 +QtPrivate::ResultIteratorBase (0x0x7fb60c7a9420) 0 + +Vtable for QtPrivate::ResultStoreBase +QtPrivate::ResultStoreBase::_ZTVN9QtPrivate15ResultStoreBaseE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN9QtPrivate15ResultStoreBaseE) +16 (int (*)(...))QtPrivate::ResultStoreBase::~ResultStoreBase +24 (int (*)(...))QtPrivate::ResultStoreBase::~ResultStoreBase + +Class QtPrivate::ResultStoreBase + size=48 align=8 + base size=44 base align=8 +QtPrivate::ResultStoreBase (0x0x7fb60c7a95a0) 0 + vptr=((& QtPrivate::ResultStoreBase::_ZTVN9QtPrivate15ResultStoreBaseE) + 16u) + +Vtable for QFutureInterfaceBase +QFutureInterfaceBase::_ZTV20QFutureInterfaceBase: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI20QFutureInterfaceBase) +16 (int (*)(...))QFutureInterfaceBase::~QFutureInterfaceBase +24 (int (*)(...))QFutureInterfaceBase::~QFutureInterfaceBase + +Class QFutureInterfaceBase + size=16 align=8 + base size=16 base align=8 +QFutureInterfaceBase (0x0x7fb60c7a9660) 0 + vptr=((& QFutureInterfaceBase::_ZTV20QFutureInterfaceBase) + 16u) + +Class QFutureWatcherBase::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFutureWatcherBase::QPrivateSignal (0x0x7fb60c7a99c0) 0 empty + +Vtable for QFutureWatcherBase +QFutureWatcherBase::_ZTV18QFutureWatcherBase: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QFutureWatcherBase) +16 (int (*)(...))QFutureWatcherBase::metaObject +24 (int (*)(...))QFutureWatcherBase::qt_metacast +32 (int (*)(...))QFutureWatcherBase::qt_metacall +40 (int (*)(...))QFutureWatcherBase::~QFutureWatcherBase +48 (int (*)(...))QFutureWatcherBase::~QFutureWatcherBase +56 (int (*)(...))QFutureWatcherBase::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QFutureWatcherBase::connectNotify +104 (int (*)(...))QFutureWatcherBase::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QFutureWatcherBase + size=16 align=8 + base size=16 base align=8 +QFutureWatcherBase (0x0x7fb60c834618) 0 + vptr=((& QFutureWatcherBase::_ZTV18QFutureWatcherBase) + 16u) + QObject (0x0x7fb60c7a9960) 0 + primary-for QFutureWatcherBase (0x0x7fb60c834618) + +Class QReadWriteLock + size=8 align=8 + base size=8 base align=8 +QReadWriteLock (0x0x7fb60c7a9ae0) 0 + +Class QReadLocker + size=8 align=8 + base size=8 base align=8 +QReadLocker (0x0x7fb60c7a9b40) 0 + +Class QWriteLocker + size=8 align=8 + base size=8 base align=8 +QWriteLocker (0x0x7fb60c7a9ba0) 0 + +Class QSemaphore + size=8 align=8 + base size=8 base align=8 +QSemaphore (0x0x7fb60c7a9c00) 0 + +Class QThread::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QThread::QPrivateSignal (0x0x7fb60c7a9cc0) 0 empty + +Vtable for QThread +QThread::_ZTV7QThread: 15u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QThread) +16 (int (*)(...))QThread::metaObject +24 (int (*)(...))QThread::qt_metacast +32 (int (*)(...))QThread::qt_metacall +40 (int (*)(...))QThread::~QThread +48 (int (*)(...))QThread::~QThread +56 (int (*)(...))QThread::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QThread::run + +Class QThread + size=16 align=8 + base size=16 base align=8 +QThread (0x0x7fb60c8349c0) 0 + vptr=((& QThread::_ZTV7QThread) + 16u) + QObject (0x0x7fb60c7a9c60) 0 + primary-for QThread (0x0x7fb60c8349c0) + +Class QThreadPool::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QThreadPool::QPrivateSignal (0x0x7fb60c7a9d80) 0 empty + +Vtable for QThreadPool +QThreadPool::_ZTV11QThreadPool: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QThreadPool) +16 (int (*)(...))QThreadPool::metaObject +24 (int (*)(...))QThreadPool::qt_metacast +32 (int (*)(...))QThreadPool::qt_metacall +40 (int (*)(...))QThreadPool::~QThreadPool +48 (int (*)(...))QThreadPool::~QThreadPool +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QThreadPool + size=16 align=8 + base size=16 base align=8 +QThreadPool (0x0x7fb60c834a28) 0 + vptr=((& QThreadPool::_ZTV11QThreadPool) + 16u) + QObject (0x0x7fb60c7a9d20) 0 + primary-for QThreadPool (0x0x7fb60c834a28) + +Class QThreadStorageData + size=4 align=4 + base size=4 base align=4 +QThreadStorageData (0x0x7fb60c7a9de0) 0 + +Class QWaitCondition + size=8 align=8 + base size=8 base align=8 +QWaitCondition (0x0x7fb60c7a9ea0) 0 + +Class QBitArray + size=8 align=8 + base size=8 base align=8 +QBitArray (0x0x7fb60c581480) 0 + +Class QBitRef + size=16 align=8 + base size=12 base align=8 +QBitRef (0x0x7fb60c5814e0) 0 + +Class QByteArrayMatcher::Data + size=272 align=8 + base size=272 base align=8 +QByteArrayMatcher::Data (0x0x7fb60c581660) 0 + +Class QByteArrayMatcher + size=1040 align=8 + base size=1040 base align=8 +QByteArrayMatcher (0x0x7fb60c581600) 0 + +Class QCollatorSortKey + size=8 align=8 + base size=8 base align=8 +QCollatorSortKey (0x0x7fb60c5817e0) 0 + +Class QCollator + size=8 align=8 + base size=8 base align=8 +QCollator (0x0x7fb60c5818a0) 0 + +Class QCommandLineOption + size=8 align=8 + base size=8 base align=8 +QCommandLineOption (0x0x7fb60c581a80) 0 + +Class QCommandLineParser + size=8 align=8 + base size=8 base align=8 +QCommandLineParser (0x0x7fb60c581c00) 0 + +Class QCryptographicHash + size=8 align=8 + base size=8 base align=8 +QCryptographicHash (0x0x7fb60c581c60) 0 + +Class QElapsedTimer + size=16 align=8 + base size=16 base align=8 +QElapsedTimer (0x0x7fb60c581cc0) 0 + +Class QPoint + size=8 align=4 + base size=8 base align=4 +QPoint (0x0x7fb60c581d20) 0 + +Class QPointF + size=16 align=8 + base size=16 base align=8 +QPointF (0x0x7fb60c581e40) 0 + +Class QLine + size=16 align=4 + base size=16 base align=4 +QLine (0x0x7fb60c581f60) 0 + +Class QLineF + size=32 align=8 + base size=32 base align=8 +QLineF (0x0x7fb60c6f70c0) 0 + +Class QLinkedListData + size=32 align=8 + base size=32 base align=8 +QLinkedListData (0x0x7fb60c6f71e0) 0 + +Class QMargins + size=16 align=4 + base size=16 base align=4 +QMargins (0x0x7fb60c6f7540) 0 + +Class QMarginsF + size=32 align=8 + base size=32 base align=8 +QMarginsF (0x0x7fb60c6f7660) 0 + +Class QMessageAuthenticationCode + size=8 align=8 + base size=8 base align=8 +QMessageAuthenticationCode (0x0x7fb60c6f7780) 0 + +Class QSize + size=8 align=4 + base size=8 base align=4 +QSize (0x0x7fb60c6f7840) 0 + +Class QSizeF + size=16 align=8 + base size=16 base align=8 +QSizeF (0x0x7fb60c6f7960) 0 + +Class QRect + size=16 align=4 + base size=16 base align=4 +QRect (0x0x7fb60c6f7a80) 0 + +Class QRectF + size=32 align=8 + base size=32 base align=8 +QRectF (0x0x7fb60c6f7ba0) 0 + +Class QRegularExpression + size=8 align=8 + base size=8 base align=8 +QRegularExpression (0x0x7fb60c6f7cc0) 0 + +Class QRegularExpressionMatch + size=8 align=8 + base size=8 base align=8 +QRegularExpressionMatch (0x0x7fb60c20c000) 0 + +Class QRegularExpressionMatchIterator + size=8 align=8 + base size=8 base align=8 +QRegularExpressionMatchIterator (0x0x7fb60c20c180) 0 + +Class QAbstractConcatenable + size=1 align=1 + base size=0 base align=1 +QAbstractConcatenable (0x0x7fb60c20c3c0) 0 empty + +Class QTextBoundaryFinder + size=48 align=8 + base size=48 base align=8 +QTextBoundaryFinder (0x0x7fb60c20cde0) 0 + +Class QTimeLine::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTimeLine::QPrivateSignal (0x0x7fb60c20cf60) 0 empty + +Vtable for QTimeLine +QTimeLine::_ZTV9QTimeLine: 15u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QTimeLine) +16 (int (*)(...))QTimeLine::metaObject +24 (int (*)(...))QTimeLine::qt_metacast +32 (int (*)(...))QTimeLine::qt_metacall +40 (int (*)(...))QTimeLine::~QTimeLine +48 (int (*)(...))QTimeLine::~QTimeLine +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QTimeLine::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QTimeLine::valueForTime + +Class QTimeLine + size=16 align=8 + base size=16 base align=8 +QTimeLine (0x0x7fb60c2a0340) 0 + vptr=((& QTimeLine::_ZTV9QTimeLine) + 16u) + QObject (0x0x7fb60c20cf00) 0 + primary-for QTimeLine (0x0x7fb60c2a0340) + +Class QTimeZone::OffsetData + size=32 align=8 + base size=28 base align=8 +QTimeZone::OffsetData (0x0x7fb60c2fc060) 0 + +Class QTimeZone + size=8 align=8 + base size=8 base align=8 +QTimeZone (0x0x7fb60c2fc000) 0 + +Class QXmlStreamStringRef + size=16 align=8 + base size=16 base align=8 +QXmlStreamStringRef (0x0x7fb60c2fc2a0) 0 + +Class QXmlStreamAttribute + size=80 align=8 + base size=73 base align=8 +QXmlStreamAttribute (0x0x7fb60c2fc300) 0 + +Class QXmlStreamAttributes + size=8 align=8 + base size=8 base align=8 +QXmlStreamAttributes (0x0x7fb60c2a05b0) 0 + QVector (0x0x7fb60c2fc4e0) 0 + +Class QXmlStreamNamespaceDeclaration + size=40 align=8 + base size=40 base align=8 +QXmlStreamNamespaceDeclaration (0x0x7fb60c2fc540) 0 + +Class QXmlStreamNotationDeclaration + size=56 align=8 + base size=56 base align=8 +QXmlStreamNotationDeclaration (0x0x7fb60c2fc660) 0 + +Class QXmlStreamEntityDeclaration + size=88 align=8 + base size=88 base align=8 +QXmlStreamEntityDeclaration (0x0x7fb60c2fc780) 0 + +Vtable for QXmlStreamEntityResolver +QXmlStreamEntityResolver::_ZTV24QXmlStreamEntityResolver: 6u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI24QXmlStreamEntityResolver) +16 (int (*)(...))QXmlStreamEntityResolver::~QXmlStreamEntityResolver +24 (int (*)(...))QXmlStreamEntityResolver::~QXmlStreamEntityResolver +32 (int (*)(...))QXmlStreamEntityResolver::resolveEntity +40 (int (*)(...))QXmlStreamEntityResolver::resolveUndeclaredEntity + +Class QXmlStreamEntityResolver + size=8 align=8 + base size=8 base align=8 +QXmlStreamEntityResolver (0x0x7fb60c2fc8a0) 0 nearly-empty + vptr=((& QXmlStreamEntityResolver::_ZTV24QXmlStreamEntityResolver) + 16u) + +Class QXmlStreamReader + size=8 align=8 + base size=8 base align=8 +QXmlStreamReader (0x0x7fb60c2fc900) 0 + +Class QXmlStreamWriter + size=8 align=8 + base size=8 base align=8 +QXmlStreamWriter (0x0x7fb60c2fca20) 0 + +Class QGeoAddress + size=8 align=8 + base size=8 base align=8 +QGeoAddress (0x0x7fb60c2fcb40) 0 + +Class QGeoCoordinate + size=8 align=8 + base size=8 base align=8 +QGeoCoordinate (0x0x7fb60c2fcde0) 0 + +Class QGeoShape + size=8 align=8 + base size=8 base align=8 +QGeoShape (0x0x7fb60bfed0c0) 0 + +Class QGeoAreaMonitorInfo + size=8 align=8 + base size=8 base align=8 +QGeoAreaMonitorInfo (0x0x7fb60bfed360) 0 + +Class QGeoPositionInfo + size=8 align=8 + base size=8 base align=8 +QGeoPositionInfo (0x0x7fb60bfed420) 0 + +Class QGeoPositionInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoPositionInfoSource::QPrivateSignal (0x0x7fb60bfed4e0) 0 empty + +Vtable for QGeoPositionInfoSource +QGeoPositionInfoSource::_ZTV22QGeoPositionInfoSource: 23u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI22QGeoPositionInfoSource) +16 (int (*)(...))QGeoPositionInfoSource::metaObject +24 (int (*)(...))QGeoPositionInfoSource::qt_metacast +32 (int (*)(...))QGeoPositionInfoSource::qt_metacall +40 (int (*)(...))QGeoPositionInfoSource::~QGeoPositionInfoSource +48 (int (*)(...))QGeoPositionInfoSource::~QGeoPositionInfoSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoPositionInfoSource::setUpdateInterval +120 (int (*)(...))QGeoPositionInfoSource::setPreferredPositioningMethods +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual + +Class QGeoPositionInfoSource + size=24 align=8 + base size=24 base align=8 +QGeoPositionInfoSource (0x0x7fb60c2a0888) 0 + vptr=((& QGeoPositionInfoSource::_ZTV22QGeoPositionInfoSource) + 16u) + QObject (0x0x7fb60bfed480) 0 + primary-for QGeoPositionInfoSource (0x0x7fb60c2a0888) + +Class QGeoAreaMonitorSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoAreaMonitorSource::QPrivateSignal (0x0x7fb60bfed660) 0 empty + +Vtable for QGeoAreaMonitorSource +QGeoAreaMonitorSource::_ZTV21QGeoAreaMonitorSource: 23u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QGeoAreaMonitorSource) +16 (int (*)(...))QGeoAreaMonitorSource::metaObject +24 (int (*)(...))QGeoAreaMonitorSource::qt_metacast +32 (int (*)(...))QGeoAreaMonitorSource::qt_metacall +40 (int (*)(...))QGeoAreaMonitorSource::~QGeoAreaMonitorSource +48 (int (*)(...))QGeoAreaMonitorSource::~QGeoAreaMonitorSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoAreaMonitorSource::setPositionInfoSource +120 (int (*)(...))QGeoAreaMonitorSource::positionInfoSource +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual + +Class QGeoAreaMonitorSource + size=24 align=8 + base size=24 base align=8 +QGeoAreaMonitorSource (0x0x7fb60c2a09c0) 0 + vptr=((& QGeoAreaMonitorSource::_ZTV21QGeoAreaMonitorSource) + 16u) + QObject (0x0x7fb60bfed600) 0 + primary-for QGeoAreaMonitorSource (0x0x7fb60c2a09c0) + +Class QGeoCircle + size=8 align=8 + base size=8 base align=8 +QGeoCircle (0x0x7fb60c2a0a28) 0 + QGeoShape (0x0x7fb60bfed6c0) 0 + +Class QGeoLocation + size=8 align=8 + base size=8 base align=8 +QGeoLocation (0x0x7fb60bfed900) 0 + +Class QGeoSatelliteInfo + size=8 align=8 + base size=8 base align=8 +QGeoSatelliteInfo (0x0x7fb60bfedba0) 0 + +Class QGeoSatelliteInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoSatelliteInfoSource::QPrivateSignal (0x0x7fb60bfedc60) 0 empty + +Vtable for QGeoSatelliteInfoSource +QGeoSatelliteInfoSource::_ZTV23QGeoSatelliteInfoSource: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QGeoSatelliteInfoSource) +16 (int (*)(...))QGeoSatelliteInfoSource::metaObject +24 (int (*)(...))QGeoSatelliteInfoSource::qt_metacast +32 (int (*)(...))QGeoSatelliteInfoSource::qt_metacall +40 (int (*)(...))QGeoSatelliteInfoSource::~QGeoSatelliteInfoSource +48 (int (*)(...))QGeoSatelliteInfoSource::~QGeoSatelliteInfoSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoSatelliteInfoSource::setUpdateInterval +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual + +Class QGeoSatelliteInfoSource + size=24 align=8 + base size=24 base align=8 +QGeoSatelliteInfoSource (0x0x7fb60c2a0b60) 0 + vptr=((& QGeoSatelliteInfoSource::_ZTV23QGeoSatelliteInfoSource) + 16u) + QObject (0x0x7fb60bfedc00) 0 + primary-for QGeoSatelliteInfoSource (0x0x7fb60c2a0b60) + +Vtable for QGeoPositionInfoSourceFactory +QGeoPositionInfoSourceFactory::_ZTV29QGeoPositionInfoSourceFactory: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI29QGeoPositionInfoSourceFactory) +16 (int (*)(...))QGeoPositionInfoSourceFactory::~QGeoPositionInfoSourceFactory +24 (int (*)(...))QGeoPositionInfoSourceFactory::~QGeoPositionInfoSourceFactory +32 (int (*)(...))__cxa_pure_virtual +40 (int (*)(...))__cxa_pure_virtual +48 (int (*)(...))__cxa_pure_virtual + +Class QGeoPositionInfoSourceFactory + size=8 align=8 + base size=8 base align=8 +QGeoPositionInfoSourceFactory (0x0x7fb60bfedd20) 0 nearly-empty + vptr=((& QGeoPositionInfoSourceFactory::_ZTV29QGeoPositionInfoSourceFactory) + 16u) + +Class QGeoRectangle + size=8 align=8 + base size=8 base align=8 +QGeoRectangle (0x0x7fb60c2a0bc8) 0 + QGeoShape (0x0x7fb60bfedde0) 0 + +Class QNmeaPositionInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QNmeaPositionInfoSource::QPrivateSignal (0x0x7fb60c0d21e0) 0 empty + +Vtable for QNmeaPositionInfoSource +QNmeaPositionInfoSource::_ZTV23QNmeaPositionInfoSource: 24u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QNmeaPositionInfoSource) +16 (int (*)(...))QNmeaPositionInfoSource::metaObject +24 (int (*)(...))QNmeaPositionInfoSource::qt_metacast +32 (int (*)(...))QNmeaPositionInfoSource::qt_metacall +40 (int (*)(...))QNmeaPositionInfoSource::~QNmeaPositionInfoSource +48 (int (*)(...))QNmeaPositionInfoSource::~QNmeaPositionInfoSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QNmeaPositionInfoSource::setUpdateInterval +120 (int (*)(...))QGeoPositionInfoSource::setPreferredPositioningMethods +128 (int (*)(...))QNmeaPositionInfoSource::lastKnownPosition +136 (int (*)(...))QNmeaPositionInfoSource::supportedPositioningMethods +144 (int (*)(...))QNmeaPositionInfoSource::minimumUpdateInterval +152 (int (*)(...))QNmeaPositionInfoSource::error +160 (int (*)(...))QNmeaPositionInfoSource::startUpdates +168 (int (*)(...))QNmeaPositionInfoSource::stopUpdates +176 (int (*)(...))QNmeaPositionInfoSource::requestUpdate +184 (int (*)(...))QNmeaPositionInfoSource::parsePosInfoFromNmeaData + +Class QNmeaPositionInfoSource + size=32 align=8 + base size=32 base align=8 +QNmeaPositionInfoSource (0x0x7fb60c2a0d00) 0 + vptr=((& QNmeaPositionInfoSource::_ZTV23QNmeaPositionInfoSource) + 16u) + QGeoPositionInfoSource (0x0x7fb60c2a0d68) 0 + primary-for QNmeaPositionInfoSource (0x0x7fb60c2a0d00) + QObject (0x0x7fb60c0d2180) 0 + primary-for QGeoPositionInfoSource (0x0x7fb60c2a0d68) + diff --git a/tests/auto/bic/data/QtPositioning.5.6.0.linux-gcc-amd64.txt b/tests/auto/bic/data/QtPositioning.5.6.0.linux-gcc-amd64.txt new file mode 100644 index 0000000..42ed838 --- /dev/null +++ b/tests/auto/bic/data/QtPositioning.5.6.0.linux-gcc-amd64.txt @@ -0,0 +1,4118 @@ +Class std::__true_type + size=1 align=1 + base size=0 base align=1 +std::__true_type (0x0x7f7583ec7ae0) 0 empty + +Class std::__false_type + size=1 align=1 + base size=0 base align=1 +std::__false_type (0x0x7f7583ec7b40) 0 empty + +Class std::input_iterator_tag + size=1 align=1 + base size=0 base align=1 +std::input_iterator_tag (0x0x7f7583f85780) 0 empty + +Class std::output_iterator_tag + size=1 align=1 + base size=0 base align=1 +std::output_iterator_tag (0x0x7f7583f857e0) 0 empty + +Class std::forward_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::forward_iterator_tag (0x0x7f7583f02958) 0 empty + std::input_iterator_tag (0x0x7f7583f85840) 0 empty + +Class std::bidirectional_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::bidirectional_iterator_tag (0x0x7f7583f029c0) 0 empty + std::forward_iterator_tag (0x0x7f7583f02a28) 0 empty + std::input_iterator_tag (0x0x7f7583f858a0) 0 empty + +Class std::random_access_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::random_access_iterator_tag (0x0x7f7583f02a90) 0 empty + std::bidirectional_iterator_tag (0x0x7f7583f02af8) 0 empty + std::forward_iterator_tag (0x0x7f7583f02b60) 0 empty + std::input_iterator_tag (0x0x7f7583f85900) 0 empty + +Class __gnu_cxx::__ops::_Iter_less_iter + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Iter_less_iter (0x0x7f7583f85de0) 0 empty + +Class __gnu_cxx::__ops::_Iter_less_val + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Iter_less_val (0x0x7f7583f85e40) 0 empty + +Class __gnu_cxx::__ops::_Val_less_iter + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Val_less_iter (0x0x7f7583f85ea0) 0 empty + +Class __gnu_cxx::__ops::_Iter_equal_to_iter + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Iter_equal_to_iter (0x0x7f7583f85f00) 0 empty + +Class __gnu_cxx::__ops::_Iter_equal_to_val + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Iter_equal_to_val (0x0x7f7583f85f60) 0 empty + +Class wait + size=4 align=4 + base size=4 base align=4 +wait (0x0x7f75840199c0) 0 + +Class __locale_struct + size=232 align=8 + base size=232 base align=8 +__locale_struct (0x0x7f7584019c00) 0 + +Class timespec + size=16 align=8 + base size=16 base align=8 +timespec (0x0x7f7584019cc0) 0 + +Class timeval + size=16 align=8 + base size=16 base align=8 +timeval (0x0x7f7584019d20) 0 + +Class pthread_attr_t + size=56 align=8 + base size=56 base align=8 +pthread_attr_t (0x0x7f7584019de0) 0 + +Class __pthread_internal_list + size=16 align=8 + base size=16 base align=8 +__pthread_internal_list (0x0x7f7584019e40) 0 + +Class random_data + size=48 align=8 + base size=48 base align=8 +random_data (0x0x7f7582cc5300) 0 + +Class drand48_data + size=24 align=8 + base size=24 base align=8 +drand48_data (0x0x7f7582cc5360) 0 + +Vtable for std::exception +std::exception::_ZTVSt9exception: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt9exception) +16 (int (*)(...))std::exception::~exception +24 (int (*)(...))std::exception::~exception +32 (int (*)(...))std::exception::what + +Class std::exception + size=8 align=8 + base size=8 base align=8 +std::exception (0x0x7f7582cc53c0) 0 nearly-empty + vptr=((& std::exception::_ZTVSt9exception) + 16u) + +Vtable for std::bad_exception +std::bad_exception::_ZTVSt13bad_exception: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt13bad_exception) +16 (int (*)(...))std::bad_exception::~bad_exception +24 (int (*)(...))std::bad_exception::~bad_exception +32 (int (*)(...))std::bad_exception::what + +Class std::bad_exception + size=8 align=8 + base size=8 base align=8 +std::bad_exception (0x0x7f7583f02e38) 0 nearly-empty + vptr=((& std::bad_exception::_ZTVSt13bad_exception) + 16u) + std::exception (0x0x7f7582cc5420) 0 nearly-empty + primary-for std::bad_exception (0x0x7f7583f02e38) + +Vtable for std::bad_alloc +std::bad_alloc::_ZTVSt9bad_alloc: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt9bad_alloc) +16 (int (*)(...))std::bad_alloc::~bad_alloc +24 (int (*)(...))std::bad_alloc::~bad_alloc +32 (int (*)(...))std::bad_alloc::what + +Class std::bad_alloc + size=8 align=8 + base size=8 base align=8 +std::bad_alloc (0x0x7f7583f02ea0) 0 nearly-empty + vptr=((& std::bad_alloc::_ZTVSt9bad_alloc) + 16u) + std::exception (0x0x7f7582cc5480) 0 nearly-empty + primary-for std::bad_alloc (0x0x7f7583f02ea0) + +Class std::nothrow_t + size=1 align=1 + base size=0 base align=1 +std::nothrow_t (0x0x7f7582cc54e0) 0 empty + +Class qIsNull(double)::U + size=8 align=8 + base size=8 base align=8 +qIsNull(double)::U (0x0x7f7582a5ec00) 0 + +Class qIsNull(float)::U + size=4 align=4 + base size=4 base align=4 +qIsNull(float)::U (0x0x7f7582a5ec60) 0 + +Class QtPrivate::big_ + size=2 align=1 + base size=2 base align=1 +QtPrivate::big_ (0x0x7f7582a5ee40) 0 + +Class QSysInfo + size=1 align=1 + base size=0 base align=1 +QSysInfo (0x0x7f7582b81ea0) 0 empty + +Class QMessageLogContext + size=32 align=8 + base size=32 base align=8 +QMessageLogContext (0x0x7f7582b81f00) 0 + +Class QMessageLogger + size=32 align=8 + base size=32 base align=8 +QMessageLogger (0x0x7f7582b81f60) 0 + +Class QFlag + size=4 align=4 + base size=4 base align=4 +QFlag (0x0x7f7582be9000) 0 + +Class QIncompatibleFlag + size=4 align=4 + base size=4 base align=4 +QIncompatibleFlag (0x0x7f7582be9180) 0 + +Class QAtomicInt + size=4 align=4 + base size=4 base align=4 +QAtomicInt (0x0x7f7582bb2618) 0 + QAtomicInteger (0x0x7f7582bb2680) 0 + QBasicAtomicInteger (0x0x7f7582be9cc0) 0 + +Class QInternal + size=1 align=1 + base size=0 base align=1 +QInternal (0x0x7f75826b1de0) 0 empty + +Class QGenericArgument + size=16 align=8 + base size=16 base align=8 +QGenericArgument (0x0x7f75824ee2a0) 0 + +Class QGenericReturnArgument + size=16 align=8 + base size=16 base align=8 +QGenericReturnArgument (0x0x7f7582724888) 0 + QGenericArgument (0x0x7f75824ee300) 0 + +Class QMetaObject + size=48 align=8 + base size=48 base align=8 +QMetaObject (0x0x7f75824ee480) 0 + +Class QMetaObject::Connection + size=8 align=8 + base size=8 base align=8 +QMetaObject::Connection (0x0x7f75824ee540) 0 + +Class QLatin1Char + size=1 align=1 + base size=1 base align=1 +QLatin1Char (0x0x7f75824ee600) 0 + +Class QChar + size=2 align=2 + base size=2 base align=2 +QChar (0x0x7f75824ee660) 0 + +Class QtPrivate::RefCount + size=4 align=4 + base size=4 base align=4 +QtPrivate::RefCount (0x0x7f75824ee7e0) 0 + +Class QArrayData + size=24 align=8 + base size=24 base align=8 +QArrayData (0x0x7f75824ee8a0) 0 + +Class QtPrivate::QContainerImplHelper + size=1 align=1 + base size=0 base align=1 +QtPrivate::QContainerImplHelper (0x0x7f75824eecc0) 0 empty + +Class lconv + size=96 align=8 + base size=96 base align=8 +lconv (0x0x7f758227a060) 0 + +Vtable for __cxxabiv1::__forced_unwind +__cxxabiv1::__forced_unwind::_ZTVN10__cxxabiv115__forced_unwindE: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN10__cxxabiv115__forced_unwindE) +16 0u +24 0u +32 (int (*)(...))__cxa_pure_virtual + +Class __cxxabiv1::__forced_unwind + size=8 align=8 + base size=8 base align=8 +__cxxabiv1::__forced_unwind (0x0x7f758227a0c0) 0 nearly-empty + vptr=((& __cxxabiv1::__forced_unwind::_ZTVN10__cxxabiv115__forced_unwindE) + 16u) + +Class sched_param + size=4 align=4 + base size=4 base align=4 +sched_param (0x0x7f758227af60) 0 + +Class __sched_param + size=4 align=4 + base size=4 base align=4 +__sched_param (0x0x7f7582342000) 0 + +Class timex + size=208 align=8 + base size=208 base align=8 +timex (0x0x7f75823420c0) 0 + +Class tm + size=56 align=8 + base size=56 base align=8 +tm (0x0x7f7582342120) 0 + +Class itimerspec + size=32 align=8 + base size=32 base align=8 +itimerspec (0x0x7f7582342180) 0 + +Class _pthread_cleanup_buffer + size=32 align=8 + base size=32 base align=8 +_pthread_cleanup_buffer (0x0x7f75823421e0) 0 + +Class __pthread_cleanup_frame + size=24 align=8 + base size=24 base align=8 +__pthread_cleanup_frame (0x0x7f7582342300) 0 + +Class __pthread_cleanup_class + size=24 align=8 + base size=24 base align=8 +__pthread_cleanup_class (0x0x7f7582342360) 0 + +Class std::locale + size=8 align=8 + base size=8 base align=8 +std::locale (0x0x7f7582342c00) 0 + +Vtable for std::locale::facet +std::locale::facet::_ZTVNSt6locale5facetE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt6locale5facetE) +16 (int (*)(...))std::locale::facet::~facet +24 (int (*)(...))std::locale::facet::~facet + +Class std::locale::facet + size=16 align=8 + base size=12 base align=8 +std::locale::facet (0x0x7f7582342c60) 0 + vptr=((& std::locale::facet::_ZTVNSt6locale5facetE) + 16u) + +Class std::locale::id + size=8 align=8 + base size=8 base align=8 +std::locale::id (0x0x7f7582342cc0) 0 + +Class std::locale::_Impl + size=40 align=8 + base size=40 base align=8 +std::locale::_Impl (0x0x7f7582342d20) 0 + +Class std::__cow_string + size=8 align=8 + base size=8 base align=8 +std::__cow_string (0x0x7f7582200120) 0 + +Vtable for std::logic_error +std::logic_error::_ZTVSt11logic_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt11logic_error) +16 (int (*)(...))std::logic_error::~logic_error +24 (int (*)(...))std::logic_error::~logic_error +32 (int (*)(...))std::logic_error::what + +Class std::logic_error + size=16 align=8 + base size=16 base align=8 +std::logic_error (0x0x7f7582337c30) 0 + vptr=((& std::logic_error::_ZTVSt11logic_error) + 16u) + std::exception (0x0x7f75822001e0) 0 nearly-empty + primary-for std::logic_error (0x0x7f7582337c30) + +Vtable for std::domain_error +std::domain_error::_ZTVSt12domain_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt12domain_error) +16 (int (*)(...))std::domain_error::~domain_error +24 (int (*)(...))std::domain_error::~domain_error +32 (int (*)(...))std::logic_error::what + +Class std::domain_error + size=16 align=8 + base size=16 base align=8 +std::domain_error (0x0x7f7582337c98) 0 + vptr=((& std::domain_error::_ZTVSt12domain_error) + 16u) + std::logic_error (0x0x7f7582337d00) 0 + primary-for std::domain_error (0x0x7f7582337c98) + std::exception (0x0x7f7582200240) 0 nearly-empty + primary-for std::logic_error (0x0x7f7582337d00) + +Vtable for std::invalid_argument +std::invalid_argument::_ZTVSt16invalid_argument: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt16invalid_argument) +16 (int (*)(...))std::invalid_argument::~invalid_argument +24 (int (*)(...))std::invalid_argument::~invalid_argument +32 (int (*)(...))std::logic_error::what + +Class std::invalid_argument + size=16 align=8 + base size=16 base align=8 +std::invalid_argument (0x0x7f7582337d68) 0 + vptr=((& std::invalid_argument::_ZTVSt16invalid_argument) + 16u) + std::logic_error (0x0x7f7582337dd0) 0 + primary-for std::invalid_argument (0x0x7f7582337d68) + std::exception (0x0x7f75822002a0) 0 nearly-empty + primary-for std::logic_error (0x0x7f7582337dd0) + +Vtable for std::length_error +std::length_error::_ZTVSt12length_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt12length_error) +16 (int (*)(...))std::length_error::~length_error +24 (int (*)(...))std::length_error::~length_error +32 (int (*)(...))std::logic_error::what + +Class std::length_error + size=16 align=8 + base size=16 base align=8 +std::length_error (0x0x7f7582337e38) 0 + vptr=((& std::length_error::_ZTVSt12length_error) + 16u) + std::logic_error (0x0x7f7582337ea0) 0 + primary-for std::length_error (0x0x7f7582337e38) + std::exception (0x0x7f7582200300) 0 nearly-empty + primary-for std::logic_error (0x0x7f7582337ea0) + +Vtable for std::out_of_range +std::out_of_range::_ZTVSt12out_of_range: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt12out_of_range) +16 (int (*)(...))std::out_of_range::~out_of_range +24 (int (*)(...))std::out_of_range::~out_of_range +32 (int (*)(...))std::logic_error::what + +Class std::out_of_range + size=16 align=8 + base size=16 base align=8 +std::out_of_range (0x0x7f7582337f08) 0 + vptr=((& std::out_of_range::_ZTVSt12out_of_range) + 16u) + std::logic_error (0x0x7f7582337f70) 0 + primary-for std::out_of_range (0x0x7f7582337f08) + std::exception (0x0x7f7582200360) 0 nearly-empty + primary-for std::logic_error (0x0x7f7582337f70) + +Vtable for std::runtime_error +std::runtime_error::_ZTVSt13runtime_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt13runtime_error) +16 (int (*)(...))std::runtime_error::~runtime_error +24 (int (*)(...))std::runtime_error::~runtime_error +32 (int (*)(...))std::runtime_error::what + +Class std::runtime_error + size=16 align=8 + base size=16 base align=8 +std::runtime_error (0x0x7f7582337138) 0 + vptr=((& std::runtime_error::_ZTVSt13runtime_error) + 16u) + std::exception (0x0x7f75822003c0) 0 nearly-empty + primary-for std::runtime_error (0x0x7f7582337138) + +Vtable for std::range_error +std::range_error::_ZTVSt11range_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt11range_error) +16 (int (*)(...))std::range_error::~range_error +24 (int (*)(...))std::range_error::~range_error +32 (int (*)(...))std::runtime_error::what + +Class std::range_error + size=16 align=8 + base size=16 base align=8 +std::range_error (0x0x7f7582337208) 0 + vptr=((& std::range_error::_ZTVSt11range_error) + 16u) + std::runtime_error (0x0x7f7582337270) 0 + primary-for std::range_error (0x0x7f7582337208) + std::exception (0x0x7f7582200420) 0 nearly-empty + primary-for std::runtime_error (0x0x7f7582337270) + +Vtable for std::overflow_error +std::overflow_error::_ZTVSt14overflow_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt14overflow_error) +16 (int (*)(...))std::overflow_error::~overflow_error +24 (int (*)(...))std::overflow_error::~overflow_error +32 (int (*)(...))std::runtime_error::what + +Class std::overflow_error + size=16 align=8 + base size=16 base align=8 +std::overflow_error (0x0x7f75823372d8) 0 + vptr=((& std::overflow_error::_ZTVSt14overflow_error) + 16u) + std::runtime_error (0x0x7f75823373a8) 0 + primary-for std::overflow_error (0x0x7f75823372d8) + std::exception (0x0x7f7582200480) 0 nearly-empty + primary-for std::runtime_error (0x0x7f75823373a8) + +Vtable for std::underflow_error +std::underflow_error::_ZTVSt15underflow_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt15underflow_error) +16 (int (*)(...))std::underflow_error::~underflow_error +24 (int (*)(...))std::underflow_error::~underflow_error +32 (int (*)(...))std::runtime_error::what + +Class std::underflow_error + size=16 align=8 + base size=16 base align=8 +std::underflow_error (0x0x7f75823374e0) 0 + vptr=((& std::underflow_error::_ZTVSt15underflow_error) + 16u) + std::runtime_error (0x0x7f75823375b0) 0 + primary-for std::underflow_error (0x0x7f75823374e0) + std::exception (0x0x7f75822004e0) 0 nearly-empty + primary-for std::runtime_error (0x0x7f75823375b0) + +Class std::ios_base::system_error::error_code + size=16 align=8 + base size=16 base align=8 +std::ios_base::system_error::error_code (0x0x7f7582200600) 0 + +Vtable for std::ios_base::system_error +std::ios_base::system_error::_ZTVNSt8ios_base12system_errorE: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt8ios_base12system_errorE) +16 (int (*)(...))std::ios_base::system_error::~system_error +24 (int (*)(...))std::ios_base::system_error::~system_error +32 (int (*)(...))std::runtime_error::what + +Class std::ios_base::system_error + size=32 align=8 + base size=32 base align=8 +std::ios_base::system_error (0x0x7f7582337958) 0 + vptr=((& std::ios_base::system_error::_ZTVNSt8ios_base12system_errorE) + 16u) + std::runtime_error (0x0x7f7582337a28) 0 + primary-for std::ios_base::system_error (0x0x7f7582337958) + std::exception (0x0x7f75822005a0) 0 nearly-empty + primary-for std::runtime_error (0x0x7f7582337a28) + +Vtable for std::ios_base::failure +std::ios_base::failure::_ZTVNSt8ios_base7failureB5cxx11E: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt8ios_base7failureB5cxx11E) +16 (int (*)(...))std::ios_base::failure::~failure +24 (int (*)(...))std::ios_base::failure::~failure +32 (int (*)(...))std::ios_base::failure::what + +Class std::ios_base::failure + size=32 align=8 + base size=32 base align=8 +std::ios_base::failure (0x0x7f7582249000) 0 + vptr=((& std::ios_base::failure::_ZTVNSt8ios_base7failureB5cxx11E) + 16u) + std::ios_base::system_error (0x0x7f7582249068) 0 + primary-for std::ios_base::failure (0x0x7f7582249000) + std::runtime_error (0x0x7f75822490d0) 0 + primary-for std::ios_base::system_error (0x0x7f7582249068) + std::exception (0x0x7f7582200660) 0 nearly-empty + primary-for std::runtime_error (0x0x7f75822490d0) + +Class std::ios_base::_Callback_list + size=24 align=8 + base size=24 base align=8 +std::ios_base::_Callback_list (0x0x7f75822006c0) 0 + +Class std::ios_base::_Words + size=16 align=8 + base size=16 base align=8 +std::ios_base::_Words (0x0x7f7582200720) 0 + +Class std::ios_base::Init + size=1 align=1 + base size=0 base align=1 +std::ios_base::Init (0x0x7f7582200780) 0 empty + +Vtable for std::ios_base +std::ios_base::_ZTVSt8ios_base: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt8ios_base) +16 (int (*)(...))std::ios_base::~ios_base +24 (int (*)(...))std::ios_base::~ios_base + +Class std::ios_base + size=216 align=8 + base size=216 base align=8 +std::ios_base (0x0x7f7582200540) 0 + vptr=((& std::ios_base::_ZTVSt8ios_base) + 16u) + +Class std::ctype_base + size=1 align=1 + base size=0 base align=1 +std::ctype_base (0x0x7f7582200ea0) 0 empty + +Class std::__num_base + size=1 align=1 + base size=0 base align=1 +std::__num_base (0x0x7f7581f505a0) 0 empty + +VTT for std::basic_ostream +std::basic_ostream::_ZTTSo: 2u entries +0 ((& std::basic_ostream::_ZTVSo) + 24u) +8 ((& std::basic_ostream::_ZTVSo) + 64u) + +VTT for std::basic_ostream +std::basic_ostream::_ZTTSt13basic_ostreamIwSt11char_traitsIwEE: 2u entries +0 ((& std::basic_ostream::_ZTVSt13basic_ostreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_ostream::_ZTVSt13basic_ostreamIwSt11char_traitsIwEE) + 64u) + +VTT for std::basic_istream +std::basic_istream::_ZTTSi: 2u entries +0 ((& std::basic_istream::_ZTVSi) + 24u) +8 ((& std::basic_istream::_ZTVSi) + 64u) + +VTT for std::basic_istream +std::basic_istream::_ZTTSt13basic_istreamIwSt11char_traitsIwEE: 2u entries +0 ((& std::basic_istream::_ZTVSt13basic_istreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_istream::_ZTVSt13basic_istreamIwSt11char_traitsIwEE) + 64u) + +Construction vtable for std::basic_istream (0x0x7f7581a5d750 instance) in std::basic_iostream +std::basic_iostream::_ZTCSd0_Si: 10u entries +0 24u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISi) +24 0u +32 0u +40 18446744073709551592u +48 (int (*)(...))-24 +56 (int (*)(...))(& _ZTISi) +64 0u +72 0u + +Construction vtable for std::basic_ostream (0x0x7f7581a5d820 instance) in std::basic_iostream +std::basic_iostream::_ZTCSd16_So: 10u entries +0 8u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISo) +24 0u +32 0u +40 18446744073709551608u +48 (int (*)(...))-8 +56 (int (*)(...))(& _ZTISo) +64 0u +72 0u + +VTT for std::basic_iostream +std::basic_iostream::_ZTTSd: 7u entries +0 ((& std::basic_iostream::_ZTVSd) + 24u) +8 ((& std::basic_iostream::_ZTCSd0_Si) + 24u) +16 ((& std::basic_iostream::_ZTCSd0_Si) + 64u) +24 ((& std::basic_iostream::_ZTCSd16_So) + 24u) +32 ((& std::basic_iostream::_ZTCSd16_So) + 64u) +40 ((& std::basic_iostream::_ZTVSd) + 104u) +48 ((& std::basic_iostream::_ZTVSd) + 64u) + +Construction vtable for std::basic_istream (0x0x7f7581a5dbc8 instance) in std::basic_iostream +std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E: 10u entries +0 24u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISt13basic_istreamIwSt11char_traitsIwEE) +24 0u +32 0u +40 18446744073709551592u +48 (int (*)(...))-24 +56 (int (*)(...))(& _ZTISt13basic_istreamIwSt11char_traitsIwEE) +64 0u +72 0u + +Construction vtable for std::basic_ostream (0x0x7f7581a5dc98 instance) in std::basic_iostream +std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E: 10u entries +0 8u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISt13basic_ostreamIwSt11char_traitsIwEE) +24 0u +32 0u +40 18446744073709551608u +48 (int (*)(...))-8 +56 (int (*)(...))(& _ZTISt13basic_ostreamIwSt11char_traitsIwEE) +64 0u +72 0u + +VTT for std::basic_iostream +std::basic_iostream::_ZTTSt14basic_iostreamIwSt11char_traitsIwEE: 7u entries +0 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E) + 24u) +16 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E) + 64u) +24 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E) + 24u) +32 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E) + 64u) +40 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 104u) +48 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 64u) + +Class QByteArrayDataPtr + size=8 align=8 + base size=8 base align=8 +QByteArrayDataPtr (0x0x7f7581cd5cc0) 0 + +Class QByteArray + size=8 align=8 + base size=8 base align=8 +QByteArray (0x0x7f7581cd5d20) 0 + +Class QByteRef + size=16 align=8 + base size=12 base align=8 +QByteRef (0x0x7f7581b5f360) 0 + +Class QLatin1String + size=16 align=8 + base size=16 base align=8 +QLatin1String (0x0x7f7581b5f540) 0 + +Class QStringDataPtr + size=8 align=8 + base size=8 base align=8 +QStringDataPtr (0x0x7f7581b5f780) 0 + +Class QString::Null + size=1 align=1 + base size=0 base align=1 +QString::Null (0x0x7f7581b5f840) 0 empty + +Class QString + size=8 align=8 + base size=8 base align=8 +QString (0x0x7f7581b5f7e0) 0 + +Class QCharRef + size=16 align=8 + base size=12 base align=8 +QCharRef (0x0x7f7581b5fea0) 0 + +Class QStringRef + size=16 align=8 + base size=16 base align=8 +QStringRef (0x0x7f75819b72a0) 0 + +Class QtPrivate::QHashCombine + size=1 align=1 + base size=0 base align=1 +QtPrivate::QHashCombine (0x0x7f75819b7660) 0 empty + +Class QtPrivate::QHashCombineCommutative + size=1 align=1 + base size=0 base align=1 +QtPrivate::QHashCombineCommutative (0x0x7f75819b76c0) 0 empty + +Class std::__detail::_List_node_base + size=16 align=8 + base size=16 base align=8 +std::__detail::_List_node_base (0x0x7f75819b7720) 0 + +Class QListData::NotArrayCompatibleLayout + size=1 align=1 + base size=0 base align=1 +QListData::NotArrayCompatibleLayout (0x0x7f75819b7ae0) 0 empty + +Class QListData::NotIndirectLayout + size=1 align=1 + base size=0 base align=1 +QListData::NotIndirectLayout (0x0x7f75819b7b40) 0 empty + +Class QListData::ArrayCompatibleLayout + size=1 align=1 + base size=1 base align=1 +QListData::ArrayCompatibleLayout (0x0x7f7581b4ec98) 0 empty + QListData::NotIndirectLayout (0x0x7f75819b7ba0) 0 empty + +Class QListData::InlineWithPaddingLayout + size=1 align=1 + base size=1 base align=1 +QListData::InlineWithPaddingLayout (0x0x7f75817db310) 0 empty + QListData::NotArrayCompatibleLayout (0x0x7f75819b7c00) 0 empty + QListData::NotIndirectLayout (0x0x7f75819b7c60) 0 empty + +Class QListData::IndirectLayout + size=1 align=1 + base size=1 base align=1 +QListData::IndirectLayout (0x0x7f7581b4ed00) 0 empty + QListData::NotArrayCompatibleLayout (0x0x7f75819b7cc0) 0 empty + +Class QListData::Data + size=24 align=8 + base size=24 base align=8 +QListData::Data (0x0x7f75819b7d20) 0 + +Class QListData + size=8 align=8 + base size=8 base align=8 +QListData (0x0x7f75819b7a80) 0 + +Class QRegExp + size=8 align=8 + base size=8 base align=8 +QRegExp (0x0x7f758184a900) 0 + +Class QStringMatcher::Data + size=272 align=8 + base size=272 base align=8 +QStringMatcher::Data (0x0x7f758184aae0) 0 + +Class QStringMatcher + size=1048 align=8 + base size=1048 base align=8 +QStringMatcher (0x0x7f758184aa80) 0 + +Class QStringList + size=8 align=8 + base size=8 base align=8 +QStringList (0x0x7f758152d068) 0 + QList (0x0x7f758152d0d0) 0 + QListSpecialMethods (0x0x7f758184acc0) 0 empty + +Class QScopedPointerPodDeleter + size=1 align=1 + base size=0 base align=1 +QScopedPointerPodDeleter (0x0x7f758184af00) 0 empty + +Class std::_Bit_reference + size=16 align=8 + base size=16 base align=8 +std::_Bit_reference (0x0x7f758158f600) 0 + +Class std::_Bit_iterator_base + size=16 align=8 + base size=12 base align=8 +std::_Bit_iterator_base (0x0x7f758152dd00) 0 + std::iterator (0x0x7f758158f6c0) 0 empty + +Class std::_Bit_iterator + size=16 align=8 + base size=12 base align=8 +std::_Bit_iterator (0x0x7f758152dd68) 0 + std::_Bit_iterator_base (0x0x7f758152ddd0) 0 + std::iterator (0x0x7f758158f720) 0 empty + +Class std::_Bit_const_iterator + size=16 align=8 + base size=12 base align=8 +std::_Bit_const_iterator (0x0x7f758152de38) 0 + std::_Bit_iterator_base (0x0x7f758152dea0) 0 + std::iterator (0x0x7f758158f780) 0 empty + +Class std::_Rb_tree_node_base + size=32 align=8 + base size=32 base align=8 +std::_Rb_tree_node_base (0x0x7f758158fb40) 0 + +Class QtPrivate::AbstractDebugStreamFunction + size=16 align=8 + base size=16 base align=8 +QtPrivate::AbstractDebugStreamFunction (0x0x7f758109f060) 0 + +Class QtPrivate::AbstractComparatorFunction + size=24 align=8 + base size=24 base align=8 +QtPrivate::AbstractComparatorFunction (0x0x7f758109f120) 0 + +Class QtPrivate::AbstractConverterFunction + size=8 align=8 + base size=8 base align=8 +QtPrivate::AbstractConverterFunction (0x0x7f758109f240) 0 + +Class QMetaType + size=80 align=8 + base size=80 base align=8 +QMetaType (0x0x7f758109f3c0) 0 + +Class QtMetaTypePrivate::VariantData + size=24 align=8 + base size=20 base align=8 +QtMetaTypePrivate::VariantData (0x0x7f758109f660) 0 + +Class QtMetaTypePrivate::VectorBoolElements + size=1 align=1 + base size=0 base align=1 +QtMetaTypePrivate::VectorBoolElements (0x0x7f758109f780) 0 empty + +Class QtMetaTypePrivate::QSequentialIterableImpl + size=104 align=8 + base size=104 base align=8 +QtMetaTypePrivate::QSequentialIterableImpl (0x0x7f75811d2180) 0 + +Class QtMetaTypePrivate::QAssociativeIterableImpl + size=112 align=8 + base size=112 base align=8 +QtMetaTypePrivate::QAssociativeIterableImpl (0x0x7f75811d2540) 0 + +Class QtMetaTypePrivate::QPairVariantInterfaceImpl + size=40 align=8 + base size=40 base align=8 +QtMetaTypePrivate::QPairVariantInterfaceImpl (0x0x7f75811d2780) 0 + +Class QtPrivate::QSlotObjectBase + size=16 align=8 + base size=16 base align=8 +QtPrivate::QSlotObjectBase (0x0x7f7580faa7e0) 0 + +Vtable for QObjectData +QObjectData::_ZTV11QObjectData: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QObjectData) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))__cxa_pure_virtual + +Class QObjectData + size=48 align=8 + base size=48 base align=8 +QObjectData (0x0x7f7580faa960) 0 + vptr=((& QObjectData::_ZTV11QObjectData) + 16u) + +Class QObject::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QObject::QPrivateSignal (0x0x7f7580faab40) 0 empty + +Vtable for QObject +QObject::_ZTV7QObject: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QObject) +16 (int (*)(...))QObject::metaObject +24 (int (*)(...))QObject::qt_metacast +32 (int (*)(...))QObject::qt_metacall +40 (int (*)(...))QObject::~QObject +48 (int (*)(...))QObject::~QObject +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QObject + size=16 align=8 + base size=16 base align=8 +QObject (0x0x7f7580faaae0) 0 + vptr=((& QObject::_ZTV7QObject) + 16u) + +Vtable for QObjectUserData +QObjectUserData::_ZTV15QObjectUserData: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QObjectUserData) +16 (int (*)(...))QObjectUserData::~QObjectUserData +24 (int (*)(...))QObjectUserData::~QObjectUserData + +Class QObjectUserData + size=8 align=8 + base size=8 base align=8 +QObjectUserData (0x0x7f7580faaea0) 0 nearly-empty + vptr=((& QObjectUserData::_ZTV15QObjectUserData) + 16u) + +Class QSignalBlocker + size=16 align=8 + base size=10 base align=8 +QSignalBlocker (0x0x7f7580faaf00) 0 + +Class QAbstractAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractAnimation::QPrivateSignal (0x0x7f7580caa000) 0 empty + +Vtable for QAbstractAnimation +QAbstractAnimation::_ZTV18QAbstractAnimation: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractAnimation) +16 (int (*)(...))QAbstractAnimation::metaObject +24 (int (*)(...))QAbstractAnimation::qt_metacast +32 (int (*)(...))QAbstractAnimation::qt_metacall +40 0u +48 0u +56 (int (*)(...))QAbstractAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QAbstractAnimation + size=16 align=8 + base size=16 base align=8 +QAbstractAnimation (0x0x7f7580ca5000) 0 + vptr=((& QAbstractAnimation::_ZTV18QAbstractAnimation) + 16u) + QObject (0x0x7f7580faaf60) 0 + primary-for QAbstractAnimation (0x0x7f7580ca5000) + +Class QAnimationDriver::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAnimationDriver::QPrivateSignal (0x0x7f7580caa0c0) 0 empty + +Vtable for QAnimationDriver +QAnimationDriver::_ZTV16QAnimationDriver: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QAnimationDriver) +16 (int (*)(...))QAnimationDriver::metaObject +24 (int (*)(...))QAnimationDriver::qt_metacast +32 (int (*)(...))QAnimationDriver::qt_metacall +40 (int (*)(...))QAnimationDriver::~QAnimationDriver +48 (int (*)(...))QAnimationDriver::~QAnimationDriver +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAnimationDriver::advance +120 (int (*)(...))QAnimationDriver::elapsed +128 (int (*)(...))QAnimationDriver::start +136 (int (*)(...))QAnimationDriver::stop + +Class QAnimationDriver + size=16 align=8 + base size=16 base align=8 +QAnimationDriver (0x0x7f7580ca5068) 0 + vptr=((& QAnimationDriver::_ZTV16QAnimationDriver) + 16u) + QObject (0x0x7f7580caa060) 0 + primary-for QAnimationDriver (0x0x7f7580ca5068) + +Class QAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAnimationGroup::QPrivateSignal (0x0x7f7580caa180) 0 empty + +Vtable for QAnimationGroup +QAnimationGroup::_ZTV15QAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QAnimationGroup) +16 (int (*)(...))QAnimationGroup::metaObject +24 (int (*)(...))QAnimationGroup::qt_metacast +32 (int (*)(...))QAnimationGroup::qt_metacall +40 0u +48 0u +56 (int (*)(...))QAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QAnimationGroup + size=16 align=8 + base size=16 base align=8 +QAnimationGroup (0x0x7f7580ca50d0) 0 + vptr=((& QAnimationGroup::_ZTV15QAnimationGroup) + 16u) + QAbstractAnimation (0x0x7f7580ca5138) 0 + primary-for QAnimationGroup (0x0x7f7580ca50d0) + QObject (0x0x7f7580caa120) 0 + primary-for QAbstractAnimation (0x0x7f7580ca5138) + +Class QParallelAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QParallelAnimationGroup::QPrivateSignal (0x0x7f7580caa240) 0 empty + +Vtable for QParallelAnimationGroup +QParallelAnimationGroup::_ZTV23QParallelAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QParallelAnimationGroup) +16 (int (*)(...))QParallelAnimationGroup::metaObject +24 (int (*)(...))QParallelAnimationGroup::qt_metacast +32 (int (*)(...))QParallelAnimationGroup::qt_metacall +40 (int (*)(...))QParallelAnimationGroup::~QParallelAnimationGroup +48 (int (*)(...))QParallelAnimationGroup::~QParallelAnimationGroup +56 (int (*)(...))QParallelAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QParallelAnimationGroup::duration +120 (int (*)(...))QParallelAnimationGroup::updateCurrentTime +128 (int (*)(...))QParallelAnimationGroup::updateState +136 (int (*)(...))QParallelAnimationGroup::updateDirection + +Class QParallelAnimationGroup + size=16 align=8 + base size=16 base align=8 +QParallelAnimationGroup (0x0x7f7580ca51a0) 0 + vptr=((& QParallelAnimationGroup::_ZTV23QParallelAnimationGroup) + 16u) + QAnimationGroup (0x0x7f7580ca5208) 0 + primary-for QParallelAnimationGroup (0x0x7f7580ca51a0) + QAbstractAnimation (0x0x7f7580ca5270) 0 + primary-for QAnimationGroup (0x0x7f7580ca5208) + QObject (0x0x7f7580caa1e0) 0 + primary-for QAbstractAnimation (0x0x7f7580ca5270) + +Class QPauseAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPauseAnimation::QPrivateSignal (0x0x7f7580caa300) 0 empty + +Vtable for QPauseAnimation +QPauseAnimation::_ZTV15QPauseAnimation: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QPauseAnimation) +16 (int (*)(...))QPauseAnimation::metaObject +24 (int (*)(...))QPauseAnimation::qt_metacast +32 (int (*)(...))QPauseAnimation::qt_metacall +40 (int (*)(...))QPauseAnimation::~QPauseAnimation +48 (int (*)(...))QPauseAnimation::~QPauseAnimation +56 (int (*)(...))QPauseAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QPauseAnimation::duration +120 (int (*)(...))QPauseAnimation::updateCurrentTime +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QPauseAnimation + size=16 align=8 + base size=16 base align=8 +QPauseAnimation (0x0x7f7580ca52d8) 0 + vptr=((& QPauseAnimation::_ZTV15QPauseAnimation) + 16u) + QAbstractAnimation (0x0x7f7580ca5340) 0 + primary-for QPauseAnimation (0x0x7f7580ca52d8) + QObject (0x0x7f7580caa2a0) 0 + primary-for QAbstractAnimation (0x0x7f7580ca5340) + +Class QEasingCurve + size=8 align=8 + base size=8 base align=8 +QEasingCurve (0x0x7f7580d78660) 0 + +Class QMapNodeBase + size=24 align=8 + base size=24 base align=8 +QMapNodeBase (0x0x7f7580d787e0) 0 + +Class QMapDataBase + size=40 align=8 + base size=40 base align=8 +QMapDataBase (0x0x7f7580d788a0) 0 + +Class QHashData::Node + size=16 align=8 + base size=16 base align=8 +QHashData::Node (0x0x7f7580d78c60) 0 + +Class QHashData + size=48 align=8 + base size=48 base align=8 +QHashData (0x0x7f7580d78c00) 0 + +Class QHashDummyValue + size=1 align=1 + base size=0 base align=1 +QHashDummyValue (0x0x7f7580d78cc0) 0 empty + +Class QVariant::PrivateShared + size=16 align=8 + base size=12 base align=8 +QVariant::PrivateShared (0x0x7f7580afc720) 0 + +Class QVariant::Private::Data + size=8 align=8 + base size=8 base align=8 +QVariant::Private::Data (0x0x7f7580afc7e0) 0 + +Class QVariant::Private + size=16 align=8 + base size=12 base align=8 +QVariant::Private (0x0x7f7580afc780) 0 + +Class QVariant::Handler + size=72 align=8 + base size=72 base align=8 +QVariant::Handler (0x0x7f7580afc840) 0 + +Class QVariant + size=16 align=8 + base size=16 base align=8 +QVariant (0x0x7f7580afc6c0) 0 + +Class QVariantComparisonHelper + size=8 align=8 + base size=8 base align=8 +QVariantComparisonHelper (0x0x7f7580afcb40) 0 + +Class QSequentialIterable::const_iterator + size=112 align=8 + base size=112 base align=8 +QSequentialIterable::const_iterator (0x0x7f7580afcc00) 0 + +Class QSequentialIterable + size=104 align=8 + base size=104 base align=8 +QSequentialIterable (0x0x7f7580afcba0) 0 + +Class QAssociativeIterable::const_iterator + size=120 align=8 + base size=120 base align=8 +QAssociativeIterable::const_iterator (0x0x7f7580afccc0) 0 + +Class QAssociativeIterable + size=112 align=8 + base size=112 base align=8 +QAssociativeIterable (0x0x7f7580afcc60) 0 + +Class QVariantAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QVariantAnimation::QPrivateSignal (0x0x7f75808c9900) 0 empty + +Vtable for QVariantAnimation +QVariantAnimation::_ZTV17QVariantAnimation: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QVariantAnimation) +16 (int (*)(...))QVariantAnimation::metaObject +24 (int (*)(...))QVariantAnimation::qt_metacast +32 (int (*)(...))QVariantAnimation::qt_metacall +40 (int (*)(...))QVariantAnimation::~QVariantAnimation +48 (int (*)(...))QVariantAnimation::~QVariantAnimation +56 (int (*)(...))QVariantAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QVariantAnimation::duration +120 (int (*)(...))QVariantAnimation::updateCurrentTime +128 (int (*)(...))QVariantAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection +144 (int (*)(...))QVariantAnimation::updateCurrentValue +152 (int (*)(...))QVariantAnimation::interpolated + +Class QVariantAnimation + size=16 align=8 + base size=16 base align=8 +QVariantAnimation (0x0x7f75808f43a8) 0 + vptr=((& QVariantAnimation::_ZTV17QVariantAnimation) + 16u) + QAbstractAnimation (0x0x7f75808f4410) 0 + primary-for QVariantAnimation (0x0x7f75808f43a8) + QObject (0x0x7f75808c98a0) 0 + primary-for QAbstractAnimation (0x0x7f75808f4410) + +Class QPropertyAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPropertyAnimation::QPrivateSignal (0x0x7f75808c99c0) 0 empty + +Vtable for QPropertyAnimation +QPropertyAnimation::_ZTV18QPropertyAnimation: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QPropertyAnimation) +16 (int (*)(...))QPropertyAnimation::metaObject +24 (int (*)(...))QPropertyAnimation::qt_metacast +32 (int (*)(...))QPropertyAnimation::qt_metacall +40 (int (*)(...))QPropertyAnimation::~QPropertyAnimation +48 (int (*)(...))QPropertyAnimation::~QPropertyAnimation +56 (int (*)(...))QPropertyAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QVariantAnimation::duration +120 (int (*)(...))QVariantAnimation::updateCurrentTime +128 (int (*)(...))QPropertyAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection +144 (int (*)(...))QPropertyAnimation::updateCurrentValue +152 (int (*)(...))QVariantAnimation::interpolated + +Class QPropertyAnimation + size=16 align=8 + base size=16 base align=8 +QPropertyAnimation (0x0x7f75808f44e0) 0 + vptr=((& QPropertyAnimation::_ZTV18QPropertyAnimation) + 16u) + QVariantAnimation (0x0x7f75808f4548) 0 + primary-for QPropertyAnimation (0x0x7f75808f44e0) + QAbstractAnimation (0x0x7f75808f45b0) 0 + primary-for QVariantAnimation (0x0x7f75808f4548) + QObject (0x0x7f75808c9960) 0 + primary-for QAbstractAnimation (0x0x7f75808f45b0) + +Class QSequentialAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSequentialAnimationGroup::QPrivateSignal (0x0x7f75808c9a80) 0 empty + +Vtable for QSequentialAnimationGroup +QSequentialAnimationGroup::_ZTV25QSequentialAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI25QSequentialAnimationGroup) +16 (int (*)(...))QSequentialAnimationGroup::metaObject +24 (int (*)(...))QSequentialAnimationGroup::qt_metacast +32 (int (*)(...))QSequentialAnimationGroup::qt_metacall +40 (int (*)(...))QSequentialAnimationGroup::~QSequentialAnimationGroup +48 (int (*)(...))QSequentialAnimationGroup::~QSequentialAnimationGroup +56 (int (*)(...))QSequentialAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSequentialAnimationGroup::duration +120 (int (*)(...))QSequentialAnimationGroup::updateCurrentTime +128 (int (*)(...))QSequentialAnimationGroup::updateState +136 (int (*)(...))QSequentialAnimationGroup::updateDirection + +Class QSequentialAnimationGroup + size=16 align=8 + base size=16 base align=8 +QSequentialAnimationGroup (0x0x7f75808f4618) 0 + vptr=((& QSequentialAnimationGroup::_ZTV25QSequentialAnimationGroup) + 16u) + QAnimationGroup (0x0x7f75808f4680) 0 + primary-for QSequentialAnimationGroup (0x0x7f75808f4618) + QAbstractAnimation (0x0x7f75808f46e8) 0 + primary-for QAnimationGroup (0x0x7f75808f4680) + QObject (0x0x7f75808c9a20) 0 + primary-for QAbstractAnimation (0x0x7f75808f46e8) + +Class QTextCodec::ConverterState + size=32 align=8 + base size=32 base align=8 +QTextCodec::ConverterState (0x0x7f75808c9b40) 0 + +Vtable for QTextCodec +QTextCodec::_ZTV10QTextCodec: 9u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QTextCodec) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))QTextCodec::aliases +32 (int (*)(...))__cxa_pure_virtual +40 (int (*)(...))__cxa_pure_virtual +48 (int (*)(...))__cxa_pure_virtual +56 0u +64 0u + +Class QTextCodec + size=8 align=8 + base size=8 base align=8 +QTextCodec (0x0x7f75808c9ae0) 0 nearly-empty + vptr=((& QTextCodec::_ZTV10QTextCodec) + 16u) + +Class QTextEncoder + size=40 align=8 + base size=40 base align=8 +QTextEncoder (0x0x7f75808c9c60) 0 + +Class QTextDecoder + size=40 align=8 + base size=40 base align=8 +QTextDecoder (0x0x7f75808c9cc0) 0 + +Class QSharedData + size=4 align=4 + base size=4 base align=4 +QSharedData (0x0x7f75808c9d20) 0 + +Class std::__numeric_limits_base + size=1 align=1 + base size=0 base align=1 +std::__numeric_limits_base (0x0x7f75808c9f00) 0 empty + +Class QDate + size=8 align=8 + base size=8 base align=8 +QDate (0x0x7f7580a066c0) 0 + +Class QTime + size=4 align=4 + base size=4 base align=4 +QTime (0x0x7f7580a06840) 0 + +Class QDateTime + size=8 align=8 + base size=8 base align=8 +QDateTime (0x0x7f7580a069c0) 0 + +Class QLibraryInfo + size=1 align=1 + base size=0 base align=1 +QLibraryInfo (0x0x7f7580a06ba0) 0 empty + +Class QIODevice::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QIODevice::QPrivateSignal (0x0x7f7580a06c60) 0 empty + +Vtable for QIODevice +QIODevice::_ZTV9QIODevice: 30u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QIODevice) +16 (int (*)(...))QIODevice::metaObject +24 (int (*)(...))QIODevice::qt_metacast +32 (int (*)(...))QIODevice::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QIODevice::isSequential +120 (int (*)(...))QIODevice::open +128 (int (*)(...))QIODevice::close +136 (int (*)(...))QIODevice::pos +144 (int (*)(...))QIODevice::size +152 (int (*)(...))QIODevice::seek +160 (int (*)(...))QIODevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))__cxa_pure_virtual +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))__cxa_pure_virtual + +Class QIODevice + size=16 align=8 + base size=16 base align=8 +QIODevice (0x0x7f75808f49c0) 0 + vptr=((& QIODevice::_ZTV9QIODevice) + 16u) + QObject (0x0x7f7580a06c00) 0 + primary-for QIODevice (0x0x7f75808f49c0) + +Class QBuffer::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QBuffer::QPrivateSignal (0x0x7f7580a06de0) 0 empty + +Vtable for QBuffer +QBuffer::_ZTV7QBuffer: 30u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QBuffer) +16 (int (*)(...))QBuffer::metaObject +24 (int (*)(...))QBuffer::qt_metacast +32 (int (*)(...))QBuffer::qt_metacall +40 (int (*)(...))QBuffer::~QBuffer +48 (int (*)(...))QBuffer::~QBuffer +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QBuffer::connectNotify +104 (int (*)(...))QBuffer::disconnectNotify +112 (int (*)(...))QIODevice::isSequential +120 (int (*)(...))QBuffer::open +128 (int (*)(...))QBuffer::close +136 (int (*)(...))QBuffer::pos +144 (int (*)(...))QBuffer::size +152 (int (*)(...))QBuffer::seek +160 (int (*)(...))QBuffer::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QBuffer::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QBuffer::readData +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))QBuffer::writeData + +Class QBuffer + size=16 align=8 + base size=16 base align=8 +QBuffer (0x0x7f75808f4af8) 0 + vptr=((& QBuffer::_ZTV7QBuffer) + 16u) + QIODevice (0x0x7f75808f4b60) 0 + primary-for QBuffer (0x0x7f75808f4af8) + QObject (0x0x7f7580a06d80) 0 + primary-for QIODevice (0x0x7f75808f4b60) + +Class QDataStream + size=32 align=8 + base size=32 base align=8 +QDataStream (0x0x7f7580a06e40) 0 + +Class QLocale + size=8 align=8 + base size=8 base align=8 +QLocale (0x0x7f7580a06f00) 0 + +Class _IO_marker + size=24 align=8 + base size=24 base align=8 +_IO_marker (0x0x7f75807fb2a0) 0 + +Class _IO_FILE + size=216 align=8 + base size=216 base align=8 +_IO_FILE (0x0x7f75807fb300) 0 + +Vtable for QTextStream +QTextStream::_ZTV11QTextStream: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTextStream) +16 (int (*)(...))QTextStream::~QTextStream +24 (int (*)(...))QTextStream::~QTextStream + +Class QTextStream + size=16 align=8 + base size=16 base align=8 +QTextStream (0x0x7f75807fb3c0) 0 + vptr=((& QTextStream::_ZTV11QTextStream) + 16u) + +Class QTextStreamManipulator + size=40 align=8 + base size=38 base align=8 +QTextStreamManipulator (0x0x7f75807fb5a0) 0 + +Class QContiguousCacheData + size=24 align=4 + base size=24 base align=4 +QContiguousCacheData (0x0x7f75807fb7e0) 0 + +Class QDebug::Stream + size=80 align=8 + base size=76 base align=8 +QDebug::Stream (0x0x7f75807fbae0) 0 + +Class QDebug + size=8 align=8 + base size=8 base align=8 +QDebug (0x0x7f75807fba80) 0 + +Class QDebugStateSaver + size=8 align=8 + base size=8 base align=8 +QDebugStateSaver (0x0x7f75807fbc60) 0 + +Class QNoDebug + size=1 align=1 + base size=0 base align=1 +QNoDebug (0x0x7f75807fbd20) 0 empty + +Class QFileDevice::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileDevice::QPrivateSignal (0x0x7f75807fbf00) 0 empty + +Vtable for QFileDevice +QFileDevice::_ZTV11QFileDevice: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QFileDevice) +16 (int (*)(...))QFileDevice::metaObject +24 (int (*)(...))QFileDevice::qt_metacast +32 (int (*)(...))QFileDevice::qt_metacall +40 (int (*)(...))QFileDevice::~QFileDevice +48 (int (*)(...))QFileDevice::~QFileDevice +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QIODevice::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFileDevice::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QFileDevice::fileName +248 (int (*)(...))QFileDevice::resize +256 (int (*)(...))QFileDevice::permissions +264 (int (*)(...))QFileDevice::setPermissions + +Class QFileDevice + size=16 align=8 + base size=16 base align=8 +QFileDevice (0x0x7f758055d340) 0 + vptr=((& QFileDevice::_ZTV11QFileDevice) + 16u) + QIODevice (0x0x7f758055d3a8) 0 + primary-for QFileDevice (0x0x7f758055d340) + QObject (0x0x7f75807fbea0) 0 + primary-for QIODevice (0x0x7f758055d3a8) + +Class QFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFile::QPrivateSignal (0x0x7f75806330c0) 0 empty + +Vtable for QFile +QFile::_ZTV5QFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI5QFile) +16 (int (*)(...))QFile::metaObject +24 (int (*)(...))QFile::qt_metacast +32 (int (*)(...))QFile::qt_metacall +40 (int (*)(...))QFile::~QFile +48 (int (*)(...))QFile::~QFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QFile::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFile::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QFile::fileName +248 (int (*)(...))QFile::resize +256 (int (*)(...))QFile::permissions +264 (int (*)(...))QFile::setPermissions + +Class QFile + size=16 align=8 + base size=16 base align=8 +QFile (0x0x7f758055d4e0) 0 + vptr=((& QFile::_ZTV5QFile) + 16u) + QFileDevice (0x0x7f758055d548) 0 + primary-for QFile (0x0x7f758055d4e0) + QIODevice (0x0x7f758055d5b0) 0 + primary-for QFileDevice (0x0x7f758055d548) + QObject (0x0x7f7580633060) 0 + primary-for QIODevice (0x0x7f758055d5b0) + +Class QFileInfo + size=8 align=8 + base size=8 base align=8 +QFileInfo (0x0x7f75806331e0) 0 + +Class QDir + size=8 align=8 + base size=8 base align=8 +QDir (0x0x7f75806334e0) 0 + +Class QDirIterator + size=8 align=8 + base size=8 base align=8 +QDirIterator (0x0x7f7580633840) 0 + +Class QFileSelector::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileSelector::QPrivateSignal (0x0x7f7580633a20) 0 empty + +Vtable for QFileSelector +QFileSelector::_ZTV13QFileSelector: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QFileSelector) +16 (int (*)(...))QFileSelector::metaObject +24 (int (*)(...))QFileSelector::qt_metacast +32 (int (*)(...))QFileSelector::qt_metacall +40 (int (*)(...))QFileSelector::~QFileSelector +48 (int (*)(...))QFileSelector::~QFileSelector +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QFileSelector + size=16 align=8 + base size=16 base align=8 +QFileSelector (0x0x7f758055da90) 0 + vptr=((& QFileSelector::_ZTV13QFileSelector) + 16u) + QObject (0x0x7f75806339c0) 0 + primary-for QFileSelector (0x0x7f758055da90) + +Class QFileSystemWatcher::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileSystemWatcher::QPrivateSignal (0x0x7f7580633ae0) 0 empty + +Vtable for QFileSystemWatcher +QFileSystemWatcher::_ZTV18QFileSystemWatcher: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QFileSystemWatcher) +16 (int (*)(...))QFileSystemWatcher::metaObject +24 (int (*)(...))QFileSystemWatcher::qt_metacast +32 (int (*)(...))QFileSystemWatcher::qt_metacall +40 (int (*)(...))QFileSystemWatcher::~QFileSystemWatcher +48 (int (*)(...))QFileSystemWatcher::~QFileSystemWatcher +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QFileSystemWatcher + size=16 align=8 + base size=16 base align=8 +QFileSystemWatcher (0x0x7f758055daf8) 0 + vptr=((& QFileSystemWatcher::_ZTV18QFileSystemWatcher) + 16u) + QObject (0x0x7f7580633a80) 0 + primary-for QFileSystemWatcher (0x0x7f758055daf8) + +Class QLockFile + size=8 align=8 + base size=8 base align=8 +QLockFile (0x0x7f7580633b40) 0 + +Class QLoggingCategory::AtomicBools + size=4 align=1 + base size=4 base align=1 +QLoggingCategory::AtomicBools (0x0x7f7580633cc0) 0 + +Class QLoggingCategory + size=24 align=8 + base size=24 base align=8 +QLoggingCategory (0x0x7f7580633c60) 0 + +Class QProcessEnvironment + size=8 align=8 + base size=8 base align=8 +QProcessEnvironment (0x0x7f7580633e40) 0 + +Class QProcess::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QProcess::QPrivateSignal (0x0x7f75803880c0) 0 empty + +Vtable for QProcess +QProcess::_ZTV8QProcess: 31u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI8QProcess) +16 (int (*)(...))QProcess::metaObject +24 (int (*)(...))QProcess::qt_metacast +32 (int (*)(...))QProcess::qt_metacall +40 (int (*)(...))QProcess::~QProcess +48 (int (*)(...))QProcess::~QProcess +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QProcess::isSequential +120 (int (*)(...))QProcess::open +128 (int (*)(...))QProcess::close +136 (int (*)(...))QIODevice::pos +144 (int (*)(...))QIODevice::size +152 (int (*)(...))QIODevice::seek +160 (int (*)(...))QProcess::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QProcess::bytesAvailable +184 (int (*)(...))QProcess::bytesToWrite +192 (int (*)(...))QProcess::canReadLine +200 (int (*)(...))QProcess::waitForReadyRead +208 (int (*)(...))QProcess::waitForBytesWritten +216 (int (*)(...))QProcess::readData +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))QProcess::writeData +240 (int (*)(...))QProcess::setupChildProcess + +Class QProcess + size=16 align=8 + base size=16 base align=8 +QProcess (0x0x7f758055dd00) 0 + vptr=((& QProcess::_ZTV8QProcess) + 16u) + QIODevice (0x0x7f758055dd68) 0 + primary-for QProcess (0x0x7f758055dd00) + QObject (0x0x7f7580388060) 0 + primary-for QIODevice (0x0x7f758055dd68) + +Class QResource + size=8 align=8 + base size=8 base align=8 +QResource (0x0x7f7580388120) 0 + +Class QSaveFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSaveFile::QPrivateSignal (0x0x7f75803882a0) 0 empty + +Vtable for QSaveFile +QSaveFile::_ZTV9QSaveFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QSaveFile) +16 (int (*)(...))QSaveFile::metaObject +24 (int (*)(...))QSaveFile::qt_metacast +32 (int (*)(...))QSaveFile::qt_metacall +40 (int (*)(...))QSaveFile::~QSaveFile +48 (int (*)(...))QSaveFile::~QSaveFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QSaveFile::open +128 (int (*)(...))QSaveFile::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFileDevice::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QSaveFile::writeData +240 (int (*)(...))QSaveFile::fileName +248 (int (*)(...))QFileDevice::resize +256 (int (*)(...))QFileDevice::permissions +264 (int (*)(...))QFileDevice::setPermissions + +Class QSaveFile + size=16 align=8 + base size=16 base align=8 +QSaveFile (0x0x7f758055ddd0) 0 + vptr=((& QSaveFile::_ZTV9QSaveFile) + 16u) + QFileDevice (0x0x7f758055de38) 0 + primary-for QSaveFile (0x0x7f758055ddd0) + QIODevice (0x0x7f758055dea0) 0 + primary-for QFileDevice (0x0x7f758055de38) + QObject (0x0x7f7580388240) 0 + primary-for QIODevice (0x0x7f758055dea0) + +Class QSettings::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSettings::QPrivateSignal (0x0x7f7580388360) 0 empty + +Vtable for QSettings +QSettings::_ZTV9QSettings: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QSettings) +16 (int (*)(...))QSettings::metaObject +24 (int (*)(...))QSettings::qt_metacast +32 (int (*)(...))QSettings::qt_metacall +40 (int (*)(...))QSettings::~QSettings +48 (int (*)(...))QSettings::~QSettings +56 (int (*)(...))QSettings::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSettings + size=16 align=8 + base size=16 base align=8 +QSettings (0x0x7f758055df08) 0 + vptr=((& QSettings::_ZTV9QSettings) + 16u) + QObject (0x0x7f7580388300) 0 + primary-for QSettings (0x0x7f758055df08) + +Class QStandardPaths + size=1 align=1 + base size=0 base align=1 +QStandardPaths (0x0x7f75803883c0) 0 empty + +Class QStorageInfo + size=8 align=8 + base size=8 base align=8 +QStorageInfo (0x0x7f75803884e0) 0 + +Class QTemporaryDir + size=8 align=8 + base size=8 base align=8 +QTemporaryDir (0x0x7f75803887e0) 0 + +Class QTemporaryFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTemporaryFile::QPrivateSignal (0x0x7f7580388900) 0 empty + +Vtable for QTemporaryFile +QTemporaryFile::_ZTV14QTemporaryFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI14QTemporaryFile) +16 (int (*)(...))QTemporaryFile::metaObject +24 (int (*)(...))QTemporaryFile::qt_metacast +32 (int (*)(...))QTemporaryFile::qt_metacall +40 (int (*)(...))QTemporaryFile::~QTemporaryFile +48 (int (*)(...))QTemporaryFile::~QTemporaryFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QTemporaryFile::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFile::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QTemporaryFile::fileName +248 (int (*)(...))QFile::resize +256 (int (*)(...))QFile::permissions +264 (int (*)(...))QFile::setPermissions + +Class QTemporaryFile + size=16 align=8 + base size=16 base align=8 +QTemporaryFile (0x0x7f758043b068) 0 + vptr=((& QTemporaryFile::_ZTV14QTemporaryFile) + 16u) + QFile (0x0x7f758043b0d0) 0 + primary-for QTemporaryFile (0x0x7f758043b068) + QFileDevice (0x0x7f758043b138) 0 + primary-for QFile (0x0x7f758043b0d0) + QIODevice (0x0x7f758043b1a0) 0 + primary-for QFileDevice (0x0x7f758043b138) + QObject (0x0x7f75803888a0) 0 + primary-for QIODevice (0x0x7f758043b1a0) + +Class QUrl + size=8 align=8 + base size=8 base align=8 +QUrl (0x0x7f7580388a20) 0 + +Class QUrlQuery + size=8 align=8 + base size=8 base align=8 +QUrlQuery (0x0x7f7580388e40) 0 + +Class QModelIndex + size=24 align=8 + base size=24 base align=8 +QModelIndex (0x0x7f7580144060) 0 + +Class QPersistentModelIndex + size=8 align=8 + base size=8 base align=8 +QPersistentModelIndex (0x0x7f75801441e0) 0 + +Class QAbstractItemModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractItemModel::QPrivateSignal (0x0x7f75801443c0) 0 empty + +Vtable for QAbstractItemModel +QAbstractItemModel::_ZTV18QAbstractItemModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractItemModel) +16 (int (*)(...))QAbstractItemModel::metaObject +24 (int (*)(...))QAbstractItemModel::qt_metacast +32 (int (*)(...))QAbstractItemModel::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractItemModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractItemModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractItemModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractItemModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractItemModel + size=16 align=8 + base size=16 base align=8 +QAbstractItemModel (0x0x7f758043b680) 0 + vptr=((& QAbstractItemModel::_ZTV18QAbstractItemModel) + 16u) + QObject (0x0x7f7580144360) 0 + primary-for QAbstractItemModel (0x0x7f758043b680) + +Class QAbstractTableModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractTableModel::QPrivateSignal (0x0x7f7580144720) 0 empty + +Vtable for QAbstractTableModel +QAbstractTableModel::_ZTV19QAbstractTableModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractTableModel) +16 (int (*)(...))QAbstractTableModel::metaObject +24 (int (*)(...))QAbstractTableModel::qt_metacast +32 (int (*)(...))QAbstractTableModel::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractTableModel::index +120 (int (*)(...))QAbstractTableModel::parent +128 (int (*)(...))QAbstractTableModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractTableModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractTableModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractTableModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractTableModel + size=16 align=8 + base size=16 base align=8 +QAbstractTableModel (0x0x7f758043b888) 0 + vptr=((& QAbstractTableModel::_ZTV19QAbstractTableModel) + 16u) + QAbstractItemModel (0x0x7f758043b8f0) 0 + primary-for QAbstractTableModel (0x0x7f758043b888) + QObject (0x0x7f75801446c0) 0 + primary-for QAbstractItemModel (0x0x7f758043b8f0) + +Class QAbstractListModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractListModel::QPrivateSignal (0x0x7f75801447e0) 0 empty + +Vtable for QAbstractListModel +QAbstractListModel::_ZTV18QAbstractListModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractListModel) +16 (int (*)(...))QAbstractListModel::metaObject +24 (int (*)(...))QAbstractListModel::qt_metacast +32 (int (*)(...))QAbstractListModel::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractListModel::index +120 (int (*)(...))QAbstractListModel::parent +128 (int (*)(...))QAbstractListModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))QAbstractListModel::columnCount +152 (int (*)(...))QAbstractListModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractListModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractListModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractListModel + size=16 align=8 + base size=16 base align=8 +QAbstractListModel (0x0x7f758043b958) 0 + vptr=((& QAbstractListModel::_ZTV18QAbstractListModel) + 16u) + QAbstractItemModel (0x0x7f758043b9c0) 0 + primary-for QAbstractListModel (0x0x7f758043b958) + QObject (0x0x7f7580144780) 0 + primary-for QAbstractItemModel (0x0x7f758043b9c0) + +Class QAbstractProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractProxyModel::QPrivateSignal (0x0x7f7580144ae0) 0 empty + +Vtable for QAbstractProxyModel +QAbstractProxyModel::_ZTV19QAbstractProxyModel: 53u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractProxyModel) +16 (int (*)(...))QAbstractProxyModel::metaObject +24 (int (*)(...))QAbstractProxyModel::qt_metacast +32 (int (*)(...))QAbstractProxyModel::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractProxyModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractProxyModel::hasChildren +160 (int (*)(...))QAbstractProxyModel::data +168 (int (*)(...))QAbstractProxyModel::setData +176 (int (*)(...))QAbstractProxyModel::headerData +184 (int (*)(...))QAbstractProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QAbstractProxyModel::mimeTypes +216 (int (*)(...))QAbstractProxyModel::mimeData +224 (int (*)(...))QAbstractProxyModel::canDropMimeData +232 (int (*)(...))QAbstractProxyModel::dropMimeData +240 (int (*)(...))QAbstractProxyModel::supportedDropActions +248 (int (*)(...))QAbstractProxyModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractProxyModel::fetchMore +312 (int (*)(...))QAbstractProxyModel::canFetchMore +320 (int (*)(...))QAbstractProxyModel::flags +328 (int (*)(...))QAbstractProxyModel::sort +336 (int (*)(...))QAbstractProxyModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QAbstractProxyModel::setSourceModel +392 (int (*)(...))__cxa_pure_virtual +400 (int (*)(...))__cxa_pure_virtual +408 (int (*)(...))QAbstractProxyModel::mapSelectionToSource +416 (int (*)(...))QAbstractProxyModel::mapSelectionFromSource + +Class QAbstractProxyModel + size=16 align=8 + base size=16 base align=8 +QAbstractProxyModel (0x0x7f758043baf8) 0 + vptr=((& QAbstractProxyModel::_ZTV19QAbstractProxyModel) + 16u) + QAbstractItemModel (0x0x7f758043bb60) 0 + primary-for QAbstractProxyModel (0x0x7f758043baf8) + QObject (0x0x7f7580144a80) 0 + primary-for QAbstractItemModel (0x0x7f758043bb60) + +Class QIdentityProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QIdentityProxyModel::QPrivateSignal (0x0x7f7580144ba0) 0 empty + +Vtable for QIdentityProxyModel +QIdentityProxyModel::_ZTV19QIdentityProxyModel: 53u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QIdentityProxyModel) +16 (int (*)(...))QIdentityProxyModel::metaObject +24 (int (*)(...))QIdentityProxyModel::qt_metacast +32 (int (*)(...))QIdentityProxyModel::qt_metacall +40 (int (*)(...))QIdentityProxyModel::~QIdentityProxyModel +48 (int (*)(...))QIdentityProxyModel::~QIdentityProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QIdentityProxyModel::index +120 (int (*)(...))QIdentityProxyModel::parent +128 (int (*)(...))QIdentityProxyModel::sibling +136 (int (*)(...))QIdentityProxyModel::rowCount +144 (int (*)(...))QIdentityProxyModel::columnCount +152 (int (*)(...))QAbstractProxyModel::hasChildren +160 (int (*)(...))QAbstractProxyModel::data +168 (int (*)(...))QAbstractProxyModel::setData +176 (int (*)(...))QIdentityProxyModel::headerData +184 (int (*)(...))QAbstractProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QAbstractProxyModel::mimeTypes +216 (int (*)(...))QAbstractProxyModel::mimeData +224 (int (*)(...))QAbstractProxyModel::canDropMimeData +232 (int (*)(...))QIdentityProxyModel::dropMimeData +240 (int (*)(...))QAbstractProxyModel::supportedDropActions +248 (int (*)(...))QAbstractProxyModel::supportedDragActions +256 (int (*)(...))QIdentityProxyModel::insertRows +264 (int (*)(...))QIdentityProxyModel::insertColumns +272 (int (*)(...))QIdentityProxyModel::removeRows +280 (int (*)(...))QIdentityProxyModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractProxyModel::fetchMore +312 (int (*)(...))QAbstractProxyModel::canFetchMore +320 (int (*)(...))QAbstractProxyModel::flags +328 (int (*)(...))QAbstractProxyModel::sort +336 (int (*)(...))QAbstractProxyModel::buddy +344 (int (*)(...))QIdentityProxyModel::match +352 (int (*)(...))QAbstractProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QIdentityProxyModel::setSourceModel +392 (int (*)(...))QIdentityProxyModel::mapToSource +400 (int (*)(...))QIdentityProxyModel::mapFromSource +408 (int (*)(...))QIdentityProxyModel::mapSelectionToSource +416 (int (*)(...))QIdentityProxyModel::mapSelectionFromSource + +Class QIdentityProxyModel + size=16 align=8 + base size=16 base align=8 +QIdentityProxyModel (0x0x7f758043bbc8) 0 + vptr=((& QIdentityProxyModel::_ZTV19QIdentityProxyModel) + 16u) + QAbstractProxyModel (0x0x7f758043bc30) 0 + primary-for QIdentityProxyModel (0x0x7f758043bbc8) + QAbstractItemModel (0x0x7f758043bc98) 0 + primary-for QAbstractProxyModel (0x0x7f758043bc30) + QObject (0x0x7f7580144b40) 0 + primary-for QAbstractItemModel (0x0x7f758043bc98) + +Class QItemSelectionRange + size=16 align=8 + base size=16 base align=8 +QItemSelectionRange (0x0x7f7580144c00) 0 + +Class QItemSelectionModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QItemSelectionModel::QPrivateSignal (0x0x7f7580144de0) 0 empty + +Vtable for QItemSelectionModel +QItemSelectionModel::_ZTV19QItemSelectionModel: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QItemSelectionModel) +16 (int (*)(...))QItemSelectionModel::metaObject +24 (int (*)(...))QItemSelectionModel::qt_metacast +32 (int (*)(...))QItemSelectionModel::qt_metacall +40 (int (*)(...))QItemSelectionModel::~QItemSelectionModel +48 (int (*)(...))QItemSelectionModel::~QItemSelectionModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QItemSelectionModel::setCurrentIndex +120 (int (*)(...))QItemSelectionModel::select +128 (int (*)(...))QItemSelectionModel::select +136 (int (*)(...))QItemSelectionModel::clear +144 (int (*)(...))QItemSelectionModel::reset +152 (int (*)(...))QItemSelectionModel::clearCurrentIndex + +Class QItemSelectionModel + size=16 align=8 + base size=16 base align=8 +QItemSelectionModel (0x0x7f758043bdd0) 0 + vptr=((& QItemSelectionModel::_ZTV19QItemSelectionModel) + 16u) + QObject (0x0x7f7580144d80) 0 + primary-for QItemSelectionModel (0x0x7f758043bdd0) + +Class QItemSelection + size=8 align=8 + base size=8 base align=8 +QItemSelection (0x0x7f757ff17000) 0 + QList (0x0x7f757ff17068) 0 + QListSpecialMethods (0x0x7f757fef2060) 0 empty + +Class QSortFilterProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSortFilterProxyModel::QPrivateSignal (0x0x7f757fef2480) 0 empty + +Vtable for QSortFilterProxyModel +QSortFilterProxyModel::_ZTV21QSortFilterProxyModel: 56u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QSortFilterProxyModel) +16 (int (*)(...))QSortFilterProxyModel::metaObject +24 (int (*)(...))QSortFilterProxyModel::qt_metacast +32 (int (*)(...))QSortFilterProxyModel::qt_metacall +40 (int (*)(...))QSortFilterProxyModel::~QSortFilterProxyModel +48 (int (*)(...))QSortFilterProxyModel::~QSortFilterProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSortFilterProxyModel::index +120 (int (*)(...))QSortFilterProxyModel::parent +128 (int (*)(...))QSortFilterProxyModel::sibling +136 (int (*)(...))QSortFilterProxyModel::rowCount +144 (int (*)(...))QSortFilterProxyModel::columnCount +152 (int (*)(...))QSortFilterProxyModel::hasChildren +160 (int (*)(...))QSortFilterProxyModel::data +168 (int (*)(...))QSortFilterProxyModel::setData +176 (int (*)(...))QSortFilterProxyModel::headerData +184 (int (*)(...))QSortFilterProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QSortFilterProxyModel::mimeTypes +216 (int (*)(...))QSortFilterProxyModel::mimeData +224 (int (*)(...))QAbstractProxyModel::canDropMimeData +232 (int (*)(...))QSortFilterProxyModel::dropMimeData +240 (int (*)(...))QSortFilterProxyModel::supportedDropActions +248 (int (*)(...))QAbstractProxyModel::supportedDragActions +256 (int (*)(...))QSortFilterProxyModel::insertRows +264 (int (*)(...))QSortFilterProxyModel::insertColumns +272 (int (*)(...))QSortFilterProxyModel::removeRows +280 (int (*)(...))QSortFilterProxyModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QSortFilterProxyModel::fetchMore +312 (int (*)(...))QSortFilterProxyModel::canFetchMore +320 (int (*)(...))QSortFilterProxyModel::flags +328 (int (*)(...))QSortFilterProxyModel::sort +336 (int (*)(...))QSortFilterProxyModel::buddy +344 (int (*)(...))QSortFilterProxyModel::match +352 (int (*)(...))QSortFilterProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QSortFilterProxyModel::setSourceModel +392 (int (*)(...))QSortFilterProxyModel::mapToSource +400 (int (*)(...))QSortFilterProxyModel::mapFromSource +408 (int (*)(...))QSortFilterProxyModel::mapSelectionToSource +416 (int (*)(...))QSortFilterProxyModel::mapSelectionFromSource +424 (int (*)(...))QSortFilterProxyModel::filterAcceptsRow +432 (int (*)(...))QSortFilterProxyModel::filterAcceptsColumn +440 (int (*)(...))QSortFilterProxyModel::lessThan + +Class QSortFilterProxyModel + size=16 align=8 + base size=16 base align=8 +QSortFilterProxyModel (0x0x7f757ff17138) 0 + vptr=((& QSortFilterProxyModel::_ZTV21QSortFilterProxyModel) + 16u) + QAbstractProxyModel (0x0x7f757ff171a0) 0 + primary-for QSortFilterProxyModel (0x0x7f757ff17138) + QAbstractItemModel (0x0x7f757ff17208) 0 + primary-for QAbstractProxyModel (0x0x7f757ff171a0) + QObject (0x0x7f757fef2420) 0 + primary-for QAbstractItemModel (0x0x7f757ff17208) + +Class QStringListModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QStringListModel::QPrivateSignal (0x0x7f757fef2540) 0 empty + +Vtable for QStringListModel +QStringListModel::_ZTV16QStringListModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QStringListModel) +16 (int (*)(...))QStringListModel::metaObject +24 (int (*)(...))QStringListModel::qt_metacast +32 (int (*)(...))QStringListModel::qt_metacall +40 (int (*)(...))QStringListModel::~QStringListModel +48 (int (*)(...))QStringListModel::~QStringListModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractListModel::index +120 (int (*)(...))QAbstractListModel::parent +128 (int (*)(...))QStringListModel::sibling +136 (int (*)(...))QStringListModel::rowCount +144 (int (*)(...))QAbstractListModel::columnCount +152 (int (*)(...))QAbstractListModel::hasChildren +160 (int (*)(...))QStringListModel::data +168 (int (*)(...))QStringListModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractListModel::dropMimeData +240 (int (*)(...))QStringListModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QStringListModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QStringListModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QStringListModel::flags +328 (int (*)(...))QStringListModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QStringListModel + size=24 align=8 + base size=24 base align=8 +QStringListModel (0x0x7f757ff17270) 0 + vptr=((& QStringListModel::_ZTV16QStringListModel) + 16u) + QAbstractListModel (0x0x7f757ff172d8) 0 + primary-for QStringListModel (0x0x7f757ff17270) + QAbstractItemModel (0x0x7f757ff17340) 0 + primary-for QAbstractListModel (0x0x7f757ff172d8) + QObject (0x0x7f757fef24e0) 0 + primary-for QAbstractItemModel (0x0x7f757ff17340) + +Class QJsonValue + size=24 align=8 + base size=20 base align=8 +QJsonValue (0x0x7f757fef25a0) 0 + +Class QJsonValueRef + size=16 align=8 + base size=12 base align=8 +QJsonValueRef (0x0x7f757fef2660) 0 + +Class QJsonValuePtr + size=24 align=8 + base size=24 base align=8 +QJsonValuePtr (0x0x7f757fef2720) 0 + +Class QJsonValueRefPtr + size=16 align=8 + base size=16 base align=8 +QJsonValueRefPtr (0x0x7f757fef2780) 0 + +Class QJsonArray::iterator + size=16 align=8 + base size=12 base align=8 +QJsonArray::iterator (0x0x7f757fef2840) 0 + +Class QJsonArray::const_iterator + size=16 align=8 + base size=12 base align=8 +QJsonArray::const_iterator (0x0x7f757fef28a0) 0 + +Class QJsonArray + size=16 align=8 + base size=16 base align=8 +QJsonArray (0x0x7f757fef27e0) 0 + +Class QJsonParseError + size=8 align=4 + base size=8 base align=4 +QJsonParseError (0x0x7f757fef2900) 0 + +Class QJsonDocument + size=8 align=8 + base size=8 base align=8 +QJsonDocument (0x0x7f757fef2960) 0 + +Class QJsonObject::iterator + size=16 align=8 + base size=12 base align=8 +QJsonObject::iterator (0x0x7f757fef2a20) 0 + +Class QJsonObject::const_iterator + size=16 align=8 + base size=12 base align=8 +QJsonObject::const_iterator (0x0x7f757fef2a80) 0 + +Class QJsonObject + size=16 align=8 + base size=16 base align=8 +QJsonObject (0x0x7f757fef29c0) 0 + +Class QEventLoop::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QEventLoop::QPrivateSignal (0x0x7f757fef2ba0) 0 empty + +Vtable for QEventLoop +QEventLoop::_ZTV10QEventLoop: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QEventLoop) +16 (int (*)(...))QEventLoop::metaObject +24 (int (*)(...))QEventLoop::qt_metacast +32 (int (*)(...))QEventLoop::qt_metacall +40 (int (*)(...))QEventLoop::~QEventLoop +48 (int (*)(...))QEventLoop::~QEventLoop +56 (int (*)(...))QEventLoop::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QEventLoop + size=16 align=8 + base size=16 base align=8 +QEventLoop (0x0x7f757ff173a8) 0 + vptr=((& QEventLoop::_ZTV10QEventLoop) + 16u) + QObject (0x0x7f757fef2b40) 0 + primary-for QEventLoop (0x0x7f757ff173a8) + +Class QEventLoopLocker + size=8 align=8 + base size=8 base align=8 +QEventLoopLocker (0x0x7f757fef2cc0) 0 + +Class QAbstractEventDispatcher::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractEventDispatcher::QPrivateSignal (0x0x7f757fef2d80) 0 empty + +Class QAbstractEventDispatcher::TimerInfo + size=12 align=4 + base size=12 base align=4 +QAbstractEventDispatcher::TimerInfo (0x0x7f757fef2de0) 0 + +Vtable for QAbstractEventDispatcher +QAbstractEventDispatcher::_ZTV24QAbstractEventDispatcher: 28u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI24QAbstractEventDispatcher) +16 (int (*)(...))QAbstractEventDispatcher::metaObject +24 (int (*)(...))QAbstractEventDispatcher::qt_metacast +32 (int (*)(...))QAbstractEventDispatcher::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual +184 (int (*)(...))__cxa_pure_virtual +192 (int (*)(...))__cxa_pure_virtual +200 (int (*)(...))__cxa_pure_virtual +208 (int (*)(...))QAbstractEventDispatcher::startingUp +216 (int (*)(...))QAbstractEventDispatcher::closingDown + +Class QAbstractEventDispatcher + size=16 align=8 + base size=16 base align=8 +QAbstractEventDispatcher (0x0x7f757ff174e0) 0 + vptr=((& QAbstractEventDispatcher::_ZTV24QAbstractEventDispatcher) + 16u) + QObject (0x0x7f757fef2d20) 0 + primary-for QAbstractEventDispatcher (0x0x7f757ff174e0) + +Vtable for QAbstractNativeEventFilter +QAbstractNativeEventFilter::_ZTV26QAbstractNativeEventFilter: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI26QAbstractNativeEventFilter) +16 0u +24 0u +32 (int (*)(...))__cxa_pure_virtual + +Class QAbstractNativeEventFilter + size=16 align=8 + base size=16 base align=8 +QAbstractNativeEventFilter (0x0x7f757fef2e40) 0 + vptr=((& QAbstractNativeEventFilter::_ZTV26QAbstractNativeEventFilter) + 16u) + +Class QBasicTimer + size=4 align=4 + base size=4 base align=4 +QBasicTimer (0x0x7f757fef2ea0) 0 + +Vtable for QEvent +QEvent::_ZTV6QEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QEvent) +16 (int (*)(...))QEvent::~QEvent +24 (int (*)(...))QEvent::~QEvent + +Class QEvent + size=24 align=8 + base size=20 base align=8 +QEvent (0x0x7f757fcc7060) 0 + vptr=((& QEvent::_ZTV6QEvent) + 16u) + +Vtable for QTimerEvent +QTimerEvent::_ZTV11QTimerEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTimerEvent) +16 (int (*)(...))QTimerEvent::~QTimerEvent +24 (int (*)(...))QTimerEvent::~QTimerEvent + +Class QTimerEvent + size=24 align=8 + base size=24 base align=8 +QTimerEvent (0x0x7f757ff175b0) 0 + vptr=((& QTimerEvent::_ZTV11QTimerEvent) + 16u) + QEvent (0x0x7f757fcc70c0) 0 + primary-for QTimerEvent (0x0x7f757ff175b0) + +Vtable for QChildEvent +QChildEvent::_ZTV11QChildEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QChildEvent) +16 (int (*)(...))QChildEvent::~QChildEvent +24 (int (*)(...))QChildEvent::~QChildEvent + +Class QChildEvent + size=32 align=8 + base size=32 base align=8 +QChildEvent (0x0x7f757ff17618) 0 + vptr=((& QChildEvent::_ZTV11QChildEvent) + 16u) + QEvent (0x0x7f757fcc7120) 0 + primary-for QChildEvent (0x0x7f757ff17618) + +Vtable for QDynamicPropertyChangeEvent +QDynamicPropertyChangeEvent::_ZTV27QDynamicPropertyChangeEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI27QDynamicPropertyChangeEvent) +16 (int (*)(...))QDynamicPropertyChangeEvent::~QDynamicPropertyChangeEvent +24 (int (*)(...))QDynamicPropertyChangeEvent::~QDynamicPropertyChangeEvent + +Class QDynamicPropertyChangeEvent + size=32 align=8 + base size=32 base align=8 +QDynamicPropertyChangeEvent (0x0x7f757ff17680) 0 + vptr=((& QDynamicPropertyChangeEvent::_ZTV27QDynamicPropertyChangeEvent) + 16u) + QEvent (0x0x7f757fcc7180) 0 + primary-for QDynamicPropertyChangeEvent (0x0x7f757ff17680) + +Vtable for QDeferredDeleteEvent +QDeferredDeleteEvent::_ZTV20QDeferredDeleteEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI20QDeferredDeleteEvent) +16 (int (*)(...))QDeferredDeleteEvent::~QDeferredDeleteEvent +24 (int (*)(...))QDeferredDeleteEvent::~QDeferredDeleteEvent + +Class QDeferredDeleteEvent + size=24 align=8 + base size=24 base align=8 +QDeferredDeleteEvent (0x0x7f757ff176e8) 0 + vptr=((& QDeferredDeleteEvent::_ZTV20QDeferredDeleteEvent) + 16u) + QEvent (0x0x7f757fcc71e0) 0 + primary-for QDeferredDeleteEvent (0x0x7f757ff176e8) + +Class QCoreApplication::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QCoreApplication::QPrivateSignal (0x0x7f757fcc72a0) 0 empty + +Vtable for QCoreApplication +QCoreApplication::_ZTV16QCoreApplication: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QCoreApplication) +16 (int (*)(...))QCoreApplication::metaObject +24 (int (*)(...))QCoreApplication::qt_metacast +32 (int (*)(...))QCoreApplication::qt_metacall +40 (int (*)(...))QCoreApplication::~QCoreApplication +48 (int (*)(...))QCoreApplication::~QCoreApplication +56 (int (*)(...))QCoreApplication::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QCoreApplication::notify +120 (int (*)(...))QCoreApplication::compressEvent + +Class QCoreApplication + size=16 align=8 + base size=16 base align=8 +QCoreApplication (0x0x7f757ff17750) 0 + vptr=((& QCoreApplication::_ZTV16QCoreApplication) + 16u) + QObject (0x0x7f757fcc7240) 0 + primary-for QCoreApplication (0x0x7f757ff17750) + +Class __exception + size=40 align=8 + base size=40 base align=8 +__exception (0x0x7f757fcc7300) 0 + +Class QMetaMethod + size=16 align=8 + base size=12 base align=8 +QMetaMethod (0x0x7f757fcc7480) 0 + +Class QMetaEnum + size=16 align=8 + base size=12 base align=8 +QMetaEnum (0x0x7f757fcc7600) 0 + +Class QMetaProperty + size=32 align=8 + base size=32 base align=8 +QMetaProperty (0x0x7f757fcc7780) 0 + +Class QMetaClassInfo + size=16 align=8 + base size=12 base align=8 +QMetaClassInfo (0x0x7f757fcc77e0) 0 + +Class QMimeData::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QMimeData::QPrivateSignal (0x0x7f757fcc79c0) 0 empty + +Vtable for QMimeData +QMimeData::_ZTV9QMimeData: 17u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QMimeData) +16 (int (*)(...))QMimeData::metaObject +24 (int (*)(...))QMimeData::qt_metacast +32 (int (*)(...))QMimeData::qt_metacall +40 (int (*)(...))QMimeData::~QMimeData +48 (int (*)(...))QMimeData::~QMimeData +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QMimeData::hasFormat +120 (int (*)(...))QMimeData::formats +128 (int (*)(...))QMimeData::retrieveData + +Class QMimeData + size=16 align=8 + base size=16 base align=8 +QMimeData (0x0x7f757ff178f0) 0 + vptr=((& QMimeData::_ZTV9QMimeData) + 16u) + QObject (0x0x7f757fcc7960) 0 + primary-for QMimeData (0x0x7f757ff178f0) + +Class QObjectCleanupHandler::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QObjectCleanupHandler::QPrivateSignal (0x0x7f757fcc7a80) 0 empty + +Vtable for QObjectCleanupHandler +QObjectCleanupHandler::_ZTV21QObjectCleanupHandler: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QObjectCleanupHandler) +16 (int (*)(...))QObjectCleanupHandler::metaObject +24 (int (*)(...))QObjectCleanupHandler::qt_metacast +32 (int (*)(...))QObjectCleanupHandler::qt_metacall +40 (int (*)(...))QObjectCleanupHandler::~QObjectCleanupHandler +48 (int (*)(...))QObjectCleanupHandler::~QObjectCleanupHandler +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QObjectCleanupHandler + size=24 align=8 + base size=24 base align=8 +QObjectCleanupHandler (0x0x7f757ff17958) 0 + vptr=((& QObjectCleanupHandler::_ZTV21QObjectCleanupHandler) + 16u) + QObject (0x0x7f757fcc7a20) 0 + primary-for QObjectCleanupHandler (0x0x7f757ff17958) + +Class QtSharedPointer::NormalDeleter + size=1 align=1 + base size=0 base align=1 +QtSharedPointer::NormalDeleter (0x0x7f757fcc7ae0) 0 empty + +Class QtSharedPointer::ExternalRefCountData + size=16 align=8 + base size=16 base align=8 +QtSharedPointer::ExternalRefCountData (0x0x7f757fcc7c60) 0 + +Class QSharedMemory::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSharedMemory::QPrivateSignal (0x0x7f757fad12a0) 0 empty + +Vtable for QSharedMemory +QSharedMemory::_ZTV13QSharedMemory: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QSharedMemory) +16 (int (*)(...))QSharedMemory::metaObject +24 (int (*)(...))QSharedMemory::qt_metacast +32 (int (*)(...))QSharedMemory::qt_metacall +40 (int (*)(...))QSharedMemory::~QSharedMemory +48 (int (*)(...))QSharedMemory::~QSharedMemory +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSharedMemory + size=16 align=8 + base size=16 base align=8 +QSharedMemory (0x0x7f757ff17d68) 0 + vptr=((& QSharedMemory::_ZTV13QSharedMemory) + 16u) + QObject (0x0x7f757fad1240) 0 + primary-for QSharedMemory (0x0x7f757ff17d68) + +Class QSignalMapper::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSignalMapper::QPrivateSignal (0x0x7f757fad1360) 0 empty + +Vtable for QSignalMapper +QSignalMapper::_ZTV13QSignalMapper: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QSignalMapper) +16 (int (*)(...))QSignalMapper::metaObject +24 (int (*)(...))QSignalMapper::qt_metacast +32 (int (*)(...))QSignalMapper::qt_metacall +40 (int (*)(...))QSignalMapper::~QSignalMapper +48 (int (*)(...))QSignalMapper::~QSignalMapper +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSignalMapper + size=16 align=8 + base size=16 base align=8 +QSignalMapper (0x0x7f757ff17dd0) 0 + vptr=((& QSignalMapper::_ZTV13QSignalMapper) + 16u) + QObject (0x0x7f757fad1300) 0 + primary-for QSignalMapper (0x0x7f757ff17dd0) + +Class QSocketNotifier::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSocketNotifier::QPrivateSignal (0x0x7f757fad1420) 0 empty + +Vtable for QSocketNotifier +QSocketNotifier::_ZTV15QSocketNotifier: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QSocketNotifier) +16 (int (*)(...))QSocketNotifier::metaObject +24 (int (*)(...))QSocketNotifier::qt_metacast +32 (int (*)(...))QSocketNotifier::qt_metacall +40 (int (*)(...))QSocketNotifier::~QSocketNotifier +48 (int (*)(...))QSocketNotifier::~QSocketNotifier +56 (int (*)(...))QSocketNotifier::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSocketNotifier + size=16 align=8 + base size=16 base align=8 +QSocketNotifier (0x0x7f757ff17e38) 0 + vptr=((& QSocketNotifier::_ZTV15QSocketNotifier) + 16u) + QObject (0x0x7f757fad13c0) 0 + primary-for QSocketNotifier (0x0x7f757ff17e38) + +Class QSystemSemaphore + size=8 align=8 + base size=8 base align=8 +QSystemSemaphore (0x0x7f757fad1480) 0 + +Class QTimer::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTimer::QPrivateSignal (0x0x7f757fad15a0) 0 empty + +Vtable for QTimer +QTimer::_ZTV6QTimer: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QTimer) +16 (int (*)(...))QTimer::metaObject +24 (int (*)(...))QTimer::qt_metacast +32 (int (*)(...))QTimer::qt_metacall +40 (int (*)(...))QTimer::~QTimer +48 (int (*)(...))QTimer::~QTimer +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QTimer::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QTimer + size=32 align=8 + base size=29 base align=8 +QTimer (0x0x7f757ff17ea0) 0 + vptr=((& QTimer::_ZTV6QTimer) + 16u) + QObject (0x0x7f757fad1540) 0 + primary-for QTimer (0x0x7f757ff17ea0) + +Class QTranslator::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTranslator::QPrivateSignal (0x0x7f757fad1720) 0 empty + +Vtable for QTranslator +QTranslator::_ZTV11QTranslator: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTranslator) +16 (int (*)(...))QTranslator::metaObject +24 (int (*)(...))QTranslator::qt_metacast +32 (int (*)(...))QTranslator::qt_metacall +40 (int (*)(...))QTranslator::~QTranslator +48 (int (*)(...))QTranslator::~QTranslator +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QTranslator::translate +120 (int (*)(...))QTranslator::isEmpty + +Class QTranslator + size=16 align=8 + base size=16 base align=8 +QTranslator (0x0x7f757ff17f70) 0 + vptr=((& QTranslator::_ZTV11QTranslator) + 16u) + QObject (0x0x7f757fad16c0) 0 + primary-for QTranslator (0x0x7f757ff17f70) + +Class QMimeType + size=8 align=8 + base size=8 base align=8 +QMimeType (0x0x7f757fad1780) 0 + +Class QMimeDatabase + size=8 align=8 + base size=8 base align=8 +QMimeDatabase (0x0x7f757fad1960) 0 + +Vtable for QFactoryInterface +QFactoryInterface::_ZTV17QFactoryInterface: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QFactoryInterface) +16 0u +24 0u +32 (int (*)(...))__cxa_pure_virtual + +Class QFactoryInterface + size=8 align=8 + base size=8 base align=8 +QFactoryInterface (0x0x7f757fad19c0) 0 nearly-empty + vptr=((& QFactoryInterface::_ZTV17QFactoryInterface) + 16u) + +Class QLibrary::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QLibrary::QPrivateSignal (0x0x7f757fad1ae0) 0 empty + +Vtable for QLibrary +QLibrary::_ZTV8QLibrary: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI8QLibrary) +16 (int (*)(...))QLibrary::metaObject +24 (int (*)(...))QLibrary::qt_metacast +32 (int (*)(...))QLibrary::qt_metacall +40 (int (*)(...))QLibrary::~QLibrary +48 (int (*)(...))QLibrary::~QLibrary +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QLibrary + size=32 align=8 + base size=25 base align=8 +QLibrary (0x0x7f757ff17c98) 0 + vptr=((& QLibrary::_ZTV8QLibrary) + 16u) + QObject (0x0x7f757fad1a80) 0 + primary-for QLibrary (0x0x7f757ff17c98) + +Class QStaticPlugin + size=16 align=8 + base size=16 base align=8 +QStaticPlugin (0x0x7f757fad1c00) 0 + +Class QPluginLoader::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPluginLoader::QPrivateSignal (0x0x7f757fad1de0) 0 empty + +Vtable for QPluginLoader +QPluginLoader::_ZTV13QPluginLoader: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QPluginLoader) +16 (int (*)(...))QPluginLoader::metaObject +24 (int (*)(...))QPluginLoader::qt_metacast +32 (int (*)(...))QPluginLoader::qt_metacall +40 (int (*)(...))QPluginLoader::~QPluginLoader +48 (int (*)(...))QPluginLoader::~QPluginLoader +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QPluginLoader + size=32 align=8 + base size=25 base align=8 +QPluginLoader (0x0x7f757fbab0d0) 0 + vptr=((& QPluginLoader::_ZTV13QPluginLoader) + 16u) + QObject (0x0x7f757fad1d80) 0 + primary-for QPluginLoader (0x0x7f757fbab0d0) + +Class QUuid + size=16 align=4 + base size=16 base align=4 +QUuid (0x0x7f757fad1e40) 0 + +Class QAbstractState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractState::QPrivateSignal (0x0x7f757fbf7060) 0 empty + +Vtable for QAbstractState +QAbstractState::_ZTV14QAbstractState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI14QAbstractState) +16 (int (*)(...))QAbstractState::metaObject +24 (int (*)(...))QAbstractState::qt_metacast +32 (int (*)(...))QAbstractState::qt_metacall +40 0u +48 0u +56 (int (*)(...))QAbstractState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QAbstractState + size=16 align=8 + base size=16 base align=8 +QAbstractState (0x0x7f757fbab1a0) 0 + vptr=((& QAbstractState::_ZTV14QAbstractState) + 16u) + QObject (0x0x7f757fbf7000) 0 + primary-for QAbstractState (0x0x7f757fbab1a0) + +Class QAbstractTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractTransition::QPrivateSignal (0x0x7f757fbf7120) 0 empty + +Vtable for QAbstractTransition +QAbstractTransition::_ZTV19QAbstractTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractTransition) +16 (int (*)(...))QAbstractTransition::metaObject +24 (int (*)(...))QAbstractTransition::qt_metacast +32 (int (*)(...))QAbstractTransition::qt_metacall +40 0u +48 0u +56 (int (*)(...))QAbstractTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QAbstractTransition + size=16 align=8 + base size=16 base align=8 +QAbstractTransition (0x0x7f757fbab208) 0 + vptr=((& QAbstractTransition::_ZTV19QAbstractTransition) + 16u) + QObject (0x0x7f757fbf70c0) 0 + primary-for QAbstractTransition (0x0x7f757fbab208) + +Class QEventTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QEventTransition::QPrivateSignal (0x0x7f757fbf71e0) 0 empty + +Vtable for QEventTransition +QEventTransition::_ZTV16QEventTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QEventTransition) +16 (int (*)(...))QEventTransition::metaObject +24 (int (*)(...))QEventTransition::qt_metacast +32 (int (*)(...))QEventTransition::qt_metacall +40 (int (*)(...))QEventTransition::~QEventTransition +48 (int (*)(...))QEventTransition::~QEventTransition +56 (int (*)(...))QEventTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QEventTransition::eventTest +120 (int (*)(...))QEventTransition::onTransition + +Class QEventTransition + size=16 align=8 + base size=16 base align=8 +QEventTransition (0x0x7f757fbab270) 0 + vptr=((& QEventTransition::_ZTV16QEventTransition) + 16u) + QAbstractTransition (0x0x7f757fbab2d8) 0 + primary-for QEventTransition (0x0x7f757fbab270) + QObject (0x0x7f757fbf7180) 0 + primary-for QAbstractTransition (0x0x7f757fbab2d8) + +Class QFinalState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFinalState::QPrivateSignal (0x0x7f757fbf72a0) 0 empty + +Vtable for QFinalState +QFinalState::_ZTV11QFinalState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QFinalState) +16 (int (*)(...))QFinalState::metaObject +24 (int (*)(...))QFinalState::qt_metacast +32 (int (*)(...))QFinalState::qt_metacall +40 (int (*)(...))QFinalState::~QFinalState +48 (int (*)(...))QFinalState::~QFinalState +56 (int (*)(...))QFinalState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFinalState::onEntry +120 (int (*)(...))QFinalState::onExit + +Class QFinalState + size=16 align=8 + base size=16 base align=8 +QFinalState (0x0x7f757fbab340) 0 + vptr=((& QFinalState::_ZTV11QFinalState) + 16u) + QAbstractState (0x0x7f757fbab3a8) 0 + primary-for QFinalState (0x0x7f757fbab340) + QObject (0x0x7f757fbf7240) 0 + primary-for QAbstractState (0x0x7f757fbab3a8) + +Class QHistoryState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QHistoryState::QPrivateSignal (0x0x7f757fbf7360) 0 empty + +Vtable for QHistoryState +QHistoryState::_ZTV13QHistoryState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QHistoryState) +16 (int (*)(...))QHistoryState::metaObject +24 (int (*)(...))QHistoryState::qt_metacast +32 (int (*)(...))QHistoryState::qt_metacall +40 (int (*)(...))QHistoryState::~QHistoryState +48 (int (*)(...))QHistoryState::~QHistoryState +56 (int (*)(...))QHistoryState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QHistoryState::onEntry +120 (int (*)(...))QHistoryState::onExit + +Class QHistoryState + size=16 align=8 + base size=16 base align=8 +QHistoryState (0x0x7f757fbab410) 0 + vptr=((& QHistoryState::_ZTV13QHistoryState) + 16u) + QAbstractState (0x0x7f757fbab478) 0 + primary-for QHistoryState (0x0x7f757fbab410) + QObject (0x0x7f757fbf7300) 0 + primary-for QAbstractState (0x0x7f757fbab478) + +Class QSignalTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSignalTransition::QPrivateSignal (0x0x7f757fbf7420) 0 empty + +Vtable for QSignalTransition +QSignalTransition::_ZTV17QSignalTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QSignalTransition) +16 (int (*)(...))QSignalTransition::metaObject +24 (int (*)(...))QSignalTransition::qt_metacast +32 (int (*)(...))QSignalTransition::qt_metacall +40 (int (*)(...))QSignalTransition::~QSignalTransition +48 (int (*)(...))QSignalTransition::~QSignalTransition +56 (int (*)(...))QSignalTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSignalTransition::eventTest +120 (int (*)(...))QSignalTransition::onTransition + +Class QSignalTransition + size=16 align=8 + base size=16 base align=8 +QSignalTransition (0x0x7f757fbab4e0) 0 + vptr=((& QSignalTransition::_ZTV17QSignalTransition) + 16u) + QAbstractTransition (0x0x7f757fbab548) 0 + primary-for QSignalTransition (0x0x7f757fbab4e0) + QObject (0x0x7f757fbf73c0) 0 + primary-for QAbstractTransition (0x0x7f757fbab548) + +Class QState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QState::QPrivateSignal (0x0x7f757fbf74e0) 0 empty + +Vtable for QState +QState::_ZTV6QState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QState) +16 (int (*)(...))QState::metaObject +24 (int (*)(...))QState::qt_metacast +32 (int (*)(...))QState::qt_metacall +40 (int (*)(...))QState::~QState +48 (int (*)(...))QState::~QState +56 (int (*)(...))QState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QState::onEntry +120 (int (*)(...))QState::onExit + +Class QState + size=16 align=8 + base size=16 base align=8 +QState (0x0x7f757fbab5b0) 0 + vptr=((& QState::_ZTV6QState) + 16u) + QAbstractState (0x0x7f757fbab618) 0 + primary-for QState (0x0x7f757fbab5b0) + QObject (0x0x7f757fbf7480) 0 + primary-for QAbstractState (0x0x7f757fbab618) + +Class QStateMachine::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QStateMachine::QPrivateSignal (0x0x7f757fbf7600) 0 empty + +Vtable for QStateMachine::SignalEvent +QStateMachine::SignalEvent::_ZTVN13QStateMachine11SignalEventE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN13QStateMachine11SignalEventE) +16 (int (*)(...))QStateMachine::SignalEvent::~SignalEvent +24 (int (*)(...))QStateMachine::SignalEvent::~SignalEvent + +Class QStateMachine::SignalEvent + size=48 align=8 + base size=48 base align=8 +QStateMachine::SignalEvent (0x0x7f757fbab7b8) 0 + vptr=((& QStateMachine::SignalEvent::_ZTVN13QStateMachine11SignalEventE) + 16u) + QEvent (0x0x7f757fbf7660) 0 + primary-for QStateMachine::SignalEvent (0x0x7f757fbab7b8) + +Vtable for QStateMachine::WrappedEvent +QStateMachine::WrappedEvent::_ZTVN13QStateMachine12WrappedEventE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN13QStateMachine12WrappedEventE) +16 (int (*)(...))QStateMachine::WrappedEvent::~WrappedEvent +24 (int (*)(...))QStateMachine::WrappedEvent::~WrappedEvent + +Class QStateMachine::WrappedEvent + size=40 align=8 + base size=40 base align=8 +QStateMachine::WrappedEvent (0x0x7f757fbab820) 0 + vptr=((& QStateMachine::WrappedEvent::_ZTVN13QStateMachine12WrappedEventE) + 16u) + QEvent (0x0x7f757fbf76c0) 0 + primary-for QStateMachine::WrappedEvent (0x0x7f757fbab820) + +Vtable for QStateMachine +QStateMachine::_ZTV13QStateMachine: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QStateMachine) +16 (int (*)(...))QStateMachine::metaObject +24 (int (*)(...))QStateMachine::qt_metacast +32 (int (*)(...))QStateMachine::qt_metacall +40 (int (*)(...))QStateMachine::~QStateMachine +48 (int (*)(...))QStateMachine::~QStateMachine +56 (int (*)(...))QStateMachine::event +64 (int (*)(...))QStateMachine::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QStateMachine::onEntry +120 (int (*)(...))QStateMachine::onExit +128 (int (*)(...))QStateMachine::beginSelectTransitions +136 (int (*)(...))QStateMachine::endSelectTransitions +144 (int (*)(...))QStateMachine::beginMicrostep +152 (int (*)(...))QStateMachine::endMicrostep + +Class QStateMachine + size=16 align=8 + base size=16 base align=8 +QStateMachine (0x0x7f757fbab680) 0 + vptr=((& QStateMachine::_ZTV13QStateMachine) + 16u) + QState (0x0x7f757fbab6e8) 0 + primary-for QStateMachine (0x0x7f757fbab680) + QAbstractState (0x0x7f757fbab750) 0 + primary-for QState (0x0x7f757fbab6e8) + QObject (0x0x7f757fbf75a0) 0 + primary-for QAbstractState (0x0x7f757fbab750) + +Vtable for QException +QException::_ZTV10QException: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QException) +16 (int (*)(...))QException::~QException +24 (int (*)(...))QException::~QException +32 (int (*)(...))std::exception::what +40 (int (*)(...))QException::raise +48 (int (*)(...))QException::clone + +Class QException + size=8 align=8 + base size=8 base align=8 +QException (0x0x7f757fbab888) 0 nearly-empty + vptr=((& QException::_ZTV10QException) + 16u) + std::exception (0x0x7f757fbf7720) 0 nearly-empty + primary-for QException (0x0x7f757fbab888) + +Vtable for QUnhandledException +QUnhandledException::_ZTV19QUnhandledException: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QUnhandledException) +16 (int (*)(...))QUnhandledException::~QUnhandledException +24 (int (*)(...))QUnhandledException::~QUnhandledException +32 (int (*)(...))std::exception::what +40 (int (*)(...))QUnhandledException::raise +48 (int (*)(...))QUnhandledException::clone + +Class QUnhandledException + size=8 align=8 + base size=8 base align=8 +QUnhandledException (0x0x7f757fbab8f0) 0 nearly-empty + vptr=((& QUnhandledException::_ZTV19QUnhandledException) + 16u) + QException (0x0x7f757fbab958) 0 nearly-empty + primary-for QUnhandledException (0x0x7f757fbab8f0) + std::exception (0x0x7f757fbf7780) 0 nearly-empty + primary-for QException (0x0x7f757fbab958) + +Class QtPrivate::ExceptionHolder + size=8 align=8 + base size=8 base align=8 +QtPrivate::ExceptionHolder (0x0x7f757fbf77e0) 0 + +Class QtPrivate::ExceptionStore + size=8 align=8 + base size=8 base align=8 +QtPrivate::ExceptionStore (0x0x7f757fbf78a0) 0 + +Vtable for QRunnable +QRunnable::_ZTV9QRunnable: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QRunnable) +16 (int (*)(...))__cxa_pure_virtual +24 0u +32 0u + +Class QRunnable + size=16 align=8 + base size=12 base align=8 +QRunnable (0x0x7f757fbf7900) 0 + vptr=((& QRunnable::_ZTV9QRunnable) + 16u) + +Class QBasicMutex + size=8 align=8 + base size=8 base align=8 +QBasicMutex (0x0x7f757fbf7960) 0 + +Class QMutex + size=8 align=8 + base size=8 base align=8 +QMutex (0x0x7f757fbabaf8) 0 + QBasicMutex (0x0x7f757fbf7ae0) 0 + +Class QMutexLocker + size=8 align=8 + base size=8 base align=8 +QMutexLocker (0x0x7f757fbf7b40) 0 + +Class QtPrivate::ResultItem + size=16 align=8 + base size=16 base align=8 +QtPrivate::ResultItem (0x0x7f757fbf7c00) 0 + +Class QtPrivate::ResultIteratorBase + size=16 align=8 + base size=12 base align=8 +QtPrivate::ResultIteratorBase (0x0x7f757fbf7c60) 0 + +Vtable for QtPrivate::ResultStoreBase +QtPrivate::ResultStoreBase::_ZTVN9QtPrivate15ResultStoreBaseE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN9QtPrivate15ResultStoreBaseE) +16 (int (*)(...))QtPrivate::ResultStoreBase::~ResultStoreBase +24 (int (*)(...))QtPrivate::ResultStoreBase::~ResultStoreBase + +Class QtPrivate::ResultStoreBase + size=48 align=8 + base size=44 base align=8 +QtPrivate::ResultStoreBase (0x0x7f757fbf7de0) 0 + vptr=((& QtPrivate::ResultStoreBase::_ZTVN9QtPrivate15ResultStoreBaseE) + 16u) + +Vtable for QFutureInterfaceBase +QFutureInterfaceBase::_ZTV20QFutureInterfaceBase: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI20QFutureInterfaceBase) +16 (int (*)(...))QFutureInterfaceBase::~QFutureInterfaceBase +24 (int (*)(...))QFutureInterfaceBase::~QFutureInterfaceBase + +Class QFutureInterfaceBase + size=16 align=8 + base size=16 base align=8 +QFutureInterfaceBase (0x0x7f757fbf7ea0) 0 + vptr=((& QFutureInterfaceBase::_ZTV20QFutureInterfaceBase) + 16u) + +Class QFutureWatcherBase::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFutureWatcherBase::QPrivateSignal (0x0x7f757f976240) 0 empty + +Vtable for QFutureWatcherBase +QFutureWatcherBase::_ZTV18QFutureWatcherBase: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QFutureWatcherBase) +16 (int (*)(...))QFutureWatcherBase::metaObject +24 (int (*)(...))QFutureWatcherBase::qt_metacast +32 (int (*)(...))QFutureWatcherBase::qt_metacall +40 0u +48 0u +56 (int (*)(...))QFutureWatcherBase::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QFutureWatcherBase::connectNotify +104 (int (*)(...))QFutureWatcherBase::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QFutureWatcherBase + size=16 align=8 + base size=16 base align=8 +QFutureWatcherBase (0x0x7f757f9373a8) 0 + vptr=((& QFutureWatcherBase::_ZTV18QFutureWatcherBase) + 16u) + QObject (0x0x7f757f9761e0) 0 + primary-for QFutureWatcherBase (0x0x7f757f9373a8) + +Class QReadWriteLock + size=8 align=8 + base size=8 base align=8 +QReadWriteLock (0x0x7f757f976360) 0 + +Class QReadLocker + size=8 align=8 + base size=8 base align=8 +QReadLocker (0x0x7f757f9763c0) 0 + +Class QWriteLocker + size=8 align=8 + base size=8 base align=8 +QWriteLocker (0x0x7f757f976420) 0 + +Class QSemaphore + size=8 align=8 + base size=8 base align=8 +QSemaphore (0x0x7f757f976480) 0 + +Class QThread::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QThread::QPrivateSignal (0x0x7f757f976540) 0 empty + +Vtable for QThread +QThread::_ZTV7QThread: 15u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QThread) +16 (int (*)(...))QThread::metaObject +24 (int (*)(...))QThread::qt_metacast +32 (int (*)(...))QThread::qt_metacall +40 (int (*)(...))QThread::~QThread +48 (int (*)(...))QThread::~QThread +56 (int (*)(...))QThread::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QThread::run + +Class QThread + size=16 align=8 + base size=16 base align=8 +QThread (0x0x7f757f937820) 0 + vptr=((& QThread::_ZTV7QThread) + 16u) + QObject (0x0x7f757f9764e0) 0 + primary-for QThread (0x0x7f757f937820) + +Class QThreadPool::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QThreadPool::QPrivateSignal (0x0x7f757f976600) 0 empty + +Vtable for QThreadPool +QThreadPool::_ZTV11QThreadPool: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QThreadPool) +16 (int (*)(...))QThreadPool::metaObject +24 (int (*)(...))QThreadPool::qt_metacast +32 (int (*)(...))QThreadPool::qt_metacall +40 (int (*)(...))QThreadPool::~QThreadPool +48 (int (*)(...))QThreadPool::~QThreadPool +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QThreadPool + size=16 align=8 + base size=16 base align=8 +QThreadPool (0x0x7f757f937888) 0 + vptr=((& QThreadPool::_ZTV11QThreadPool) + 16u) + QObject (0x0x7f757f9765a0) 0 + primary-for QThreadPool (0x0x7f757f937888) + +Class QThreadStorageData + size=4 align=4 + base size=4 base align=4 +QThreadStorageData (0x0x7f757f976660) 0 + +Class QWaitCondition + size=8 align=8 + base size=8 base align=8 +QWaitCondition (0x0x7f757f976720) 0 + +Class QBitArray + size=8 align=8 + base size=8 base align=8 +QBitArray (0x0x7f757f976c00) 0 + +Class QBitRef + size=16 align=8 + base size=12 base align=8 +QBitRef (0x0x7f757f976de0) 0 + +Class QByteArrayMatcher::Data + size=272 align=8 + base size=272 base align=8 +QByteArrayMatcher::Data (0x0x7f757f6fb060) 0 + +Class QByteArrayMatcher + size=1040 align=8 + base size=1040 base align=8 +QByteArrayMatcher (0x0x7f757f6fb000) 0 + +Class QCollatorSortKey + size=8 align=8 + base size=8 base align=8 +QCollatorSortKey (0x0x7f757f6fb1e0) 0 + +Class QCollator + size=8 align=8 + base size=8 base align=8 +QCollator (0x0x7f757f6fb2a0) 0 + +Class QCommandLineOption + size=8 align=8 + base size=8 base align=8 +QCommandLineOption (0x0x7f757f6fb540) 0 + +Class QCommandLineParser + size=8 align=8 + base size=8 base align=8 +QCommandLineParser (0x0x7f757f6fb720) 0 + +Class QCryptographicHash + size=8 align=8 + base size=8 base align=8 +QCryptographicHash (0x0x7f757f6fb780) 0 + +Class QElapsedTimer + size=16 align=8 + base size=16 base align=8 +QElapsedTimer (0x0x7f757f6fb7e0) 0 + +Class QPoint + size=8 align=4 + base size=8 base align=4 +QPoint (0x0x7f757f6fb840) 0 + +Class QPointF + size=16 align=8 + base size=16 base align=8 +QPointF (0x0x7f757f6fb9c0) 0 + +Class QLine + size=16 align=4 + base size=16 base align=4 +QLine (0x0x7f757f6fbb40) 0 + +Class QLineF + size=32 align=8 + base size=32 base align=8 +QLineF (0x0x7f757f6fbcc0) 0 + +Class QLinkedListData + size=32 align=8 + base size=32 base align=8 +QLinkedListData (0x0x7f757f6fbe40) 0 + +Class QMargins + size=16 align=4 + base size=16 base align=4 +QMargins (0x0x7f757f44d600) 0 + +Class QMarginsF + size=32 align=8 + base size=32 base align=8 +QMarginsF (0x0x7f757f44d780) 0 + +Class QMessageAuthenticationCode + size=8 align=8 + base size=8 base align=8 +QMessageAuthenticationCode (0x0x7f757f44d900) 0 + +Class QSize + size=8 align=4 + base size=8 base align=4 +QSize (0x0x7f757f44d9c0) 0 + +Class QSizeF + size=16 align=8 + base size=16 base align=8 +QSizeF (0x0x7f757f44dc00) 0 + +Class QRect + size=16 align=4 + base size=16 base align=4 +QRect (0x0x7f757f44de40) 0 + +Class QRectF + size=32 align=8 + base size=32 base align=8 +QRectF (0x0x7f757f631000) 0 + +Class QRegularExpression + size=8 align=8 + base size=8 base align=8 +QRegularExpression (0x0x7f757f631180) 0 + +Class QRegularExpressionMatch + size=8 align=8 + base size=8 base align=8 +QRegularExpressionMatch (0x0x7f757f6314e0) 0 + +Class QRegularExpressionMatchIterator + size=8 align=8 + base size=8 base align=8 +QRegularExpressionMatchIterator (0x0x7f757f6316c0) 0 + +Class QAbstractConcatenable + size=1 align=1 + base size=0 base align=1 +QAbstractConcatenable (0x0x7f757f631a80) 0 empty + +Class QTextBoundaryFinder + size=48 align=8 + base size=48 base align=8 +QTextBoundaryFinder (0x0x7f757f2f04e0) 0 + +Class QTimeLine::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTimeLine::QPrivateSignal (0x0x7f757f2f0660) 0 empty + +Vtable for QTimeLine +QTimeLine::_ZTV9QTimeLine: 15u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QTimeLine) +16 (int (*)(...))QTimeLine::metaObject +24 (int (*)(...))QTimeLine::qt_metacast +32 (int (*)(...))QTimeLine::qt_metacall +40 (int (*)(...))QTimeLine::~QTimeLine +48 (int (*)(...))QTimeLine::~QTimeLine +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QTimeLine::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QTimeLine::valueForTime + +Class QTimeLine + size=16 align=8 + base size=16 base align=8 +QTimeLine (0x0x7f757f258bc8) 0 + vptr=((& QTimeLine::_ZTV9QTimeLine) + 16u) + QObject (0x0x7f757f2f0600) 0 + primary-for QTimeLine (0x0x7f757f258bc8) + +Class QTimeZone::OffsetData + size=32 align=8 + base size=28 base align=8 +QTimeZone::OffsetData (0x0x7f757f2f0720) 0 + +Class QTimeZone + size=8 align=8 + base size=8 base align=8 +QTimeZone (0x0x7f757f2f06c0) 0 + +Class QVersionNumber::SegmentStorage + size=8 align=8 + base size=8 base align=8 +QVersionNumber::SegmentStorage (0x0x7f757f2f0a80) 0 + +Class QVersionNumber + size=8 align=8 + base size=8 base align=8 +QVersionNumber (0x0x7f757f2f0a20) 0 + +Class QXmlStreamStringRef + size=16 align=8 + base size=16 base align=8 +QXmlStreamStringRef (0x0x7f757f2f0d80) 0 + +Class QXmlStreamAttribute + size=80 align=8 + base size=73 base align=8 +QXmlStreamAttribute (0x0x7f757f2f0f00) 0 + +Class QXmlStreamAttributes + size=8 align=8 + base size=8 base align=8 +QXmlStreamAttributes (0x0x7f757f02d068) 0 + QVector (0x0x7f757f012180) 0 + +Class QXmlStreamNamespaceDeclaration + size=40 align=8 + base size=40 base align=8 +QXmlStreamNamespaceDeclaration (0x0x7f757f0121e0) 0 + +Class QXmlStreamNotationDeclaration + size=56 align=8 + base size=56 base align=8 +QXmlStreamNotationDeclaration (0x0x7f757f012360) 0 + +Class QXmlStreamEntityDeclaration + size=88 align=8 + base size=88 base align=8 +QXmlStreamEntityDeclaration (0x0x7f757f0124e0) 0 + +Vtable for QXmlStreamEntityResolver +QXmlStreamEntityResolver::_ZTV24QXmlStreamEntityResolver: 6u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI24QXmlStreamEntityResolver) +16 (int (*)(...))QXmlStreamEntityResolver::~QXmlStreamEntityResolver +24 (int (*)(...))QXmlStreamEntityResolver::~QXmlStreamEntityResolver +32 (int (*)(...))QXmlStreamEntityResolver::resolveEntity +40 (int (*)(...))QXmlStreamEntityResolver::resolveUndeclaredEntity + +Class QXmlStreamEntityResolver + size=8 align=8 + base size=8 base align=8 +QXmlStreamEntityResolver (0x0x7f757f012660) 0 nearly-empty + vptr=((& QXmlStreamEntityResolver::_ZTV24QXmlStreamEntityResolver) + 16u) + +Class QXmlStreamReader + size=8 align=8 + base size=8 base align=8 +QXmlStreamReader (0x0x7f757f0126c0) 0 + +Class QXmlStreamWriter + size=8 align=8 + base size=8 base align=8 +QXmlStreamWriter (0x0x7f757f0127e0) 0 + +Class QGeoAddress + size=8 align=8 + base size=8 base align=8 +QGeoAddress (0x0x7f757f012900) 0 + +Class QGeoCoordinate + size=8 align=8 + base size=8 base align=8 +QGeoCoordinate (0x0x7f757f012c00) 0 + +Class QGeoShape + size=8 align=8 + base size=8 base align=8 +QGeoShape (0x0x7f757f012f00) 0 + +Class QGeoAreaMonitorInfo + size=8 align=8 + base size=8 base align=8 +QGeoAreaMonitorInfo (0x0x7f757f118240) 0 + +Class QGeoPositionInfo + size=8 align=8 + base size=8 base align=8 +QGeoPositionInfo (0x0x7f757f118300) 0 + +Class QGeoPositionInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoPositionInfoSource::QPrivateSignal (0x0x7f757f1183c0) 0 empty + +Vtable for QGeoPositionInfoSource +QGeoPositionInfoSource::_ZTV22QGeoPositionInfoSource: 23u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI22QGeoPositionInfoSource) +16 (int (*)(...))QGeoPositionInfoSource::metaObject +24 (int (*)(...))QGeoPositionInfoSource::qt_metacast +32 (int (*)(...))QGeoPositionInfoSource::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoPositionInfoSource::setUpdateInterval +120 (int (*)(...))QGeoPositionInfoSource::setPreferredPositioningMethods +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual + +Class QGeoPositionInfoSource + size=24 align=8 + base size=24 base align=8 +QGeoPositionInfoSource (0x0x7f757f02d340) 0 + vptr=((& QGeoPositionInfoSource::_ZTV22QGeoPositionInfoSource) + 16u) + QObject (0x0x7f757f118360) 0 + primary-for QGeoPositionInfoSource (0x0x7f757f02d340) + +Class QGeoAreaMonitorSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoAreaMonitorSource::QPrivateSignal (0x0x7f757f118540) 0 empty + +Vtable for QGeoAreaMonitorSource +QGeoAreaMonitorSource::_ZTV21QGeoAreaMonitorSource: 23u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QGeoAreaMonitorSource) +16 (int (*)(...))QGeoAreaMonitorSource::metaObject +24 (int (*)(...))QGeoAreaMonitorSource::qt_metacast +32 (int (*)(...))QGeoAreaMonitorSource::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoAreaMonitorSource::setPositionInfoSource +120 (int (*)(...))QGeoAreaMonitorSource::positionInfoSource +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual + +Class QGeoAreaMonitorSource + size=24 align=8 + base size=24 base align=8 +QGeoAreaMonitorSource (0x0x7f757f02d478) 0 + vptr=((& QGeoAreaMonitorSource::_ZTV21QGeoAreaMonitorSource) + 16u) + QObject (0x0x7f757f1184e0) 0 + primary-for QGeoAreaMonitorSource (0x0x7f757f02d478) + +Class QGeoCircle + size=8 align=8 + base size=8 base align=8 +QGeoCircle (0x0x7f757f02d4e0) 0 + QGeoShape (0x0x7f757f1185a0) 0 + +Class QGeoLocation + size=8 align=8 + base size=8 base align=8 +QGeoLocation (0x0x7f757f118840) 0 + +Class QGeoSatelliteInfo + size=8 align=8 + base size=8 base align=8 +QGeoSatelliteInfo (0x0x7f757f118b40) 0 + +Class QGeoSatelliteInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoSatelliteInfoSource::QPrivateSignal (0x0x7f757f118c00) 0 empty + +Vtable for QGeoSatelliteInfoSource +QGeoSatelliteInfoSource::_ZTV23QGeoSatelliteInfoSource: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QGeoSatelliteInfoSource) +16 (int (*)(...))QGeoSatelliteInfoSource::metaObject +24 (int (*)(...))QGeoSatelliteInfoSource::qt_metacast +32 (int (*)(...))QGeoSatelliteInfoSource::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoSatelliteInfoSource::setUpdateInterval +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual + +Class QGeoSatelliteInfoSource + size=24 align=8 + base size=24 base align=8 +QGeoSatelliteInfoSource (0x0x7f757f02d618) 0 + vptr=((& QGeoSatelliteInfoSource::_ZTV23QGeoSatelliteInfoSource) + 16u) + QObject (0x0x7f757f118ba0) 0 + primary-for QGeoSatelliteInfoSource (0x0x7f757f02d618) + +Vtable for QGeoPositionInfoSourceFactory +QGeoPositionInfoSourceFactory::_ZTV29QGeoPositionInfoSourceFactory: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI29QGeoPositionInfoSourceFactory) +16 0u +24 0u +32 (int (*)(...))__cxa_pure_virtual +40 (int (*)(...))__cxa_pure_virtual +48 (int (*)(...))__cxa_pure_virtual + +Class QGeoPositionInfoSourceFactory + size=8 align=8 + base size=8 base align=8 +QGeoPositionInfoSourceFactory (0x0x7f757f118cc0) 0 nearly-empty + vptr=((& QGeoPositionInfoSourceFactory::_ZTV29QGeoPositionInfoSourceFactory) + 16u) + +Class QGeoRectangle + size=8 align=8 + base size=8 base align=8 +QGeoRectangle (0x0x7f757f02d680) 0 + QGeoShape (0x0x7f757f118d80) 0 + +Class QNmeaPositionInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QNmeaPositionInfoSource::QPrivateSignal (0x0x7f757eded1e0) 0 empty + +Vtable for QNmeaPositionInfoSource +QNmeaPositionInfoSource::_ZTV23QNmeaPositionInfoSource: 24u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QNmeaPositionInfoSource) +16 (int (*)(...))QNmeaPositionInfoSource::metaObject +24 (int (*)(...))QNmeaPositionInfoSource::qt_metacast +32 (int (*)(...))QNmeaPositionInfoSource::qt_metacall +40 (int (*)(...))QNmeaPositionInfoSource::~QNmeaPositionInfoSource +48 (int (*)(...))QNmeaPositionInfoSource::~QNmeaPositionInfoSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QNmeaPositionInfoSource::setUpdateInterval +120 (int (*)(...))QGeoPositionInfoSource::setPreferredPositioningMethods +128 (int (*)(...))QNmeaPositionInfoSource::lastKnownPosition +136 (int (*)(...))QNmeaPositionInfoSource::supportedPositioningMethods +144 (int (*)(...))QNmeaPositionInfoSource::minimumUpdateInterval +152 (int (*)(...))QNmeaPositionInfoSource::error +160 (int (*)(...))QNmeaPositionInfoSource::startUpdates +168 (int (*)(...))QNmeaPositionInfoSource::stopUpdates +176 (int (*)(...))QNmeaPositionInfoSource::requestUpdate +184 (int (*)(...))QNmeaPositionInfoSource::parsePosInfoFromNmeaData + +Class QNmeaPositionInfoSource + size=32 align=8 + base size=32 base align=8 +QNmeaPositionInfoSource (0x0x7f757f02d820) 0 + vptr=((& QNmeaPositionInfoSource::_ZTV23QNmeaPositionInfoSource) + 16u) + QGeoPositionInfoSource (0x0x7f757f02d888) 0 + primary-for QNmeaPositionInfoSource (0x0x7f757f02d820) + QObject (0x0x7f757eded180) 0 + primary-for QGeoPositionInfoSource (0x0x7f757f02d888) + diff --git a/tests/auto/bic/data/QtPositioning.5.7.0.linux-gcc-amd64.txt b/tests/auto/bic/data/QtPositioning.5.7.0.linux-gcc-amd64.txt new file mode 100644 index 0000000..cf3f81f --- /dev/null +++ b/tests/auto/bic/data/QtPositioning.5.7.0.linux-gcc-amd64.txt @@ -0,0 +1,4400 @@ +Class std::__failure_type + size=1 align=1 + base size=0 base align=1 +std::__failure_type (0x0x7ff717ebbd80) 0 empty + +Class std::__do_is_destructible_impl + size=1 align=1 + base size=0 base align=1 +std::__do_is_destructible_impl (0x0x7ff717f63540) 0 empty + +Class std::__do_is_nt_destructible_impl + size=1 align=1 + base size=0 base align=1 +std::__do_is_nt_destructible_impl (0x0x7ff717f63780) 0 empty + +Class std::__do_is_default_constructible_impl + size=1 align=1 + base size=0 base align=1 +std::__do_is_default_constructible_impl (0x0x7ff717f639c0) 0 empty + +Class std::__do_is_static_castable_impl + size=1 align=1 + base size=0 base align=1 +std::__do_is_static_castable_impl (0x0x7ff717f63c00) 0 empty + +Class std::__do_is_direct_constructible_impl + size=1 align=1 + base size=0 base align=1 +std::__do_is_direct_constructible_impl (0x0x7ff717f63d80) 0 empty + +Class std::__do_is_nary_constructible_impl + size=1 align=1 + base size=0 base align=1 +std::__do_is_nary_constructible_impl (0x0x7ff717f97180) 0 empty + +Class std::__do_common_type_impl + size=1 align=1 + base size=0 base align=1 +std::__do_common_type_impl (0x0x7ff715c1b900) 0 empty + +Class std::__do_member_type_wrapper + size=1 align=1 + base size=0 base align=1 +std::__do_member_type_wrapper (0x0x7ff715c1b9c0) 0 empty + +Class std::__result_of_memfun_ref_impl + size=1 align=1 + base size=0 base align=1 +std::__result_of_memfun_ref_impl (0x0x7ff715c1bd20) 0 empty + +Class std::__result_of_memfun_deref_impl + size=1 align=1 + base size=0 base align=1 +std::__result_of_memfun_deref_impl (0x0x7ff715c1bde0) 0 empty + +Class std::__result_of_memobj_ref_impl + size=1 align=1 + base size=0 base align=1 +std::__result_of_memobj_ref_impl (0x0x7ff715c1bea0) 0 empty + +Class std::__result_of_memobj_deref_impl + size=1 align=1 + base size=0 base align=1 +std::__result_of_memobj_deref_impl (0x0x7ff715c1bf60) 0 empty + +Class std::__result_of_other_impl + size=1 align=1 + base size=0 base align=1 +std::__result_of_other_impl (0x0x7ff715c52240) 0 empty + +Class std::piecewise_construct_t + size=1 align=1 + base size=0 base align=1 +std::piecewise_construct_t (0x0x7ff715c523c0) 0 empty + +Class std::__true_type + size=1 align=1 + base size=0 base align=1 +std::__true_type (0x0x7ff715c52840) 0 empty + +Class std::__false_type + size=1 align=1 + base size=0 base align=1 +std::__false_type (0x0x7ff715c528a0) 0 empty + +Class std::input_iterator_tag + size=1 align=1 + base size=0 base align=1 +std::input_iterator_tag (0x0x7ff715cfb540) 0 empty + +Class std::output_iterator_tag + size=1 align=1 + base size=0 base align=1 +std::output_iterator_tag (0x0x7ff715cfb5a0) 0 empty + +Class std::forward_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::forward_iterator_tag (0x0x7ff715c1f750) 0 empty + std::input_iterator_tag (0x0x7ff715cfb600) 0 empty + +Class std::bidirectional_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::bidirectional_iterator_tag (0x0x7ff715c1f7b8) 0 empty + std::forward_iterator_tag (0x0x7ff715c1f820) 0 empty + std::input_iterator_tag (0x0x7ff715cfb660) 0 empty + +Class std::random_access_iterator_tag + size=1 align=1 + base size=1 base align=1 +std::random_access_iterator_tag (0x0x7ff715c1f888) 0 empty + std::bidirectional_iterator_tag (0x0x7ff715c1f8f0) 0 empty + std::forward_iterator_tag (0x0x7ff715c1f958) 0 empty + std::input_iterator_tag (0x0x7ff715cfb6c0) 0 empty + +Class __gnu_cxx::__ops::_Iter_less_iter + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Iter_less_iter (0x0x7ff715d3d360) 0 empty + +Class __gnu_cxx::__ops::_Iter_less_val + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Iter_less_val (0x0x7ff715d3d3c0) 0 empty + +Class __gnu_cxx::__ops::_Val_less_iter + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Val_less_iter (0x0x7ff715d3d420) 0 empty + +Class __gnu_cxx::__ops::_Iter_equal_to_iter + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Iter_equal_to_iter (0x0x7ff715d3d480) 0 empty + +Class __gnu_cxx::__ops::_Iter_equal_to_val + size=1 align=1 + base size=0 base align=1 +__gnu_cxx::__ops::_Iter_equal_to_val (0x0x7ff715d3d4e0) 0 empty + +Class wait + size=4 align=4 + base size=4 base align=4 +wait (0x0x7ff715a65000) 0 + +Class __locale_struct + size=232 align=8 + base size=232 base align=8 +__locale_struct (0x0x7ff715a65240) 0 + +Class timespec + size=16 align=8 + base size=16 base align=8 +timespec (0x0x7ff715a65300) 0 + +Class timeval + size=16 align=8 + base size=16 base align=8 +timeval (0x0x7ff715a65360) 0 + +Class pthread_attr_t + size=56 align=8 + base size=56 base align=8 +pthread_attr_t (0x0x7ff715a65420) 0 + +Class __pthread_internal_list + size=16 align=8 + base size=16 base align=8 +__pthread_internal_list (0x0x7ff715a65480) 0 + +Class random_data + size=48 align=8 + base size=48 base align=8 +random_data (0x0x7ff715a65900) 0 + +Class drand48_data + size=24 align=8 + base size=24 base align=8 +drand48_data (0x0x7ff715a65960) 0 + +Vtable for std::exception +std::exception::_ZTVSt9exception: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt9exception) +16 (int (*)(...))std::exception::~exception +24 (int (*)(...))std::exception::~exception +32 (int (*)(...))std::exception::what + +Class std::exception + size=8 align=8 + base size=8 base align=8 +std::exception (0x0x7ff715a659c0) 0 nearly-empty + vptr=((& std::exception::_ZTVSt9exception) + 16u) + +Vtable for std::bad_exception +std::bad_exception::_ZTVSt13bad_exception: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt13bad_exception) +16 (int (*)(...))std::bad_exception::~bad_exception +24 (int (*)(...))std::bad_exception::~bad_exception +32 (int (*)(...))std::bad_exception::what + +Class std::bad_exception + size=8 align=8 + base size=8 base align=8 +std::bad_exception (0x0x7ff715c1fea0) 0 nearly-empty + vptr=((& std::bad_exception::_ZTVSt13bad_exception) + 16u) + std::exception (0x0x7ff715a65a20) 0 nearly-empty + primary-for std::bad_exception (0x0x7ff715c1fea0) + +Class std::__exception_ptr::exception_ptr + size=8 align=8 + base size=8 base align=8 +std::__exception_ptr::exception_ptr (0x0x7ff715a65a80) 0 + +Vtable for std::nested_exception +std::nested_exception::_ZTVSt16nested_exception: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt16nested_exception) +16 (int (*)(...))std::nested_exception::~nested_exception +24 (int (*)(...))std::nested_exception::~nested_exception + +Class std::nested_exception + size=16 align=8 + base size=16 base align=8 +std::nested_exception (0x0x7ff715a65ae0) 0 + vptr=((& std::nested_exception::_ZTVSt16nested_exception) + 16u) + +Vtable for std::bad_alloc +std::bad_alloc::_ZTVSt9bad_alloc: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt9bad_alloc) +16 (int (*)(...))std::bad_alloc::~bad_alloc +24 (int (*)(...))std::bad_alloc::~bad_alloc +32 (int (*)(...))std::bad_alloc::what + +Class std::bad_alloc + size=8 align=8 + base size=8 base align=8 +std::bad_alloc (0x0x7ff715b6f0d0) 0 nearly-empty + vptr=((& std::bad_alloc::_ZTVSt9bad_alloc) + 16u) + std::exception (0x0x7ff715a65f00) 0 nearly-empty + primary-for std::bad_alloc (0x0x7ff715b6f0d0) + +Vtable for std::bad_array_new_length +std::bad_array_new_length::_ZTVSt20bad_array_new_length: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt20bad_array_new_length) +16 (int (*)(...))std::bad_array_new_length::~bad_array_new_length +24 (int (*)(...))std::bad_array_new_length::~bad_array_new_length +32 (int (*)(...))std::bad_array_new_length::what + +Class std::bad_array_new_length + size=8 align=8 + base size=8 base align=8 +std::bad_array_new_length (0x0x7ff715b6f138) 0 nearly-empty + vptr=((& std::bad_array_new_length::_ZTVSt20bad_array_new_length) + 16u) + std::bad_alloc (0x0x7ff715b6f1a0) 0 nearly-empty + primary-for std::bad_array_new_length (0x0x7ff715b6f138) + std::exception (0x0x7ff715a65f60) 0 nearly-empty + primary-for std::bad_alloc (0x0x7ff715b6f1a0) + +Class std::nothrow_t + size=1 align=1 + base size=0 base align=1 +std::nothrow_t (0x0x7ff715b81000) 0 empty + +Class __exception + size=40 align=8 + base size=40 base align=8 +__exception (0x0x7ff715b81c00) 0 + +Class lconv + size=96 align=8 + base size=96 base align=8 +lconv (0x0x7ff715982900) 0 + +Vtable for __cxxabiv1::__forced_unwind +__cxxabiv1::__forced_unwind::_ZTVN10__cxxabiv115__forced_unwindE: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN10__cxxabiv115__forced_unwindE) +16 0u +24 0u +32 (int (*)(...))__cxa_pure_virtual + +Class __cxxabiv1::__forced_unwind + size=8 align=8 + base size=8 base align=8 +__cxxabiv1::__forced_unwind (0x0x7ff715982960) 0 nearly-empty + vptr=((& __cxxabiv1::__forced_unwind::_ZTVN10__cxxabiv115__forced_unwindE) + 16u) + +Class sched_param + size=4 align=4 + base size=4 base align=4 +sched_param (0x0x7ff715678840) 0 + +Class __sched_param + size=4 align=4 + base size=4 base align=4 +__sched_param (0x0x7ff7156788a0) 0 + +Class timex + size=208 align=8 + base size=208 base align=8 +timex (0x0x7ff715678960) 0 + +Class tm + size=56 align=8 + base size=56 base align=8 +tm (0x0x7ff7156789c0) 0 + +Class itimerspec + size=32 align=8 + base size=32 base align=8 +itimerspec (0x0x7ff715678a20) 0 + +Class _pthread_cleanup_buffer + size=32 align=8 + base size=32 base align=8 +_pthread_cleanup_buffer (0x0x7ff715678a80) 0 + +Class __pthread_cleanup_frame + size=24 align=8 + base size=24 base align=8 +__pthread_cleanup_frame (0x0x7ff715678ba0) 0 + +Class __pthread_cleanup_class + size=24 align=8 + base size=24 base align=8 +__pthread_cleanup_class (0x0x7ff715678c00) 0 + +Class _IO_marker + size=24 align=8 + base size=24 base align=8 +_IO_marker (0x0x7ff71542f060) 0 + +Class _IO_FILE + size=216 align=8 + base size=216 base align=8 +_IO_FILE (0x0x7ff71542f0c0) 0 + +Class std::_Hash_impl + size=1 align=1 + base size=0 base align=1 +std::_Hash_impl (0x0x7ff7151ca8a0) 0 empty + +Class std::_Fnv_hash_impl + size=1 align=1 + base size=0 base align=1 +std::_Fnv_hash_impl (0x0x7ff7151ca900) 0 empty + +Class std::__numeric_limits_base + size=1 align=1 + base size=0 base align=1 +std::__numeric_limits_base (0x0x7ff7151fc8a0) 0 empty + +Class std::_Bit_reference + size=16 align=8 + base size=16 base align=8 +std::_Bit_reference (0x0x7ff714ff96c0) 0 + +Class std::_Bit_iterator_base + size=16 align=8 + base size=12 base align=8 +std::_Bit_iterator_base (0x0x7ff7151d5f08) 0 + std::iterator (0x0x7ff714ff9780) 0 empty + +Class std::_Bit_iterator + size=16 align=8 + base size=12 base align=8 +std::_Bit_iterator (0x0x7ff7151d5f70) 0 + std::_Bit_iterator_base (0x0x7ff7151d56e8) 0 + std::iterator (0x0x7ff714ff97e0) 0 empty + +Class std::_Bit_const_iterator + size=16 align=8 + base size=12 base align=8 +std::_Bit_const_iterator (0x0x7ff7151d5750) 0 + std::_Bit_iterator_base (0x0x7ff7151d5a28) 0 + std::iterator (0x0x7ff714ff9840) 0 empty + +Class std::random_device + size=5000 align=8 + base size=5000 base align=8 +std::random_device (0x0x7ff714e26660) 0 + +Class std::bernoulli_distribution::param_type + size=8 align=8 + base size=8 base align=8 +std::bernoulli_distribution::param_type (0x0x7ff714f21420) 0 + +Class std::bernoulli_distribution + size=8 align=8 + base size=8 base align=8 +std::bernoulli_distribution (0x0x7ff714f213c0) 0 + +Class std::seed_seq + size=24 align=8 + base size=24 base align=8 +std::seed_seq (0x0x7ff714cc13c0) 0 + +Class qIsNull(double)::U + size=8 align=8 + base size=8 base align=8 +qIsNull(double)::U (0x0x7ff713a87e40) 0 + +Class qIsNull(float)::U + size=4 align=4 + base size=4 base align=4 +qIsNull(float)::U (0x0x7ff713a87ea0) 0 + +Class QtPrivate::big_ + size=2 align=1 + base size=2 base align=1 +QtPrivate::big_ (0x0x7ff71383c2a0) 0 + +Class QSysInfo + size=1 align=1 + base size=0 base align=1 +QSysInfo (0x0x7ff7138f3780) 0 empty + +Class QMessageLogContext + size=32 align=8 + base size=32 base align=8 +QMessageLogContext (0x0x7ff7138f37e0) 0 + +Class QMessageLogger + size=32 align=8 + base size=32 base align=8 +QMessageLogger (0x0x7ff7138f3840) 0 + +Class QFlag + size=4 align=4 + base size=4 base align=4 +QFlag (0x0x7ff7138f38a0) 0 + +Class QIncompatibleFlag + size=4 align=4 + base size=4 base align=4 +QIncompatibleFlag (0x0x7ff7138f3a20) 0 + +Class std::__atomic_flag_base + size=1 align=1 + base size=1 base align=1 +std::__atomic_flag_base (0x0x7ff7138f3e40) 0 + +Class std::atomic_flag + size=1 align=1 + base size=1 base align=1 +std::atomic_flag (0x0x7ff713948138) 0 + std::__atomic_flag_base (0x0x7ff7138f3ea0) 0 + +Class QAtomicInt + size=4 align=4 + base size=4 base align=4 +QAtomicInt (0x0x7ff713948888) 0 + QAtomicInteger (0x0x7ff7139488f0) 0 + QBasicAtomicInteger (0x0x7ff71349c420) 0 + +Class QInternal + size=1 align=1 + base size=0 base align=1 +QInternal (0x0x7ff7132d4cc0) 0 empty + +Class QGenericArgument + size=16 align=8 + base size=16 base align=8 +QGenericArgument (0x0x7ff713115ba0) 0 + +Class QGenericReturnArgument + size=16 align=8 + base size=16 base align=8 +QGenericReturnArgument (0x0x7ff713291b60) 0 + QGenericArgument (0x0x7ff713115c00) 0 + +Class QMetaObject + size=48 align=8 + base size=48 base align=8 +QMetaObject (0x0x7ff713115d80) 0 + +Class QMetaObject::Connection + size=8 align=8 + base size=8 base align=8 +QMetaObject::Connection (0x0x7ff713115e40) 0 + +Class QLatin1Char + size=1 align=1 + base size=1 base align=1 +QLatin1Char (0x0x7ff712df1ea0) 0 + +Class QChar + size=2 align=2 + base size=2 base align=2 +QChar (0x0x7ff712df1f00) 0 + +Class QtPrivate::RefCount + size=4 align=4 + base size=4 base align=4 +QtPrivate::RefCount (0x0x7ff712e970c0) 0 + +Class QArrayData + size=24 align=8 + base size=24 base align=8 +QArrayData (0x0x7ff712e97180) 0 + +Class QtPrivate::QContainerImplHelper + size=1 align=1 + base size=0 base align=1 +QtPrivate::QContainerImplHelper (0x0x7ff712e975a0) 0 empty + +Class std::locale + size=8 align=8 + base size=8 base align=8 +std::locale (0x0x7ff712e97600) 0 + +Vtable for std::locale::facet +std::locale::facet::_ZTVNSt6locale5facetE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt6locale5facetE) +16 (int (*)(...))std::locale::facet::~facet +24 (int (*)(...))std::locale::facet::~facet + +Class std::locale::facet + size=16 align=8 + base size=12 base align=8 +std::locale::facet (0x0x7ff712e97660) 0 + vptr=((& std::locale::facet::_ZTVNSt6locale5facetE) + 16u) + +Class std::locale::id + size=8 align=8 + base size=8 base align=8 +std::locale::id (0x0x7ff712e976c0) 0 + +Class std::locale::_Impl + size=40 align=8 + base size=40 base align=8 +std::locale::_Impl (0x0x7ff712e97720) 0 + +Class std::__cow_string + size=8 align=8 + base size=8 base align=8 +std::__cow_string (0x0x7ff712e97ae0) 0 + +Vtable for std::logic_error +std::logic_error::_ZTVSt11logic_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt11logic_error) +16 (int (*)(...))std::logic_error::~logic_error +24 (int (*)(...))std::logic_error::~logic_error +32 (int (*)(...))std::logic_error::what + +Class std::logic_error + size=16 align=8 + base size=16 base align=8 +std::logic_error (0x0x7ff712e10d00) 0 + vptr=((& std::logic_error::_ZTVSt11logic_error) + 16u) + std::exception (0x0x7ff712e97ba0) 0 nearly-empty + primary-for std::logic_error (0x0x7ff712e10d00) + +Vtable for std::domain_error +std::domain_error::_ZTVSt12domain_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt12domain_error) +16 (int (*)(...))std::domain_error::~domain_error +24 (int (*)(...))std::domain_error::~domain_error +32 (int (*)(...))std::logic_error::what + +Class std::domain_error + size=16 align=8 + base size=16 base align=8 +std::domain_error (0x0x7ff712e10dd0) 0 + vptr=((& std::domain_error::_ZTVSt12domain_error) + 16u) + std::logic_error (0x0x7ff712fa9000) 0 + primary-for std::domain_error (0x0x7ff712e10dd0) + std::exception (0x0x7ff712e97c00) 0 nearly-empty + primary-for std::logic_error (0x0x7ff712fa9000) + +Vtable for std::invalid_argument +std::invalid_argument::_ZTVSt16invalid_argument: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt16invalid_argument) +16 (int (*)(...))std::invalid_argument::~invalid_argument +24 (int (*)(...))std::invalid_argument::~invalid_argument +32 (int (*)(...))std::logic_error::what + +Class std::invalid_argument + size=16 align=8 + base size=16 base align=8 +std::invalid_argument (0x0x7ff712fa9068) 0 + vptr=((& std::invalid_argument::_ZTVSt16invalid_argument) + 16u) + std::logic_error (0x0x7ff712fa90d0) 0 + primary-for std::invalid_argument (0x0x7ff712fa9068) + std::exception (0x0x7ff712e97c60) 0 nearly-empty + primary-for std::logic_error (0x0x7ff712fa90d0) + +Vtable for std::length_error +std::length_error::_ZTVSt12length_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt12length_error) +16 (int (*)(...))std::length_error::~length_error +24 (int (*)(...))std::length_error::~length_error +32 (int (*)(...))std::logic_error::what + +Class std::length_error + size=16 align=8 + base size=16 base align=8 +std::length_error (0x0x7ff712fa9138) 0 + vptr=((& std::length_error::_ZTVSt12length_error) + 16u) + std::logic_error (0x0x7ff712fa91a0) 0 + primary-for std::length_error (0x0x7ff712fa9138) + std::exception (0x0x7ff712e97cc0) 0 nearly-empty + primary-for std::logic_error (0x0x7ff712fa91a0) + +Vtable for std::out_of_range +std::out_of_range::_ZTVSt12out_of_range: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt12out_of_range) +16 (int (*)(...))std::out_of_range::~out_of_range +24 (int (*)(...))std::out_of_range::~out_of_range +32 (int (*)(...))std::logic_error::what + +Class std::out_of_range + size=16 align=8 + base size=16 base align=8 +std::out_of_range (0x0x7ff712fa9208) 0 + vptr=((& std::out_of_range::_ZTVSt12out_of_range) + 16u) + std::logic_error (0x0x7ff712fa9270) 0 + primary-for std::out_of_range (0x0x7ff712fa9208) + std::exception (0x0x7ff712e97d20) 0 nearly-empty + primary-for std::logic_error (0x0x7ff712fa9270) + +Vtable for std::runtime_error +std::runtime_error::_ZTVSt13runtime_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt13runtime_error) +16 (int (*)(...))std::runtime_error::~runtime_error +24 (int (*)(...))std::runtime_error::~runtime_error +32 (int (*)(...))std::runtime_error::what + +Class std::runtime_error + size=16 align=8 + base size=16 base align=8 +std::runtime_error (0x0x7ff712fa92d8) 0 + vptr=((& std::runtime_error::_ZTVSt13runtime_error) + 16u) + std::exception (0x0x7ff712e97d80) 0 nearly-empty + primary-for std::runtime_error (0x0x7ff712fa92d8) + +Vtable for std::range_error +std::range_error::_ZTVSt11range_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt11range_error) +16 (int (*)(...))std::range_error::~range_error +24 (int (*)(...))std::range_error::~range_error +32 (int (*)(...))std::runtime_error::what + +Class std::range_error + size=16 align=8 + base size=16 base align=8 +std::range_error (0x0x7ff712fa9340) 0 + vptr=((& std::range_error::_ZTVSt11range_error) + 16u) + std::runtime_error (0x0x7ff712fa93a8) 0 + primary-for std::range_error (0x0x7ff712fa9340) + std::exception (0x0x7ff712e97de0) 0 nearly-empty + primary-for std::runtime_error (0x0x7ff712fa93a8) + +Vtable for std::overflow_error +std::overflow_error::_ZTVSt14overflow_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt14overflow_error) +16 (int (*)(...))std::overflow_error::~overflow_error +24 (int (*)(...))std::overflow_error::~overflow_error +32 (int (*)(...))std::runtime_error::what + +Class std::overflow_error + size=16 align=8 + base size=16 base align=8 +std::overflow_error (0x0x7ff712fa9410) 0 + vptr=((& std::overflow_error::_ZTVSt14overflow_error) + 16u) + std::runtime_error (0x0x7ff712fa9478) 0 + primary-for std::overflow_error (0x0x7ff712fa9410) + std::exception (0x0x7ff712e97e40) 0 nearly-empty + primary-for std::runtime_error (0x0x7ff712fa9478) + +Vtable for std::underflow_error +std::underflow_error::_ZTVSt15underflow_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt15underflow_error) +16 (int (*)(...))std::underflow_error::~underflow_error +24 (int (*)(...))std::underflow_error::~underflow_error +32 (int (*)(...))std::runtime_error::what + +Class std::underflow_error + size=16 align=8 + base size=16 base align=8 +std::underflow_error (0x0x7ff712fa94e0) 0 + vptr=((& std::underflow_error::_ZTVSt15underflow_error) + 16u) + std::runtime_error (0x0x7ff712fa9548) 0 + primary-for std::underflow_error (0x0x7ff712fa94e0) + std::exception (0x0x7ff712e97ea0) 0 nearly-empty + primary-for std::runtime_error (0x0x7ff712fa9548) + +Vtable for std::_V2::error_category +std::_V2::error_category::_ZTVNSt3_V214error_categoryE: 10u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt3_V214error_categoryE) +16 0u +24 0u +32 (int (*)(...))__cxa_pure_virtual +40 (int (*)(...))std::_V2::error_category::_M_message +48 (int (*)(...))__cxa_pure_virtual +56 (int (*)(...))std::_V2::error_category::default_error_condition +64 (int (*)(...))std::_V2::error_category::equivalent +72 (int (*)(...))std::_V2::error_category::equivalent + +Class std::_V2::error_category + size=8 align=8 + base size=8 base align=8 +std::_V2::error_category (0x0x7ff712bcb060) 0 nearly-empty + vptr=((& std::_V2::error_category::_ZTVNSt3_V214error_categoryE) + 16u) + +Class std::error_code + size=16 align=8 + base size=16 base align=8 +std::error_code (0x0x7ff712bcb2a0) 0 + +Class std::error_condition + size=16 align=8 + base size=16 base align=8 +std::error_condition (0x0x7ff712bcb420) 0 + +Vtable for std::system_error +std::system_error::_ZTVSt12system_error: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt12system_error) +16 (int (*)(...))std::system_error::~system_error +24 (int (*)(...))std::system_error::~system_error +32 (int (*)(...))std::runtime_error::what + +Class std::system_error + size=32 align=8 + base size=32 base align=8 +std::system_error (0x0x7ff712fa9a28) 0 + vptr=((& std::system_error::_ZTVSt12system_error) + 16u) + std::runtime_error (0x0x7ff712fa9a90) 0 + primary-for std::system_error (0x0x7ff712fa9a28) + std::exception (0x0x7ff712bcb660) 0 nearly-empty + primary-for std::runtime_error (0x0x7ff712fa9a90) + +Vtable for std::ios_base::failure +std::ios_base::failure::_ZTVNSt8ios_base7failureB5cxx11E: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTINSt8ios_base7failureB5cxx11E) +16 (int (*)(...))std::ios_base::failure::~failure +24 (int (*)(...))std::ios_base::failure::~failure +32 (int (*)(...))std::ios_base::failure::what + +Class std::ios_base::failure + size=32 align=8 + base size=32 base align=8 +std::ios_base::failure (0x0x7ff712c27680) 0 + vptr=((& std::ios_base::failure::_ZTVNSt8ios_base7failureB5cxx11E) + 16u) + std::system_error (0x0x7ff712c276e8) 0 + primary-for std::ios_base::failure (0x0x7ff712c27680) + std::runtime_error (0x0x7ff712c27750) 0 + primary-for std::system_error (0x0x7ff712c276e8) + std::exception (0x0x7ff712bcb960) 0 nearly-empty + primary-for std::runtime_error (0x0x7ff712c27750) + +Class std::ios_base::_Callback_list + size=24 align=8 + base size=24 base align=8 +std::ios_base::_Callback_list (0x0x7ff712bcb9c0) 0 + +Class std::ios_base::_Words + size=16 align=8 + base size=16 base align=8 +std::ios_base::_Words (0x0x7ff712bcba20) 0 + +Class std::ios_base::Init + size=1 align=1 + base size=0 base align=1 +std::ios_base::Init (0x0x7ff712bcba80) 0 empty + +Vtable for std::ios_base +std::ios_base::_ZTVSt8ios_base: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt8ios_base) +16 (int (*)(...))std::ios_base::~ios_base +24 (int (*)(...))std::ios_base::~ios_base + +Class std::ios_base + size=216 align=8 + base size=216 base align=8 +std::ios_base (0x0x7ff712bcb900) 0 + vptr=((& std::ios_base::_ZTVSt8ios_base) + 16u) + +Class std::ctype_base + size=1 align=1 + base size=0 base align=1 +std::ctype_base (0x0x7ff712cff240) 0 empty + +Class std::__num_base + size=1 align=1 + base size=0 base align=1 +std::__num_base (0x0x7ff712cff900) 0 empty + +VTT for std::basic_ostream +std::basic_ostream::_ZTTSo: 2u entries +0 ((& std::basic_ostream::_ZTVSo) + 24u) +8 ((& std::basic_ostream::_ZTVSo) + 64u) + +VTT for std::basic_ostream +std::basic_ostream::_ZTTSt13basic_ostreamIwSt11char_traitsIwEE: 2u entries +0 ((& std::basic_ostream::_ZTVSt13basic_ostreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_ostream::_ZTVSt13basic_ostreamIwSt11char_traitsIwEE) + 64u) + +VTT for std::basic_istream +std::basic_istream::_ZTTSi: 2u entries +0 ((& std::basic_istream::_ZTVSi) + 24u) +8 ((& std::basic_istream::_ZTVSi) + 64u) + +VTT for std::basic_istream +std::basic_istream::_ZTTSt13basic_istreamIwSt11char_traitsIwEE: 2u entries +0 ((& std::basic_istream::_ZTVSt13basic_istreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_istream::_ZTVSt13basic_istreamIwSt11char_traitsIwEE) + 64u) + +Construction vtable for std::basic_istream (0x0x7ff71291b000 instance) in std::basic_iostream +std::basic_iostream::_ZTCSd0_Si: 10u entries +0 24u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISi) +24 0u +32 0u +40 18446744073709551592u +48 (int (*)(...))-24 +56 (int (*)(...))(& _ZTISi) +64 0u +72 0u + +Construction vtable for std::basic_ostream (0x0x7ff71291b0d0 instance) in std::basic_iostream +std::basic_iostream::_ZTCSd16_So: 10u entries +0 8u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISo) +24 0u +32 0u +40 18446744073709551608u +48 (int (*)(...))-8 +56 (int (*)(...))(& _ZTISo) +64 0u +72 0u + +VTT for std::basic_iostream +std::basic_iostream::_ZTTSd: 7u entries +0 ((& std::basic_iostream::_ZTVSd) + 24u) +8 ((& std::basic_iostream::_ZTCSd0_Si) + 24u) +16 ((& std::basic_iostream::_ZTCSd0_Si) + 64u) +24 ((& std::basic_iostream::_ZTCSd16_So) + 24u) +32 ((& std::basic_iostream::_ZTCSd16_So) + 64u) +40 ((& std::basic_iostream::_ZTVSd) + 104u) +48 ((& std::basic_iostream::_ZTVSd) + 64u) + +Construction vtable for std::basic_istream (0x0x7ff71291b478 instance) in std::basic_iostream +std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E: 10u entries +0 24u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISt13basic_istreamIwSt11char_traitsIwEE) +24 0u +32 0u +40 18446744073709551592u +48 (int (*)(...))-24 +56 (int (*)(...))(& _ZTISt13basic_istreamIwSt11char_traitsIwEE) +64 0u +72 0u + +Construction vtable for std::basic_ostream (0x0x7ff71291b548 instance) in std::basic_iostream +std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E: 10u entries +0 8u +8 (int (*)(...))0 +16 (int (*)(...))(& _ZTISt13basic_ostreamIwSt11char_traitsIwEE) +24 0u +32 0u +40 18446744073709551608u +48 (int (*)(...))-8 +56 (int (*)(...))(& _ZTISt13basic_ostreamIwSt11char_traitsIwEE) +64 0u +72 0u + +VTT for std::basic_iostream +std::basic_iostream::_ZTTSt14basic_iostreamIwSt11char_traitsIwEE: 7u entries +0 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 24u) +8 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E) + 24u) +16 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE0_St13basic_istreamIwS1_E) + 64u) +24 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E) + 24u) +32 ((& std::basic_iostream::_ZTCSt14basic_iostreamIwSt11char_traitsIwEE16_St13basic_ostreamIwS1_E) + 64u) +40 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 104u) +48 ((& std::basic_iostream::_ZTVSt14basic_iostreamIwSt11char_traitsIwEE) + 64u) + +Class QByteArrayDataPtr + size=8 align=8 + base size=8 base align=8 +QByteArrayDataPtr (0x0x7ff7129391e0) 0 + +Class QByteArray + size=8 align=8 + base size=8 base align=8 +QByteArray (0x0x7ff712939240) 0 + +Class QByteRef + size=16 align=8 + base size=12 base align=8 +QByteRef (0x0x7ff712637660) 0 + +Class QLatin1String + size=16 align=8 + base size=16 base align=8 +QLatin1String (0x0x7ff712637840) 0 + +Class QStringDataPtr + size=8 align=8 + base size=8 base align=8 +QStringDataPtr (0x0x7ff712637a80) 0 + +Class QString::Null + size=1 align=1 + base size=0 base align=1 +QString::Null (0x0x7ff712637b40) 0 empty + +Class QString + size=8 align=8 + base size=8 base align=8 +QString (0x0x7ff712637ae0) 0 + +Class QCharRef + size=16 align=8 + base size=12 base align=8 +QCharRef (0x0x7ff712463ae0) 0 + +Class QStringRef + size=16 align=8 + base size=16 base align=8 +QStringRef (0x0x7ff7121f6660) 0 + +Class QtPrivate::QHashCombine + size=1 align=1 + base size=0 base align=1 +QtPrivate::QHashCombine (0x0x7ff7121f6a80) 0 empty + +Class QtPrivate::QHashCombineCommutative + size=1 align=1 + base size=0 base align=1 +QtPrivate::QHashCombineCommutative (0x0x7ff7121f6ae0) 0 empty + +Class std::__detail::_List_node_base + size=16 align=8 + base size=16 base align=8 +std::__detail::_List_node_base (0x0x7ff7121f6b40) 0 + +Class QListData::NotArrayCompatibleLayout + size=1 align=1 + base size=0 base align=1 +QListData::NotArrayCompatibleLayout (0x0x7ff7121f6f00) 0 empty + +Class QListData::NotIndirectLayout + size=1 align=1 + base size=0 base align=1 +QListData::NotIndirectLayout (0x0x7ff7121f6f60) 0 empty + +Class QListData::ArrayCompatibleLayout + size=1 align=1 + base size=1 base align=1 +QListData::ArrayCompatibleLayout (0x0x7ff7121fc618) 0 empty + QListData::NotIndirectLayout (0x0x7ff711ff3000) 0 empty + +Class QListData::InlineWithPaddingLayout + size=1 align=1 + base size=1 base align=1 +QListData::InlineWithPaddingLayout (0x0x7ff711fec850) 0 empty + QListData::NotArrayCompatibleLayout (0x0x7ff711ff3060) 0 empty + QListData::NotIndirectLayout (0x0x7ff711ff30c0) 0 empty + +Class QListData::IndirectLayout + size=1 align=1 + base size=1 base align=1 +QListData::IndirectLayout (0x0x7ff7121fc680) 0 empty + QListData::NotArrayCompatibleLayout (0x0x7ff711ff3120) 0 empty + +Class QListData::Data + size=24 align=8 + base size=24 base align=8 +QListData::Data (0x0x7ff711ff3180) 0 + +Class QListData + size=8 align=8 + base size=8 base align=8 +QListData (0x0x7ff7121f6ea0) 0 + +Class QRegExp + size=8 align=8 + base size=8 base align=8 +QRegExp (0x0x7ff711ff3d20) 0 + +Class QStringMatcher::Data + size=272 align=8 + base size=272 base align=8 +QStringMatcher::Data (0x0x7ff712181e40) 0 + +Class QStringMatcher + size=1048 align=8 + base size=1048 base align=8 +QStringMatcher (0x0x7ff712181de0) 0 + +Class QStringList + size=8 align=8 + base size=8 base align=8 +QStringList (0x0x7ff712186f08) 0 + QList (0x0x7ff712186f70) 0 + QListSpecialMethods (0x0x7ff7121bc060) 0 empty + +Class QScopedPointerPodDeleter + size=1 align=1 + base size=0 base align=1 +QScopedPointerPodDeleter (0x0x7ff7121bc360) 0 empty + +Class std::_Rb_tree_node_base + size=32 align=8 + base size=32 base align=8 +std::_Rb_tree_node_base (0x0x7ff7121bc780) 0 + +Class std::allocator_arg_t + size=1 align=1 + base size=0 base align=1 +std::allocator_arg_t (0x0x7ff7121bcde0) 0 empty + +Class std::__uses_alloc_base + size=1 align=1 + base size=0 base align=1 +std::__uses_alloc_base (0x0x7ff7121bcf60) 0 empty + +Class std::__uses_alloc0::_Sink + size=1 align=1 + base size=0 base align=1 +std::__uses_alloc0::_Sink (0x0x7ff711c36060) 0 empty + +Class std::__uses_alloc0 + size=1 align=1 + base size=1 base align=1 +std::__uses_alloc0 (0x0x7ff711dfdf70) 0 + std::__uses_alloc_base (0x0x7ff711c36000) 0 empty + +Class std::_Swallow_assign + size=1 align=1 + base size=0 base align=1 +std::_Swallow_assign (0x0x7ff711d2c0c0) 0 empty + +Class QtPrivate::AbstractDebugStreamFunction + size=16 align=8 + base size=16 base align=8 +QtPrivate::AbstractDebugStreamFunction (0x0x7ff711d2c300) 0 + +Class QtPrivate::AbstractComparatorFunction + size=24 align=8 + base size=24 base align=8 +QtPrivate::AbstractComparatorFunction (0x0x7ff711d2c3c0) 0 + +Class QtPrivate::AbstractConverterFunction + size=8 align=8 + base size=8 base align=8 +QtPrivate::AbstractConverterFunction (0x0x7ff711d2c4e0) 0 + +Class QMetaType + size=80 align=8 + base size=80 base align=8 +QMetaType (0x0x7ff711d2c660) 0 + +Class QtMetaTypePrivate::VariantData + size=24 align=8 + base size=20 base align=8 +QtMetaTypePrivate::VariantData (0x0x7ff711d2ca20) 0 + +Class QtMetaTypePrivate::VectorBoolElements + size=1 align=1 + base size=0 base align=1 +QtMetaTypePrivate::VectorBoolElements (0x0x7ff711d2cb40) 0 empty + +Class QtMetaTypePrivate::QSequentialIterableImpl + size=104 align=8 + base size=104 base align=8 +QtMetaTypePrivate::QSequentialIterableImpl (0x0x7ff711ab54e0) 0 + +Class QtMetaTypePrivate::QAssociativeIterableImpl + size=112 align=8 + base size=112 base align=8 +QtMetaTypePrivate::QAssociativeIterableImpl (0x0x7ff711ab58a0) 0 + +Class QtMetaTypePrivate::QPairVariantInterfaceImpl + size=40 align=8 + base size=40 base align=8 +QtMetaTypePrivate::QPairVariantInterfaceImpl (0x0x7ff711ab5ae0) 0 + +Class QtPrivate::QSlotObjectBase + size=16 align=8 + base size=16 base align=8 +QtPrivate::QSlotObjectBase (0x0x7ff7118cf840) 0 + +Vtable for QObjectData +QObjectData::_ZTV11QObjectData: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QObjectData) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))__cxa_pure_virtual + +Class QObjectData + size=48 align=8 + base size=48 base align=8 +QObjectData (0x0x7ff7118cf9c0) 0 + vptr=((& QObjectData::_ZTV11QObjectData) + 16u) + +Class QObject::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QObject::QPrivateSignal (0x0x7ff7118cfba0) 0 empty + +Vtable for QObject +QObject::_ZTV7QObject: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QObject) +16 (int (*)(...))QObject::metaObject +24 (int (*)(...))QObject::qt_metacast +32 (int (*)(...))QObject::qt_metacall +40 (int (*)(...))QObject::~QObject +48 (int (*)(...))QObject::~QObject +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QObject + size=16 align=8 + base size=16 base align=8 +QObject (0x0x7ff7118cfb40) 0 + vptr=((& QObject::_ZTV7QObject) + 16u) + +Vtable for QObjectUserData +QObjectUserData::_ZTV15QObjectUserData: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QObjectUserData) +16 (int (*)(...))QObjectUserData::~QObjectUserData +24 (int (*)(...))QObjectUserData::~QObjectUserData + +Class QObjectUserData + size=8 align=8 + base size=8 base align=8 +QObjectUserData (0x0x7ff7118cff00) 0 nearly-empty + vptr=((& QObjectUserData::_ZTV15QObjectUserData) + 16u) + +Class QSignalBlocker + size=16 align=8 + base size=10 base align=8 +QSignalBlocker (0x0x7ff7118cff60) 0 + +Class QAbstractAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractAnimation::QPrivateSignal (0x0x7ff7115d6060) 0 empty + +Vtable for QAbstractAnimation +QAbstractAnimation::_ZTV18QAbstractAnimation: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractAnimation) +16 (int (*)(...))QAbstractAnimation::metaObject +24 (int (*)(...))QAbstractAnimation::qt_metacast +32 (int (*)(...))QAbstractAnimation::qt_metacall +40 0u +48 0u +56 (int (*)(...))QAbstractAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QAbstractAnimation + size=16 align=8 + base size=16 base align=8 +QAbstractAnimation (0x0x7ff711b9c8f0) 0 + vptr=((& QAbstractAnimation::_ZTV18QAbstractAnimation) + 16u) + QObject (0x0x7ff7115d6000) 0 + primary-for QAbstractAnimation (0x0x7ff711b9c8f0) + +Class QAnimationDriver::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAnimationDriver::QPrivateSignal (0x0x7ff7115d6120) 0 empty + +Vtable for QAnimationDriver +QAnimationDriver::_ZTV16QAnimationDriver: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QAnimationDriver) +16 (int (*)(...))QAnimationDriver::metaObject +24 (int (*)(...))QAnimationDriver::qt_metacast +32 (int (*)(...))QAnimationDriver::qt_metacall +40 (int (*)(...))QAnimationDriver::~QAnimationDriver +48 (int (*)(...))QAnimationDriver::~QAnimationDriver +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAnimationDriver::advance +120 (int (*)(...))QAnimationDriver::elapsed +128 (int (*)(...))QAnimationDriver::start +136 (int (*)(...))QAnimationDriver::stop + +Class QAnimationDriver + size=16 align=8 + base size=16 base align=8 +QAnimationDriver (0x0x7ff711b9c958) 0 + vptr=((& QAnimationDriver::_ZTV16QAnimationDriver) + 16u) + QObject (0x0x7ff7115d60c0) 0 + primary-for QAnimationDriver (0x0x7ff711b9c958) + +Class QAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAnimationGroup::QPrivateSignal (0x0x7ff7115d61e0) 0 empty + +Vtable for QAnimationGroup +QAnimationGroup::_ZTV15QAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QAnimationGroup) +16 (int (*)(...))QAnimationGroup::metaObject +24 (int (*)(...))QAnimationGroup::qt_metacast +32 (int (*)(...))QAnimationGroup::qt_metacall +40 0u +48 0u +56 (int (*)(...))QAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QAnimationGroup + size=16 align=8 + base size=16 base align=8 +QAnimationGroup (0x0x7ff711b9c9c0) 0 + vptr=((& QAnimationGroup::_ZTV15QAnimationGroup) + 16u) + QAbstractAnimation (0x0x7ff711b9ca28) 0 + primary-for QAnimationGroup (0x0x7ff711b9c9c0) + QObject (0x0x7ff7115d6180) 0 + primary-for QAbstractAnimation (0x0x7ff711b9ca28) + +Class QParallelAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QParallelAnimationGroup::QPrivateSignal (0x0x7ff7115d62a0) 0 empty + +Vtable for QParallelAnimationGroup +QParallelAnimationGroup::_ZTV23QParallelAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QParallelAnimationGroup) +16 (int (*)(...))QParallelAnimationGroup::metaObject +24 (int (*)(...))QParallelAnimationGroup::qt_metacast +32 (int (*)(...))QParallelAnimationGroup::qt_metacall +40 (int (*)(...))QParallelAnimationGroup::~QParallelAnimationGroup +48 (int (*)(...))QParallelAnimationGroup::~QParallelAnimationGroup +56 (int (*)(...))QParallelAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QParallelAnimationGroup::duration +120 (int (*)(...))QParallelAnimationGroup::updateCurrentTime +128 (int (*)(...))QParallelAnimationGroup::updateState +136 (int (*)(...))QParallelAnimationGroup::updateDirection + +Class QParallelAnimationGroup + size=16 align=8 + base size=16 base align=8 +QParallelAnimationGroup (0x0x7ff711b9ca90) 0 + vptr=((& QParallelAnimationGroup::_ZTV23QParallelAnimationGroup) + 16u) + QAnimationGroup (0x0x7ff711b9caf8) 0 + primary-for QParallelAnimationGroup (0x0x7ff711b9ca90) + QAbstractAnimation (0x0x7ff711b9cb60) 0 + primary-for QAnimationGroup (0x0x7ff711b9caf8) + QObject (0x0x7ff7115d6240) 0 + primary-for QAbstractAnimation (0x0x7ff711b9cb60) + +Class QPauseAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPauseAnimation::QPrivateSignal (0x0x7ff7115d6360) 0 empty + +Vtable for QPauseAnimation +QPauseAnimation::_ZTV15QPauseAnimation: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QPauseAnimation) +16 (int (*)(...))QPauseAnimation::metaObject +24 (int (*)(...))QPauseAnimation::qt_metacast +32 (int (*)(...))QPauseAnimation::qt_metacall +40 (int (*)(...))QPauseAnimation::~QPauseAnimation +48 (int (*)(...))QPauseAnimation::~QPauseAnimation +56 (int (*)(...))QPauseAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QPauseAnimation::duration +120 (int (*)(...))QPauseAnimation::updateCurrentTime +128 (int (*)(...))QAbstractAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection + +Class QPauseAnimation + size=16 align=8 + base size=16 base align=8 +QPauseAnimation (0x0x7ff711b9cbc8) 0 + vptr=((& QPauseAnimation::_ZTV15QPauseAnimation) + 16u) + QAbstractAnimation (0x0x7ff711b9cc30) 0 + primary-for QPauseAnimation (0x0x7ff711b9cbc8) + QObject (0x0x7ff7115d6300) 0 + primary-for QAbstractAnimation (0x0x7ff711b9cc30) + +Class QEasingCurve + size=8 align=8 + base size=8 base align=8 +QEasingCurve (0x0x7ff7116b36c0) 0 + +Class QMapNodeBase + size=24 align=8 + base size=24 base align=8 +QMapNodeBase (0x0x7ff711782780) 0 + +Class QMapDataBase + size=40 align=8 + base size=40 base align=8 +QMapDataBase (0x0x7ff711782840) 0 + +Class QHashData::Node + size=16 align=8 + base size=16 base align=8 +QHashData::Node (0x0x7ff711782c00) 0 + +Class QHashData + size=48 align=8 + base size=44 base align=8 +QHashData (0x0x7ff711782ba0) 0 + +Class QHashDummyValue + size=1 align=1 + base size=0 base align=1 +QHashDummyValue (0x0x7ff711782c60) 0 empty + +Class QVariant::PrivateShared + size=16 align=8 + base size=12 base align=8 +QVariant::PrivateShared (0x0x7ff7114ba6c0) 0 + +Class QVariant::Private::Data + size=8 align=8 + base size=8 base align=8 +QVariant::Private::Data (0x0x7ff7114ba780) 0 + +Class QVariant::Private + size=16 align=8 + base size=12 base align=8 +QVariant::Private (0x0x7ff7114ba720) 0 + +Class QVariant::Handler + size=72 align=8 + base size=72 base align=8 +QVariant::Handler (0x0x7ff7114ba7e0) 0 + +Class QVariant + size=16 align=8 + base size=16 base align=8 +QVariant (0x0x7ff7114ba660) 0 + +Class QVariantComparisonHelper + size=8 align=8 + base size=8 base align=8 +QVariantComparisonHelper (0x0x7ff71123cae0) 0 + +Class QSequentialIterable::const_iterator + size=112 align=8 + base size=112 base align=8 +QSequentialIterable::const_iterator (0x0x7ff71123ccc0) 0 + +Class QSequentialIterable + size=104 align=8 + base size=104 base align=8 +QSequentialIterable (0x0x7ff71123cc60) 0 + +Class QAssociativeIterable::const_iterator + size=120 align=8 + base size=120 base align=8 +QAssociativeIterable::const_iterator (0x0x7ff71123cd80) 0 + +Class QAssociativeIterable + size=112 align=8 + base size=112 base align=8 +QAssociativeIterable (0x0x7ff71123cd20) 0 + +Class QVariantAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QVariantAnimation::QPrivateSignal (0x0x7ff710fcc720) 0 empty + +Vtable for QVariantAnimation +QVariantAnimation::_ZTV17QVariantAnimation: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QVariantAnimation) +16 (int (*)(...))QVariantAnimation::metaObject +24 (int (*)(...))QVariantAnimation::qt_metacast +32 (int (*)(...))QVariantAnimation::qt_metacall +40 (int (*)(...))QVariantAnimation::~QVariantAnimation +48 (int (*)(...))QVariantAnimation::~QVariantAnimation +56 (int (*)(...))QVariantAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QVariantAnimation::duration +120 (int (*)(...))QVariantAnimation::updateCurrentTime +128 (int (*)(...))QVariantAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection +144 (int (*)(...))QVariantAnimation::updateCurrentValue +152 (int (*)(...))QVariantAnimation::interpolated + +Class QVariantAnimation + size=16 align=8 + base size=16 base align=8 +QVariantAnimation (0x0x7ff710fd7208) 0 + vptr=((& QVariantAnimation::_ZTV17QVariantAnimation) + 16u) + QAbstractAnimation (0x0x7ff710fd7270) 0 + primary-for QVariantAnimation (0x0x7ff710fd7208) + QObject (0x0x7ff710fcc6c0) 0 + primary-for QAbstractAnimation (0x0x7ff710fd7270) + +Class QPropertyAnimation::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPropertyAnimation::QPrivateSignal (0x0x7ff710fcc7e0) 0 empty + +Vtable for QPropertyAnimation +QPropertyAnimation::_ZTV18QPropertyAnimation: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QPropertyAnimation) +16 (int (*)(...))QPropertyAnimation::metaObject +24 (int (*)(...))QPropertyAnimation::qt_metacast +32 (int (*)(...))QPropertyAnimation::qt_metacall +40 (int (*)(...))QPropertyAnimation::~QPropertyAnimation +48 (int (*)(...))QPropertyAnimation::~QPropertyAnimation +56 (int (*)(...))QPropertyAnimation::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QVariantAnimation::duration +120 (int (*)(...))QVariantAnimation::updateCurrentTime +128 (int (*)(...))QPropertyAnimation::updateState +136 (int (*)(...))QAbstractAnimation::updateDirection +144 (int (*)(...))QPropertyAnimation::updateCurrentValue +152 (int (*)(...))QVariantAnimation::interpolated + +Class QPropertyAnimation + size=16 align=8 + base size=16 base align=8 +QPropertyAnimation (0x0x7ff710fd7340) 0 + vptr=((& QPropertyAnimation::_ZTV18QPropertyAnimation) + 16u) + QVariantAnimation (0x0x7ff710fd73a8) 0 + primary-for QPropertyAnimation (0x0x7ff710fd7340) + QAbstractAnimation (0x0x7ff710fd7410) 0 + primary-for QVariantAnimation (0x0x7ff710fd73a8) + QObject (0x0x7ff710fcc780) 0 + primary-for QAbstractAnimation (0x0x7ff710fd7410) + +Class QSequentialAnimationGroup::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSequentialAnimationGroup::QPrivateSignal (0x0x7ff710fcc8a0) 0 empty + +Vtable for QSequentialAnimationGroup +QSequentialAnimationGroup::_ZTV25QSequentialAnimationGroup: 18u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI25QSequentialAnimationGroup) +16 (int (*)(...))QSequentialAnimationGroup::metaObject +24 (int (*)(...))QSequentialAnimationGroup::qt_metacast +32 (int (*)(...))QSequentialAnimationGroup::qt_metacall +40 (int (*)(...))QSequentialAnimationGroup::~QSequentialAnimationGroup +48 (int (*)(...))QSequentialAnimationGroup::~QSequentialAnimationGroup +56 (int (*)(...))QSequentialAnimationGroup::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSequentialAnimationGroup::duration +120 (int (*)(...))QSequentialAnimationGroup::updateCurrentTime +128 (int (*)(...))QSequentialAnimationGroup::updateState +136 (int (*)(...))QSequentialAnimationGroup::updateDirection + +Class QSequentialAnimationGroup + size=16 align=8 + base size=16 base align=8 +QSequentialAnimationGroup (0x0x7ff710fd7478) 0 + vptr=((& QSequentialAnimationGroup::_ZTV25QSequentialAnimationGroup) + 16u) + QAnimationGroup (0x0x7ff710fd74e0) 0 + primary-for QSequentialAnimationGroup (0x0x7ff710fd7478) + QAbstractAnimation (0x0x7ff710fd7548) 0 + primary-for QAnimationGroup (0x0x7ff710fd74e0) + QObject (0x0x7ff710fcc840) 0 + primary-for QAbstractAnimation (0x0x7ff710fd7548) + +Class QTextCodec::ConverterState + size=32 align=8 + base size=32 base align=8 +QTextCodec::ConverterState (0x0x7ff710fcc960) 0 + +Vtable for QTextCodec +QTextCodec::_ZTV10QTextCodec: 9u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QTextCodec) +16 (int (*)(...))__cxa_pure_virtual +24 (int (*)(...))QTextCodec::aliases +32 (int (*)(...))__cxa_pure_virtual +40 (int (*)(...))__cxa_pure_virtual +48 (int (*)(...))__cxa_pure_virtual +56 0u +64 0u + +Class QTextCodec + size=8 align=8 + base size=8 base align=8 +QTextCodec (0x0x7ff710fcc900) 0 nearly-empty + vptr=((& QTextCodec::_ZTV10QTextCodec) + 16u) + +Class QTextEncoder + size=40 align=8 + base size=40 base align=8 +QTextEncoder (0x0x7ff710fccae0) 0 + +Class QTextDecoder + size=40 align=8 + base size=40 base align=8 +QTextDecoder (0x0x7ff710fccb40) 0 + +Class QSharedData + size=4 align=4 + base size=4 base align=4 +QSharedData (0x0x7ff710fccba0) 0 + +Class QDate + size=8 align=8 + base size=8 base align=8 +QDate (0x0x7ff710fccd80) 0 + +Class QTime + size=4 align=4 + base size=4 base align=4 +QTime (0x0x7ff710fccf00) 0 + +Class QDateTime + size=8 align=8 + base size=8 base align=8 +QDateTime (0x0x7ff7111170c0) 0 + +Class QLibraryInfo + size=1 align=1 + base size=0 base align=1 +QLibraryInfo (0x0x7ff7111172a0) 0 empty + +Class QIODevice::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QIODevice::QPrivateSignal (0x0x7ff711117360) 0 empty + +Vtable for QIODevice +QIODevice::_ZTV9QIODevice: 30u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QIODevice) +16 (int (*)(...))QIODevice::metaObject +24 (int (*)(...))QIODevice::qt_metacast +32 (int (*)(...))QIODevice::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QIODevice::isSequential +120 (int (*)(...))QIODevice::open +128 (int (*)(...))QIODevice::close +136 (int (*)(...))QIODevice::pos +144 (int (*)(...))QIODevice::size +152 (int (*)(...))QIODevice::seek +160 (int (*)(...))QIODevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))__cxa_pure_virtual +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))__cxa_pure_virtual + +Class QIODevice + size=16 align=8 + base size=16 base align=8 +QIODevice (0x0x7ff710fd77b8) 0 + vptr=((& QIODevice::_ZTV9QIODevice) + 16u) + QObject (0x0x7ff711117300) 0 + primary-for QIODevice (0x0x7ff710fd77b8) + +Class QBuffer::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QBuffer::QPrivateSignal (0x0x7ff711117540) 0 empty + +Vtable for QBuffer +QBuffer::_ZTV7QBuffer: 30u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QBuffer) +16 (int (*)(...))QBuffer::metaObject +24 (int (*)(...))QBuffer::qt_metacast +32 (int (*)(...))QBuffer::qt_metacall +40 (int (*)(...))QBuffer::~QBuffer +48 (int (*)(...))QBuffer::~QBuffer +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QBuffer::connectNotify +104 (int (*)(...))QBuffer::disconnectNotify +112 (int (*)(...))QIODevice::isSequential +120 (int (*)(...))QBuffer::open +128 (int (*)(...))QBuffer::close +136 (int (*)(...))QBuffer::pos +144 (int (*)(...))QBuffer::size +152 (int (*)(...))QBuffer::seek +160 (int (*)(...))QBuffer::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QBuffer::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QBuffer::readData +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))QBuffer::writeData + +Class QBuffer + size=16 align=8 + base size=16 base align=8 +QBuffer (0x0x7ff710fd78f0) 0 + vptr=((& QBuffer::_ZTV7QBuffer) + 16u) + QIODevice (0x0x7ff710fd7958) 0 + primary-for QBuffer (0x0x7ff710fd78f0) + QObject (0x0x7ff7111174e0) 0 + primary-for QIODevice (0x0x7ff710fd7958) + +Class QDataStream + size=32 align=8 + base size=32 base align=8 +QDataStream (0x0x7ff7111175a0) 0 + +Class QLocale + size=8 align=8 + base size=8 base align=8 +QLocale (0x0x7ff711117660) 0 + +Vtable for QTextStream +QTextStream::_ZTV11QTextStream: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTextStream) +16 (int (*)(...))QTextStream::~QTextStream +24 (int (*)(...))QTextStream::~QTextStream + +Class QTextStream + size=16 align=8 + base size=16 base align=8 +QTextStream (0x0x7ff711117960) 0 + vptr=((& QTextStream::_ZTV11QTextStream) + 16u) + +Class QTextStreamManipulator + size=40 align=8 + base size=38 base align=8 +QTextStreamManipulator (0x0x7ff711117ba0) 0 + +Class QContiguousCacheData + size=24 align=4 + base size=24 base align=4 +QContiguousCacheData (0x0x7ff711117de0) 0 + +Class QtSharedPointer::NormalDeleter + size=1 align=1 + base size=0 base align=1 +QtSharedPointer::NormalDeleter (0x0x7ff710b800c0) 0 empty + +Class QtSharedPointer::ExternalRefCountData + size=16 align=8 + base size=16 base align=8 +QtSharedPointer::ExternalRefCountData (0x0x7ff710b80240) 0 + +Class QDebug::Stream + size=80 align=8 + base size=76 base align=8 +QDebug::Stream (0x0x7ff710b806c0) 0 + +Class QDebug + size=8 align=8 + base size=8 base align=8 +QDebug (0x0x7ff710b80660) 0 + +Class QDebugStateSaver + size=8 align=8 + base size=8 base align=8 +QDebugStateSaver (0x0x7ff710d08780) 0 + +Class QNoDebug + size=1 align=1 + base size=0 base align=1 +QNoDebug (0x0x7ff710d08840) 0 empty + +Class QFileDevice::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileDevice::QPrivateSignal (0x0x7ff710d08900) 0 empty + +Vtable for QFileDevice +QFileDevice::_ZTV11QFileDevice: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QFileDevice) +16 (int (*)(...))QFileDevice::metaObject +24 (int (*)(...))QFileDevice::qt_metacast +32 (int (*)(...))QFileDevice::qt_metacall +40 (int (*)(...))QFileDevice::~QFileDevice +48 (int (*)(...))QFileDevice::~QFileDevice +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QIODevice::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFileDevice::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QFileDevice::fileName +248 (int (*)(...))QFileDevice::resize +256 (int (*)(...))QFileDevice::permissions +264 (int (*)(...))QFileDevice::setPermissions + +Class QFileDevice + size=16 align=8 + base size=16 base align=8 +QFileDevice (0x0x7ff710d09888) 0 + vptr=((& QFileDevice::_ZTV11QFileDevice) + 16u) + QIODevice (0x0x7ff710d098f0) 0 + primary-for QFileDevice (0x0x7ff710d09888) + QObject (0x0x7ff710d088a0) 0 + primary-for QIODevice (0x0x7ff710d098f0) + +Class QFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFile::QPrivateSignal (0x0x7ff710d08ae0) 0 empty + +Vtable for QFile +QFile::_ZTV5QFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI5QFile) +16 (int (*)(...))QFile::metaObject +24 (int (*)(...))QFile::qt_metacast +32 (int (*)(...))QFile::qt_metacall +40 (int (*)(...))QFile::~QFile +48 (int (*)(...))QFile::~QFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QFile::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFile::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QFile::fileName +248 (int (*)(...))QFile::resize +256 (int (*)(...))QFile::permissions +264 (int (*)(...))QFile::setPermissions + +Class QFile + size=16 align=8 + base size=16 base align=8 +QFile (0x0x7ff710d09a28) 0 + vptr=((& QFile::_ZTV5QFile) + 16u) + QFileDevice (0x0x7ff710d09a90) 0 + primary-for QFile (0x0x7ff710d09a28) + QIODevice (0x0x7ff710d09af8) 0 + primary-for QFileDevice (0x0x7ff710d09a90) + QObject (0x0x7ff710d08a80) 0 + primary-for QIODevice (0x0x7ff710d09af8) + +Class QFileInfo + size=8 align=8 + base size=8 base align=8 +QFileInfo (0x0x7ff710d08c60) 0 + +Class QDir + size=8 align=8 + base size=8 base align=8 +QDir (0x0x7ff710d08f60) 0 + +Class QDirIterator + size=8 align=8 + base size=8 base align=8 +QDirIterator (0x0x7ff710a503c0) 0 + +Class QFileSelector::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileSelector::QPrivateSignal (0x0x7ff710a50600) 0 empty + +Vtable for QFileSelector +QFileSelector::_ZTV13QFileSelector: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QFileSelector) +16 (int (*)(...))QFileSelector::metaObject +24 (int (*)(...))QFileSelector::qt_metacast +32 (int (*)(...))QFileSelector::qt_metacall +40 (int (*)(...))QFileSelector::~QFileSelector +48 (int (*)(...))QFileSelector::~QFileSelector +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QFileSelector + size=16 align=8 + base size=16 base align=8 +QFileSelector (0x0x7ff710af2000) 0 + vptr=((& QFileSelector::_ZTV13QFileSelector) + 16u) + QObject (0x0x7ff710a505a0) 0 + primary-for QFileSelector (0x0x7ff710af2000) + +Class QFileSystemWatcher::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFileSystemWatcher::QPrivateSignal (0x0x7ff710a506c0) 0 empty + +Vtable for QFileSystemWatcher +QFileSystemWatcher::_ZTV18QFileSystemWatcher: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QFileSystemWatcher) +16 (int (*)(...))QFileSystemWatcher::metaObject +24 (int (*)(...))QFileSystemWatcher::qt_metacast +32 (int (*)(...))QFileSystemWatcher::qt_metacall +40 (int (*)(...))QFileSystemWatcher::~QFileSystemWatcher +48 (int (*)(...))QFileSystemWatcher::~QFileSystemWatcher +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QFileSystemWatcher + size=16 align=8 + base size=16 base align=8 +QFileSystemWatcher (0x0x7ff710af2068) 0 + vptr=((& QFileSystemWatcher::_ZTV18QFileSystemWatcher) + 16u) + QObject (0x0x7ff710a50660) 0 + primary-for QFileSystemWatcher (0x0x7ff710af2068) + +Class QLockFile + size=8 align=8 + base size=8 base align=8 +QLockFile (0x0x7ff710a50720) 0 + +Class QLoggingCategory::AtomicBools + size=4 align=1 + base size=4 base align=1 +QLoggingCategory::AtomicBools (0x0x7ff710a508a0) 0 + +Class QLoggingCategory + size=24 align=8 + base size=24 base align=8 +QLoggingCategory (0x0x7ff710a50840) 0 + +Vtable for std::type_info +std::type_info::_ZTVSt9type_info: 8u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt9type_info) +16 (int (*)(...))std::type_info::~type_info +24 (int (*)(...))std::type_info::~type_info +32 (int (*)(...))std::type_info::__is_pointer_p +40 (int (*)(...))std::type_info::__is_function_p +48 (int (*)(...))std::type_info::__do_catch +56 (int (*)(...))std::type_info::__do_upcast + +Class std::type_info + size=16 align=8 + base size=16 base align=8 +std::type_info (0x0x7ff710a50a20) 0 + vptr=((& std::type_info::_ZTVSt9type_info) + 16u) + +Vtable for std::bad_cast +std::bad_cast::_ZTVSt8bad_cast: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt8bad_cast) +16 (int (*)(...))std::bad_cast::~bad_cast +24 (int (*)(...))std::bad_cast::~bad_cast +32 (int (*)(...))std::bad_cast::what + +Class std::bad_cast + size=8 align=8 + base size=8 base align=8 +std::bad_cast (0x0x7ff710af2138) 0 nearly-empty + vptr=((& std::bad_cast::_ZTVSt8bad_cast) + 16u) + std::exception (0x0x7ff710a50a80) 0 nearly-empty + primary-for std::bad_cast (0x0x7ff710af2138) + +Vtable for std::bad_typeid +std::bad_typeid::_ZTVSt10bad_typeid: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt10bad_typeid) +16 (int (*)(...))std::bad_typeid::~bad_typeid +24 (int (*)(...))std::bad_typeid::~bad_typeid +32 (int (*)(...))std::bad_typeid::what + +Class std::bad_typeid + size=8 align=8 + base size=8 base align=8 +std::bad_typeid (0x0x7ff710af21a0) 0 nearly-empty + vptr=((& std::bad_typeid::_ZTVSt10bad_typeid) + 16u) + std::exception (0x0x7ff710a50ae0) 0 nearly-empty + primary-for std::bad_typeid (0x0x7ff710af21a0) + +Vtable for std::bad_function_call +std::bad_function_call::_ZTVSt17bad_function_call: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTISt17bad_function_call) +16 (int (*)(...))std::bad_function_call::~bad_function_call +24 (int (*)(...))std::bad_function_call::~bad_function_call +32 (int (*)(...))std::bad_function_call::what + +Class std::bad_function_call + size=8 align=8 + base size=8 base align=8 +std::bad_function_call (0x0x7ff7108d5410) 0 nearly-empty + vptr=((& std::bad_function_call::_ZTVSt17bad_function_call) + 16u) + std::exception (0x0x7ff710873ba0) 0 nearly-empty + primary-for std::bad_function_call (0x0x7ff7108d5410) + +Class std::_Nocopy_types + size=16 align=8 + base size=16 base align=8 +std::_Nocopy_types (0x0x7ff710873c60) 0 + +Class std::_Any_data + size=16 align=8 + base size=16 base align=8 +std::_Any_data (0x0x7ff710873cc0) 0 + +Class std::_Function_base + size=24 align=8 + base size=24 base align=8 +std::_Function_base (0x0x7ff710873de0) 0 + +Class QProcessEnvironment + size=8 align=8 + base size=8 base align=8 +QProcessEnvironment (0x0x7ff71093d300) 0 + +Class QProcess::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QProcess::QPrivateSignal (0x0x7ff71093d540) 0 empty + +Vtable for QProcess +QProcess::_ZTV8QProcess: 31u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI8QProcess) +16 (int (*)(...))QProcess::metaObject +24 (int (*)(...))QProcess::qt_metacast +32 (int (*)(...))QProcess::qt_metacall +40 (int (*)(...))QProcess::~QProcess +48 (int (*)(...))QProcess::~QProcess +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QProcess::isSequential +120 (int (*)(...))QProcess::open +128 (int (*)(...))QProcess::close +136 (int (*)(...))QIODevice::pos +144 (int (*)(...))QIODevice::size +152 (int (*)(...))QIODevice::seek +160 (int (*)(...))QProcess::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QProcess::bytesAvailable +184 (int (*)(...))QProcess::bytesToWrite +192 (int (*)(...))QProcess::canReadLine +200 (int (*)(...))QProcess::waitForReadyRead +208 (int (*)(...))QProcess::waitForBytesWritten +216 (int (*)(...))QProcess::readData +224 (int (*)(...))QIODevice::readLineData +232 (int (*)(...))QProcess::writeData +240 (int (*)(...))QProcess::setupChildProcess + +Class QProcess + size=16 align=8 + base size=16 base align=8 +QProcess (0x0x7ff7108d5c30) 0 + vptr=((& QProcess::_ZTV8QProcess) + 16u) + QIODevice (0x0x7ff7108d5c98) 0 + primary-for QProcess (0x0x7ff7108d5c30) + QObject (0x0x7ff71093d4e0) 0 + primary-for QIODevice (0x0x7ff7108d5c98) + +Class QResource + size=8 align=8 + base size=8 base align=8 +QResource (0x0x7ff71093d5a0) 0 + +Class QSaveFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSaveFile::QPrivateSignal (0x0x7ff71093d720) 0 empty + +Vtable for QSaveFile +QSaveFile::_ZTV9QSaveFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QSaveFile) +16 (int (*)(...))QSaveFile::metaObject +24 (int (*)(...))QSaveFile::qt_metacast +32 (int (*)(...))QSaveFile::qt_metacall +40 (int (*)(...))QSaveFile::~QSaveFile +48 (int (*)(...))QSaveFile::~QSaveFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QSaveFile::open +128 (int (*)(...))QSaveFile::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFileDevice::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QSaveFile::writeData +240 (int (*)(...))QSaveFile::fileName +248 (int (*)(...))QFileDevice::resize +256 (int (*)(...))QFileDevice::permissions +264 (int (*)(...))QFileDevice::setPermissions + +Class QSaveFile + size=16 align=8 + base size=16 base align=8 +QSaveFile (0x0x7ff7108d5d00) 0 + vptr=((& QSaveFile::_ZTV9QSaveFile) + 16u) + QFileDevice (0x0x7ff7108d5d68) 0 + primary-for QSaveFile (0x0x7ff7108d5d00) + QIODevice (0x0x7ff7108d5dd0) 0 + primary-for QFileDevice (0x0x7ff7108d5d68) + QObject (0x0x7ff71093d6c0) 0 + primary-for QIODevice (0x0x7ff7108d5dd0) + +Class QSettings::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSettings::QPrivateSignal (0x0x7ff71093d7e0) 0 empty + +Vtable for QSettings +QSettings::_ZTV9QSettings: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QSettings) +16 (int (*)(...))QSettings::metaObject +24 (int (*)(...))QSettings::qt_metacast +32 (int (*)(...))QSettings::qt_metacall +40 (int (*)(...))QSettings::~QSettings +48 (int (*)(...))QSettings::~QSettings +56 (int (*)(...))QSettings::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSettings + size=16 align=8 + base size=16 base align=8 +QSettings (0x0x7ff7108d5e38) 0 + vptr=((& QSettings::_ZTV9QSettings) + 16u) + QObject (0x0x7ff71093d780) 0 + primary-for QSettings (0x0x7ff7108d5e38) + +Class QStandardPaths + size=1 align=1 + base size=0 base align=1 +QStandardPaths (0x0x7ff71093d840) 0 empty + +Class QStorageInfo + size=8 align=8 + base size=8 base align=8 +QStorageInfo (0x0x7ff71093d9c0) 0 + +Class QTemporaryDir + size=8 align=8 + base size=8 base align=8 +QTemporaryDir (0x0x7ff71093dcc0) 0 + +Class QTemporaryFile::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTemporaryFile::QPrivateSignal (0x0x7ff71093dde0) 0 empty + +Vtable for QTemporaryFile +QTemporaryFile::_ZTV14QTemporaryFile: 34u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI14QTemporaryFile) +16 (int (*)(...))QTemporaryFile::metaObject +24 (int (*)(...))QTemporaryFile::qt_metacast +32 (int (*)(...))QTemporaryFile::qt_metacall +40 (int (*)(...))QTemporaryFile::~QTemporaryFile +48 (int (*)(...))QTemporaryFile::~QTemporaryFile +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFileDevice::isSequential +120 (int (*)(...))QTemporaryFile::open +128 (int (*)(...))QFileDevice::close +136 (int (*)(...))QFileDevice::pos +144 (int (*)(...))QFile::size +152 (int (*)(...))QFileDevice::seek +160 (int (*)(...))QFileDevice::atEnd +168 (int (*)(...))QIODevice::reset +176 (int (*)(...))QIODevice::bytesAvailable +184 (int (*)(...))QIODevice::bytesToWrite +192 (int (*)(...))QIODevice::canReadLine +200 (int (*)(...))QIODevice::waitForReadyRead +208 (int (*)(...))QIODevice::waitForBytesWritten +216 (int (*)(...))QFileDevice::readData +224 (int (*)(...))QFileDevice::readLineData +232 (int (*)(...))QFileDevice::writeData +240 (int (*)(...))QTemporaryFile::fileName +248 (int (*)(...))QFile::resize +256 (int (*)(...))QFile::permissions +264 (int (*)(...))QFile::setPermissions + +Class QTemporaryFile + size=16 align=8 + base size=16 base align=8 +QTemporaryFile (0x0x7ff71066b000) 0 + vptr=((& QTemporaryFile::_ZTV14QTemporaryFile) + 16u) + QFile (0x0x7ff71066b068) 0 + primary-for QTemporaryFile (0x0x7ff71066b000) + QFileDevice (0x0x7ff71066b0d0) 0 + primary-for QFile (0x0x7ff71066b068) + QIODevice (0x0x7ff71066b138) 0 + primary-for QFileDevice (0x0x7ff71066b0d0) + QObject (0x0x7ff71093dd80) 0 + primary-for QIODevice (0x0x7ff71066b138) + +Class QUrl + size=8 align=8 + base size=8 base align=8 +QUrl (0x0x7ff71093df00) 0 + +Class QUrlQuery + size=8 align=8 + base size=8 base align=8 +QUrlQuery (0x0x7ff71037f3c0) 0 + +Class QModelIndex + size=24 align=8 + base size=24 base align=8 +QModelIndex (0x0x7ff71037f5a0) 0 + +Class QPersistentModelIndex + size=8 align=8 + base size=8 base align=8 +QPersistentModelIndex (0x0x7ff71037f720) 0 + +Class QAbstractItemModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractItemModel::QPrivateSignal (0x0x7ff710498600) 0 empty + +Vtable for QAbstractItemModel +QAbstractItemModel::_ZTV18QAbstractItemModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractItemModel) +16 (int (*)(...))QAbstractItemModel::metaObject +24 (int (*)(...))QAbstractItemModel::qt_metacast +32 (int (*)(...))QAbstractItemModel::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractItemModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractItemModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractItemModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractItemModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractItemModel + size=16 align=8 + base size=16 base align=8 +QAbstractItemModel (0x0x7ff710484f70) 0 + vptr=((& QAbstractItemModel::_ZTV18QAbstractItemModel) + 16u) + QObject (0x0x7ff7104985a0) 0 + primary-for QAbstractItemModel (0x0x7ff710484f70) + +Class QAbstractTableModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractTableModel::QPrivateSignal (0x0x7ff710498960) 0 empty + +Vtable for QAbstractTableModel +QAbstractTableModel::_ZTV19QAbstractTableModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractTableModel) +16 (int (*)(...))QAbstractTableModel::metaObject +24 (int (*)(...))QAbstractTableModel::qt_metacast +32 (int (*)(...))QAbstractTableModel::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractTableModel::index +120 (int (*)(...))QAbstractTableModel::parent +128 (int (*)(...))QAbstractTableModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractTableModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractTableModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractTableModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractTableModel + size=16 align=8 + base size=16 base align=8 +QAbstractTableModel (0x0x7ff7104f71a0) 0 + vptr=((& QAbstractTableModel::_ZTV19QAbstractTableModel) + 16u) + QAbstractItemModel (0x0x7ff7104f7208) 0 + primary-for QAbstractTableModel (0x0x7ff7104f71a0) + QObject (0x0x7ff710498900) 0 + primary-for QAbstractItemModel (0x0x7ff7104f7208) + +Class QAbstractListModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractListModel::QPrivateSignal (0x0x7ff710498a20) 0 empty + +Vtable for QAbstractListModel +QAbstractListModel::_ZTV18QAbstractListModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QAbstractListModel) +16 (int (*)(...))QAbstractListModel::metaObject +24 (int (*)(...))QAbstractListModel::qt_metacast +32 (int (*)(...))QAbstractListModel::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractListModel::index +120 (int (*)(...))QAbstractListModel::parent +128 (int (*)(...))QAbstractListModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))QAbstractListModel::columnCount +152 (int (*)(...))QAbstractListModel::hasChildren +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))QAbstractItemModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractListModel::dropMimeData +240 (int (*)(...))QAbstractItemModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QAbstractListModel::flags +328 (int (*)(...))QAbstractItemModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QAbstractListModel + size=16 align=8 + base size=16 base align=8 +QAbstractListModel (0x0x7ff7104f7270) 0 + vptr=((& QAbstractListModel::_ZTV18QAbstractListModel) + 16u) + QAbstractItemModel (0x0x7ff7104f72d8) 0 + primary-for QAbstractListModel (0x0x7ff7104f7270) + QObject (0x0x7ff7104989c0) 0 + primary-for QAbstractItemModel (0x0x7ff7104f72d8) + +Class QAbstractProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractProxyModel::QPrivateSignal (0x0x7ff710498d20) 0 empty + +Vtable for QAbstractProxyModel +QAbstractProxyModel::_ZTV19QAbstractProxyModel: 53u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractProxyModel) +16 (int (*)(...))QAbstractProxyModel::metaObject +24 (int (*)(...))QAbstractProxyModel::qt_metacast +32 (int (*)(...))QAbstractProxyModel::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))QAbstractProxyModel::sibling +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))QAbstractProxyModel::hasChildren +160 (int (*)(...))QAbstractProxyModel::data +168 (int (*)(...))QAbstractProxyModel::setData +176 (int (*)(...))QAbstractProxyModel::headerData +184 (int (*)(...))QAbstractProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QAbstractProxyModel::mimeTypes +216 (int (*)(...))QAbstractProxyModel::mimeData +224 (int (*)(...))QAbstractProxyModel::canDropMimeData +232 (int (*)(...))QAbstractProxyModel::dropMimeData +240 (int (*)(...))QAbstractProxyModel::supportedDropActions +248 (int (*)(...))QAbstractProxyModel::supportedDragActions +256 (int (*)(...))QAbstractItemModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QAbstractItemModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractProxyModel::fetchMore +312 (int (*)(...))QAbstractProxyModel::canFetchMore +320 (int (*)(...))QAbstractProxyModel::flags +328 (int (*)(...))QAbstractProxyModel::sort +336 (int (*)(...))QAbstractProxyModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QAbstractProxyModel::setSourceModel +392 (int (*)(...))__cxa_pure_virtual +400 (int (*)(...))__cxa_pure_virtual +408 (int (*)(...))QAbstractProxyModel::mapSelectionToSource +416 (int (*)(...))QAbstractProxyModel::mapSelectionFromSource + +Class QAbstractProxyModel + size=16 align=8 + base size=16 base align=8 +QAbstractProxyModel (0x0x7ff7104f7410) 0 + vptr=((& QAbstractProxyModel::_ZTV19QAbstractProxyModel) + 16u) + QAbstractItemModel (0x0x7ff7104f7478) 0 + primary-for QAbstractProxyModel (0x0x7ff7104f7410) + QObject (0x0x7ff710498cc0) 0 + primary-for QAbstractItemModel (0x0x7ff7104f7478) + +Class QIdentityProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QIdentityProxyModel::QPrivateSignal (0x0x7ff710498de0) 0 empty + +Vtable for QIdentityProxyModel +QIdentityProxyModel::_ZTV19QIdentityProxyModel: 53u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QIdentityProxyModel) +16 (int (*)(...))QIdentityProxyModel::metaObject +24 (int (*)(...))QIdentityProxyModel::qt_metacast +32 (int (*)(...))QIdentityProxyModel::qt_metacall +40 (int (*)(...))QIdentityProxyModel::~QIdentityProxyModel +48 (int (*)(...))QIdentityProxyModel::~QIdentityProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QIdentityProxyModel::index +120 (int (*)(...))QIdentityProxyModel::parent +128 (int (*)(...))QIdentityProxyModel::sibling +136 (int (*)(...))QIdentityProxyModel::rowCount +144 (int (*)(...))QIdentityProxyModel::columnCount +152 (int (*)(...))QAbstractProxyModel::hasChildren +160 (int (*)(...))QAbstractProxyModel::data +168 (int (*)(...))QAbstractProxyModel::setData +176 (int (*)(...))QIdentityProxyModel::headerData +184 (int (*)(...))QAbstractProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QAbstractProxyModel::mimeTypes +216 (int (*)(...))QAbstractProxyModel::mimeData +224 (int (*)(...))QAbstractProxyModel::canDropMimeData +232 (int (*)(...))QIdentityProxyModel::dropMimeData +240 (int (*)(...))QAbstractProxyModel::supportedDropActions +248 (int (*)(...))QAbstractProxyModel::supportedDragActions +256 (int (*)(...))QIdentityProxyModel::insertRows +264 (int (*)(...))QIdentityProxyModel::insertColumns +272 (int (*)(...))QIdentityProxyModel::removeRows +280 (int (*)(...))QIdentityProxyModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractProxyModel::fetchMore +312 (int (*)(...))QAbstractProxyModel::canFetchMore +320 (int (*)(...))QAbstractProxyModel::flags +328 (int (*)(...))QAbstractProxyModel::sort +336 (int (*)(...))QAbstractProxyModel::buddy +344 (int (*)(...))QIdentityProxyModel::match +352 (int (*)(...))QAbstractProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QIdentityProxyModel::setSourceModel +392 (int (*)(...))QIdentityProxyModel::mapToSource +400 (int (*)(...))QIdentityProxyModel::mapFromSource +408 (int (*)(...))QIdentityProxyModel::mapSelectionToSource +416 (int (*)(...))QIdentityProxyModel::mapSelectionFromSource + +Class QIdentityProxyModel + size=16 align=8 + base size=16 base align=8 +QIdentityProxyModel (0x0x7ff7104f74e0) 0 + vptr=((& QIdentityProxyModel::_ZTV19QIdentityProxyModel) + 16u) + QAbstractProxyModel (0x0x7ff7104f7548) 0 + primary-for QIdentityProxyModel (0x0x7ff7104f74e0) + QAbstractItemModel (0x0x7ff7104f75b0) 0 + primary-for QAbstractProxyModel (0x0x7ff7104f7548) + QObject (0x0x7ff710498d80) 0 + primary-for QAbstractItemModel (0x0x7ff7104f75b0) + +Class QItemSelectionRange + size=16 align=8 + base size=16 base align=8 +QItemSelectionRange (0x0x7ff710498e40) 0 + +Class QItemSelectionModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QItemSelectionModel::QPrivateSignal (0x0x7ff71021d0c0) 0 empty + +Vtable for QItemSelectionModel +QItemSelectionModel::_ZTV19QItemSelectionModel: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QItemSelectionModel) +16 (int (*)(...))QItemSelectionModel::metaObject +24 (int (*)(...))QItemSelectionModel::qt_metacast +32 (int (*)(...))QItemSelectionModel::qt_metacall +40 (int (*)(...))QItemSelectionModel::~QItemSelectionModel +48 (int (*)(...))QItemSelectionModel::~QItemSelectionModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QItemSelectionModel::setCurrentIndex +120 (int (*)(...))QItemSelectionModel::select +128 (int (*)(...))QItemSelectionModel::select +136 (int (*)(...))QItemSelectionModel::clear +144 (int (*)(...))QItemSelectionModel::reset +152 (int (*)(...))QItemSelectionModel::clearCurrentIndex + +Class QItemSelectionModel + size=16 align=8 + base size=16 base align=8 +QItemSelectionModel (0x0x7ff7104f7820) 0 + vptr=((& QItemSelectionModel::_ZTV19QItemSelectionModel) + 16u) + QObject (0x0x7ff71021d060) 0 + primary-for QItemSelectionModel (0x0x7ff7104f7820) + +Class QItemSelection + size=8 align=8 + base size=8 base align=8 +QItemSelection (0x0x7ff7104f7a28) 0 + QList (0x0x7ff7104f7a90) 0 + QListSpecialMethods (0x0x7ff71021d360) 0 empty + +Class QSortFilterProxyModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSortFilterProxyModel::QPrivateSignal (0x0x7ff71021d780) 0 empty + +Vtable for QSortFilterProxyModel +QSortFilterProxyModel::_ZTV21QSortFilterProxyModel: 56u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QSortFilterProxyModel) +16 (int (*)(...))QSortFilterProxyModel::metaObject +24 (int (*)(...))QSortFilterProxyModel::qt_metacast +32 (int (*)(...))QSortFilterProxyModel::qt_metacall +40 (int (*)(...))QSortFilterProxyModel::~QSortFilterProxyModel +48 (int (*)(...))QSortFilterProxyModel::~QSortFilterProxyModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSortFilterProxyModel::index +120 (int (*)(...))QSortFilterProxyModel::parent +128 (int (*)(...))QSortFilterProxyModel::sibling +136 (int (*)(...))QSortFilterProxyModel::rowCount +144 (int (*)(...))QSortFilterProxyModel::columnCount +152 (int (*)(...))QSortFilterProxyModel::hasChildren +160 (int (*)(...))QSortFilterProxyModel::data +168 (int (*)(...))QSortFilterProxyModel::setData +176 (int (*)(...))QSortFilterProxyModel::headerData +184 (int (*)(...))QSortFilterProxyModel::setHeaderData +192 (int (*)(...))QAbstractProxyModel::itemData +200 (int (*)(...))QAbstractProxyModel::setItemData +208 (int (*)(...))QSortFilterProxyModel::mimeTypes +216 (int (*)(...))QSortFilterProxyModel::mimeData +224 (int (*)(...))QAbstractProxyModel::canDropMimeData +232 (int (*)(...))QSortFilterProxyModel::dropMimeData +240 (int (*)(...))QSortFilterProxyModel::supportedDropActions +248 (int (*)(...))QAbstractProxyModel::supportedDragActions +256 (int (*)(...))QSortFilterProxyModel::insertRows +264 (int (*)(...))QSortFilterProxyModel::insertColumns +272 (int (*)(...))QSortFilterProxyModel::removeRows +280 (int (*)(...))QSortFilterProxyModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QSortFilterProxyModel::fetchMore +312 (int (*)(...))QSortFilterProxyModel::canFetchMore +320 (int (*)(...))QSortFilterProxyModel::flags +328 (int (*)(...))QSortFilterProxyModel::sort +336 (int (*)(...))QSortFilterProxyModel::buddy +344 (int (*)(...))QSortFilterProxyModel::match +352 (int (*)(...))QSortFilterProxyModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractProxyModel::submit +376 (int (*)(...))QAbstractProxyModel::revert +384 (int (*)(...))QSortFilterProxyModel::setSourceModel +392 (int (*)(...))QSortFilterProxyModel::mapToSource +400 (int (*)(...))QSortFilterProxyModel::mapFromSource +408 (int (*)(...))QSortFilterProxyModel::mapSelectionToSource +416 (int (*)(...))QSortFilterProxyModel::mapSelectionFromSource +424 (int (*)(...))QSortFilterProxyModel::filterAcceptsRow +432 (int (*)(...))QSortFilterProxyModel::filterAcceptsColumn +440 (int (*)(...))QSortFilterProxyModel::lessThan + +Class QSortFilterProxyModel + size=16 align=8 + base size=16 base align=8 +QSortFilterProxyModel (0x0x7ff7104f7b60) 0 + vptr=((& QSortFilterProxyModel::_ZTV21QSortFilterProxyModel) + 16u) + QAbstractProxyModel (0x0x7ff7104f7bc8) 0 + primary-for QSortFilterProxyModel (0x0x7ff7104f7b60) + QAbstractItemModel (0x0x7ff7104f7c30) 0 + primary-for QAbstractProxyModel (0x0x7ff7104f7bc8) + QObject (0x0x7ff71021d720) 0 + primary-for QAbstractItemModel (0x0x7ff7104f7c30) + +Class QStringListModel::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QStringListModel::QPrivateSignal (0x0x7ff71021d840) 0 empty + +Vtable for QStringListModel +QStringListModel::_ZTV16QStringListModel: 48u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QStringListModel) +16 (int (*)(...))QStringListModel::metaObject +24 (int (*)(...))QStringListModel::qt_metacast +32 (int (*)(...))QStringListModel::qt_metacall +40 (int (*)(...))QStringListModel::~QStringListModel +48 (int (*)(...))QStringListModel::~QStringListModel +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QAbstractListModel::index +120 (int (*)(...))QAbstractListModel::parent +128 (int (*)(...))QStringListModel::sibling +136 (int (*)(...))QStringListModel::rowCount +144 (int (*)(...))QAbstractListModel::columnCount +152 (int (*)(...))QAbstractListModel::hasChildren +160 (int (*)(...))QStringListModel::data +168 (int (*)(...))QStringListModel::setData +176 (int (*)(...))QAbstractItemModel::headerData +184 (int (*)(...))QAbstractItemModel::setHeaderData +192 (int (*)(...))QAbstractItemModel::itemData +200 (int (*)(...))QAbstractItemModel::setItemData +208 (int (*)(...))QAbstractItemModel::mimeTypes +216 (int (*)(...))QAbstractItemModel::mimeData +224 (int (*)(...))QAbstractItemModel::canDropMimeData +232 (int (*)(...))QAbstractListModel::dropMimeData +240 (int (*)(...))QStringListModel::supportedDropActions +248 (int (*)(...))QAbstractItemModel::supportedDragActions +256 (int (*)(...))QStringListModel::insertRows +264 (int (*)(...))QAbstractItemModel::insertColumns +272 (int (*)(...))QStringListModel::removeRows +280 (int (*)(...))QAbstractItemModel::removeColumns +288 (int (*)(...))QAbstractItemModel::moveRows +296 (int (*)(...))QAbstractItemModel::moveColumns +304 (int (*)(...))QAbstractItemModel::fetchMore +312 (int (*)(...))QAbstractItemModel::canFetchMore +320 (int (*)(...))QStringListModel::flags +328 (int (*)(...))QStringListModel::sort +336 (int (*)(...))QAbstractItemModel::buddy +344 (int (*)(...))QAbstractItemModel::match +352 (int (*)(...))QAbstractItemModel::span +360 (int (*)(...))QAbstractItemModel::roleNames +368 (int (*)(...))QAbstractItemModel::submit +376 (int (*)(...))QAbstractItemModel::revert + +Class QStringListModel + size=24 align=8 + base size=24 base align=8 +QStringListModel (0x0x7ff7104f7c98) 0 + vptr=((& QStringListModel::_ZTV16QStringListModel) + 16u) + QAbstractListModel (0x0x7ff7104f7d00) 0 + primary-for QStringListModel (0x0x7ff7104f7c98) + QAbstractItemModel (0x0x7ff7104f7d68) 0 + primary-for QAbstractListModel (0x0x7ff7104f7d00) + QObject (0x0x7ff71021d7e0) 0 + primary-for QAbstractItemModel (0x0x7ff7104f7d68) + +Class QJsonValue + size=24 align=8 + base size=20 base align=8 +QJsonValue (0x0x7ff71021d8a0) 0 + +Class QJsonValueRef + size=16 align=8 + base size=12 base align=8 +QJsonValueRef (0x0x7ff71021d960) 0 + +Class QJsonValuePtr + size=24 align=8 + base size=24 base align=8 +QJsonValuePtr (0x0x7ff71021da20) 0 + +Class QJsonValueRefPtr + size=16 align=8 + base size=16 base align=8 +QJsonValueRefPtr (0x0x7ff71021da80) 0 + +Class QJsonArray::iterator + size=16 align=8 + base size=12 base align=8 +QJsonArray::iterator (0x0x7ff71021db40) 0 + +Class QJsonArray::const_iterator + size=16 align=8 + base size=12 base align=8 +QJsonArray::const_iterator (0x0x7ff71021dba0) 0 + +Class QJsonArray + size=16 align=8 + base size=16 base align=8 +QJsonArray (0x0x7ff71021dae0) 0 + +Class QJsonParseError + size=8 align=4 + base size=8 base align=4 +QJsonParseError (0x0x7ff71021dc60) 0 + +Class QJsonDocument + size=8 align=8 + base size=8 base align=8 +QJsonDocument (0x0x7ff71021dcc0) 0 + +Class QJsonObject::iterator + size=16 align=8 + base size=12 base align=8 +QJsonObject::iterator (0x0x7ff71021dd80) 0 + +Class QJsonObject::const_iterator + size=16 align=8 + base size=12 base align=8 +QJsonObject::const_iterator (0x0x7ff71021dde0) 0 + +Class QJsonObject + size=16 align=8 + base size=16 base align=8 +QJsonObject (0x0x7ff71021dd20) 0 + +Class QEventLoop::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QEventLoop::QPrivateSignal (0x0x7ff71002d000) 0 empty + +Vtable for QEventLoop +QEventLoop::_ZTV10QEventLoop: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QEventLoop) +16 (int (*)(...))QEventLoop::metaObject +24 (int (*)(...))QEventLoop::qt_metacast +32 (int (*)(...))QEventLoop::qt_metacall +40 (int (*)(...))QEventLoop::~QEventLoop +48 (int (*)(...))QEventLoop::~QEventLoop +56 (int (*)(...))QEventLoop::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QEventLoop + size=16 align=8 + base size=16 base align=8 +QEventLoop (0x0x7ff7104f7f70) 0 + vptr=((& QEventLoop::_ZTV10QEventLoop) + 16u) + QObject (0x0x7ff71021df60) 0 + primary-for QEventLoop (0x0x7ff7104f7f70) + +Class QEventLoopLocker + size=8 align=8 + base size=8 base align=8 +QEventLoopLocker (0x0x7ff71002d180) 0 + +Class QAbstractEventDispatcher::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractEventDispatcher::QPrivateSignal (0x0x7ff71002d240) 0 empty + +Class QAbstractEventDispatcher::TimerInfo + size=12 align=4 + base size=12 base align=4 +QAbstractEventDispatcher::TimerInfo (0x0x7ff71002d2a0) 0 + +Vtable for QAbstractEventDispatcher +QAbstractEventDispatcher::_ZTV24QAbstractEventDispatcher: 28u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI24QAbstractEventDispatcher) +16 (int (*)(...))QAbstractEventDispatcher::metaObject +24 (int (*)(...))QAbstractEventDispatcher::qt_metacast +32 (int (*)(...))QAbstractEventDispatcher::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual +184 (int (*)(...))__cxa_pure_virtual +192 (int (*)(...))__cxa_pure_virtual +200 (int (*)(...))__cxa_pure_virtual +208 (int (*)(...))QAbstractEventDispatcher::startingUp +216 (int (*)(...))QAbstractEventDispatcher::closingDown + +Class QAbstractEventDispatcher + size=16 align=8 + base size=16 base align=8 +QAbstractEventDispatcher (0x0x7ff7100380d0) 0 + vptr=((& QAbstractEventDispatcher::_ZTV24QAbstractEventDispatcher) + 16u) + QObject (0x0x7ff71002d1e0) 0 + primary-for QAbstractEventDispatcher (0x0x7ff7100380d0) + +Vtable for QAbstractNativeEventFilter +QAbstractNativeEventFilter::_ZTV26QAbstractNativeEventFilter: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI26QAbstractNativeEventFilter) +16 0u +24 0u +32 (int (*)(...))__cxa_pure_virtual + +Class QAbstractNativeEventFilter + size=16 align=8 + base size=16 base align=8 +QAbstractNativeEventFilter (0x0x7ff71002d300) 0 + vptr=((& QAbstractNativeEventFilter::_ZTV26QAbstractNativeEventFilter) + 16u) + +Class QBasicTimer + size=4 align=4 + base size=4 base align=4 +QBasicTimer (0x0x7ff71002d360) 0 + +Vtable for QEvent +QEvent::_ZTV6QEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QEvent) +16 (int (*)(...))QEvent::~QEvent +24 (int (*)(...))QEvent::~QEvent + +Class QEvent + size=24 align=8 + base size=20 base align=8 +QEvent (0x0x7ff71002d4e0) 0 + vptr=((& QEvent::_ZTV6QEvent) + 16u) + +Vtable for QTimerEvent +QTimerEvent::_ZTV11QTimerEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTimerEvent) +16 (int (*)(...))QTimerEvent::~QTimerEvent +24 (int (*)(...))QTimerEvent::~QTimerEvent + +Class QTimerEvent + size=24 align=8 + base size=24 base align=8 +QTimerEvent (0x0x7ff7100381a0) 0 + vptr=((& QTimerEvent::_ZTV11QTimerEvent) + 16u) + QEvent (0x0x7ff71002d540) 0 + primary-for QTimerEvent (0x0x7ff7100381a0) + +Vtable for QChildEvent +QChildEvent::_ZTV11QChildEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QChildEvent) +16 (int (*)(...))QChildEvent::~QChildEvent +24 (int (*)(...))QChildEvent::~QChildEvent + +Class QChildEvent + size=32 align=8 + base size=32 base align=8 +QChildEvent (0x0x7ff710038208) 0 + vptr=((& QChildEvent::_ZTV11QChildEvent) + 16u) + QEvent (0x0x7ff71002d5a0) 0 + primary-for QChildEvent (0x0x7ff710038208) + +Vtable for QDynamicPropertyChangeEvent +QDynamicPropertyChangeEvent::_ZTV27QDynamicPropertyChangeEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI27QDynamicPropertyChangeEvent) +16 (int (*)(...))QDynamicPropertyChangeEvent::~QDynamicPropertyChangeEvent +24 (int (*)(...))QDynamicPropertyChangeEvent::~QDynamicPropertyChangeEvent + +Class QDynamicPropertyChangeEvent + size=32 align=8 + base size=32 base align=8 +QDynamicPropertyChangeEvent (0x0x7ff710038270) 0 + vptr=((& QDynamicPropertyChangeEvent::_ZTV27QDynamicPropertyChangeEvent) + 16u) + QEvent (0x0x7ff71002d600) 0 + primary-for QDynamicPropertyChangeEvent (0x0x7ff710038270) + +Vtable for QDeferredDeleteEvent +QDeferredDeleteEvent::_ZTV20QDeferredDeleteEvent: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI20QDeferredDeleteEvent) +16 (int (*)(...))QDeferredDeleteEvent::~QDeferredDeleteEvent +24 (int (*)(...))QDeferredDeleteEvent::~QDeferredDeleteEvent + +Class QDeferredDeleteEvent + size=24 align=8 + base size=24 base align=8 +QDeferredDeleteEvent (0x0x7ff7100382d8) 0 + vptr=((& QDeferredDeleteEvent::_ZTV20QDeferredDeleteEvent) + 16u) + QEvent (0x0x7ff71002d660) 0 + primary-for QDeferredDeleteEvent (0x0x7ff7100382d8) + +Class QCoreApplication::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QCoreApplication::QPrivateSignal (0x0x7ff71002d720) 0 empty + +Vtable for QCoreApplication +QCoreApplication::_ZTV16QCoreApplication: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QCoreApplication) +16 (int (*)(...))QCoreApplication::metaObject +24 (int (*)(...))QCoreApplication::qt_metacast +32 (int (*)(...))QCoreApplication::qt_metacall +40 (int (*)(...))QCoreApplication::~QCoreApplication +48 (int (*)(...))QCoreApplication::~QCoreApplication +56 (int (*)(...))QCoreApplication::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QCoreApplication::notify +120 (int (*)(...))QCoreApplication::compressEvent + +Class QCoreApplication + size=16 align=8 + base size=16 base align=8 +QCoreApplication (0x0x7ff710038340) 0 + vptr=((& QCoreApplication::_ZTV16QCoreApplication) + 16u) + QObject (0x0x7ff71002d6c0) 0 + primary-for QCoreApplication (0x0x7ff710038340) + +Class QMetaMethod + size=16 align=8 + base size=12 base align=8 +QMetaMethod (0x0x7ff71002d7e0) 0 + +Class QMetaEnum + size=16 align=8 + base size=12 base align=8 +QMetaEnum (0x0x7ff71002d960) 0 + +Class QMetaProperty + size=32 align=8 + base size=32 base align=8 +QMetaProperty (0x0x7ff71002db40) 0 + +Class QMetaClassInfo + size=16 align=8 + base size=12 base align=8 +QMetaClassInfo (0x0x7ff71002dba0) 0 + +Class QMimeData::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QMimeData::QPrivateSignal (0x0x7ff71002dd80) 0 empty + +Vtable for QMimeData +QMimeData::_ZTV9QMimeData: 17u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QMimeData) +16 (int (*)(...))QMimeData::metaObject +24 (int (*)(...))QMimeData::qt_metacast +32 (int (*)(...))QMimeData::qt_metacall +40 (int (*)(...))QMimeData::~QMimeData +48 (int (*)(...))QMimeData::~QMimeData +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QMimeData::hasFormat +120 (int (*)(...))QMimeData::formats +128 (int (*)(...))QMimeData::retrieveData + +Class QMimeData + size=16 align=8 + base size=16 base align=8 +QMimeData (0x0x7ff7100384e0) 0 + vptr=((& QMimeData::_ZTV9QMimeData) + 16u) + QObject (0x0x7ff71002dd20) 0 + primary-for QMimeData (0x0x7ff7100384e0) + +Class QObjectCleanupHandler::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QObjectCleanupHandler::QPrivateSignal (0x0x7ff71002de40) 0 empty + +Vtable for QObjectCleanupHandler +QObjectCleanupHandler::_ZTV21QObjectCleanupHandler: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QObjectCleanupHandler) +16 (int (*)(...))QObjectCleanupHandler::metaObject +24 (int (*)(...))QObjectCleanupHandler::qt_metacast +32 (int (*)(...))QObjectCleanupHandler::qt_metacall +40 (int (*)(...))QObjectCleanupHandler::~QObjectCleanupHandler +48 (int (*)(...))QObjectCleanupHandler::~QObjectCleanupHandler +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QObjectCleanupHandler + size=24 align=8 + base size=24 base align=8 +QObjectCleanupHandler (0x0x7ff710038548) 0 + vptr=((& QObjectCleanupHandler::_ZTV21QObjectCleanupHandler) + 16u) + QObject (0x0x7ff71002dde0) 0 + primary-for QObjectCleanupHandler (0x0x7ff710038548) + +Class QSharedMemory::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSharedMemory::QPrivateSignal (0x0x7ff70fd690c0) 0 empty + +Vtable for QSharedMemory +QSharedMemory::_ZTV13QSharedMemory: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QSharedMemory) +16 (int (*)(...))QSharedMemory::metaObject +24 (int (*)(...))QSharedMemory::qt_metacast +32 (int (*)(...))QSharedMemory::qt_metacall +40 (int (*)(...))QSharedMemory::~QSharedMemory +48 (int (*)(...))QSharedMemory::~QSharedMemory +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSharedMemory + size=16 align=8 + base size=16 base align=8 +QSharedMemory (0x0x7ff7100385b0) 0 + vptr=((& QSharedMemory::_ZTV13QSharedMemory) + 16u) + QObject (0x0x7ff70fd69060) 0 + primary-for QSharedMemory (0x0x7ff7100385b0) + +Class QSignalMapper::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSignalMapper::QPrivateSignal (0x0x7ff70fd69180) 0 empty + +Vtable for QSignalMapper +QSignalMapper::_ZTV13QSignalMapper: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QSignalMapper) +16 (int (*)(...))QSignalMapper::metaObject +24 (int (*)(...))QSignalMapper::qt_metacast +32 (int (*)(...))QSignalMapper::qt_metacall +40 (int (*)(...))QSignalMapper::~QSignalMapper +48 (int (*)(...))QSignalMapper::~QSignalMapper +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSignalMapper + size=16 align=8 + base size=16 base align=8 +QSignalMapper (0x0x7ff710038618) 0 + vptr=((& QSignalMapper::_ZTV13QSignalMapper) + 16u) + QObject (0x0x7ff70fd69120) 0 + primary-for QSignalMapper (0x0x7ff710038618) + +Class QSocketNotifier::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSocketNotifier::QPrivateSignal (0x0x7ff70fd69240) 0 empty + +Vtable for QSocketNotifier +QSocketNotifier::_ZTV15QSocketNotifier: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI15QSocketNotifier) +16 (int (*)(...))QSocketNotifier::metaObject +24 (int (*)(...))QSocketNotifier::qt_metacast +32 (int (*)(...))QSocketNotifier::qt_metacall +40 (int (*)(...))QSocketNotifier::~QSocketNotifier +48 (int (*)(...))QSocketNotifier::~QSocketNotifier +56 (int (*)(...))QSocketNotifier::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QSocketNotifier + size=16 align=8 + base size=16 base align=8 +QSocketNotifier (0x0x7ff710038680) 0 + vptr=((& QSocketNotifier::_ZTV15QSocketNotifier) + 16u) + QObject (0x0x7ff70fd691e0) 0 + primary-for QSocketNotifier (0x0x7ff710038680) + +Class QSystemSemaphore + size=8 align=8 + base size=8 base align=8 +QSystemSemaphore (0x0x7ff70fd692a0) 0 + +Class QTimer::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTimer::QPrivateSignal (0x0x7ff70fd693c0) 0 empty + +Vtable for QTimer +QTimer::_ZTV6QTimer: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QTimer) +16 (int (*)(...))QTimer::metaObject +24 (int (*)(...))QTimer::qt_metacast +32 (int (*)(...))QTimer::qt_metacall +40 (int (*)(...))QTimer::~QTimer +48 (int (*)(...))QTimer::~QTimer +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QTimer::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QTimer + size=32 align=8 + base size=29 base align=8 +QTimer (0x0x7ff7100386e8) 0 + vptr=((& QTimer::_ZTV6QTimer) + 16u) + QObject (0x0x7ff70fd69360) 0 + primary-for QTimer (0x0x7ff7100386e8) + +Class QTranslator::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTranslator::QPrivateSignal (0x0x7ff70fd69540) 0 empty + +Vtable for QTranslator +QTranslator::_ZTV11QTranslator: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QTranslator) +16 (int (*)(...))QTranslator::metaObject +24 (int (*)(...))QTranslator::qt_metacast +32 (int (*)(...))QTranslator::qt_metacall +40 (int (*)(...))QTranslator::~QTranslator +48 (int (*)(...))QTranslator::~QTranslator +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QTranslator::translate +120 (int (*)(...))QTranslator::isEmpty + +Class QTranslator + size=16 align=8 + base size=16 base align=8 +QTranslator (0x0x7ff7100387b8) 0 + vptr=((& QTranslator::_ZTV11QTranslator) + 16u) + QObject (0x0x7ff70fd694e0) 0 + primary-for QTranslator (0x0x7ff7100387b8) + +Class QMimeType + size=8 align=8 + base size=8 base align=8 +QMimeType (0x0x7ff70fd695a0) 0 + +Class QMimeDatabase + size=8 align=8 + base size=8 base align=8 +QMimeDatabase (0x0x7ff70fd69780) 0 + +Vtable for QFactoryInterface +QFactoryInterface::_ZTV17QFactoryInterface: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QFactoryInterface) +16 0u +24 0u +32 (int (*)(...))__cxa_pure_virtual + +Class QFactoryInterface + size=8 align=8 + base size=8 base align=8 +QFactoryInterface (0x0x7ff70fd697e0) 0 nearly-empty + vptr=((& QFactoryInterface::_ZTV17QFactoryInterface) + 16u) + +Class QLibrary::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QLibrary::QPrivateSignal (0x0x7ff70fd69900) 0 empty + +Vtable for QLibrary +QLibrary::_ZTV8QLibrary: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI8QLibrary) +16 (int (*)(...))QLibrary::metaObject +24 (int (*)(...))QLibrary::qt_metacast +32 (int (*)(...))QLibrary::qt_metacall +40 (int (*)(...))QLibrary::~QLibrary +48 (int (*)(...))QLibrary::~QLibrary +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QLibrary + size=32 align=8 + base size=25 base align=8 +QLibrary (0x0x7ff710038888) 0 + vptr=((& QLibrary::_ZTV8QLibrary) + 16u) + QObject (0x0x7ff70fd698a0) 0 + primary-for QLibrary (0x0x7ff710038888) + +Class QStaticPlugin + size=16 align=8 + base size=16 base align=8 +QStaticPlugin (0x0x7ff70fd69a80) 0 + +Class QPluginLoader::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QPluginLoader::QPrivateSignal (0x0x7ff70fd69c60) 0 empty + +Vtable for QPluginLoader +QPluginLoader::_ZTV13QPluginLoader: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QPluginLoader) +16 (int (*)(...))QPluginLoader::metaObject +24 (int (*)(...))QPluginLoader::qt_metacast +32 (int (*)(...))QPluginLoader::qt_metacall +40 (int (*)(...))QPluginLoader::~QPluginLoader +48 (int (*)(...))QPluginLoader::~QPluginLoader +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QPluginLoader + size=32 align=8 + base size=25 base align=8 +QPluginLoader (0x0x7ff710038a28) 0 + vptr=((& QPluginLoader::_ZTV13QPluginLoader) + 16u) + QObject (0x0x7ff70fd69c00) 0 + primary-for QPluginLoader (0x0x7ff710038a28) + +Class QUuid + size=16 align=4 + base size=16 base align=4 +QUuid (0x0x7ff70fd69cc0) 0 + +Class QAbstractState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractState::QPrivateSignal (0x0x7ff70fd69ea0) 0 empty + +Vtable for QAbstractState +QAbstractState::_ZTV14QAbstractState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI14QAbstractState) +16 (int (*)(...))QAbstractState::metaObject +24 (int (*)(...))QAbstractState::qt_metacast +32 (int (*)(...))QAbstractState::qt_metacall +40 0u +48 0u +56 (int (*)(...))QAbstractState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QAbstractState + size=16 align=8 + base size=16 base align=8 +QAbstractState (0x0x7ff710038af8) 0 + vptr=((& QAbstractState::_ZTV14QAbstractState) + 16u) + QObject (0x0x7ff70fd69e40) 0 + primary-for QAbstractState (0x0x7ff710038af8) + +Class QAbstractTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QAbstractTransition::QPrivateSignal (0x0x7ff70fd69f60) 0 empty + +Vtable for QAbstractTransition +QAbstractTransition::_ZTV19QAbstractTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QAbstractTransition) +16 (int (*)(...))QAbstractTransition::metaObject +24 (int (*)(...))QAbstractTransition::qt_metacast +32 (int (*)(...))QAbstractTransition::qt_metacall +40 0u +48 0u +56 (int (*)(...))QAbstractTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QAbstractTransition + size=16 align=8 + base size=16 base align=8 +QAbstractTransition (0x0x7ff710038b60) 0 + vptr=((& QAbstractTransition::_ZTV19QAbstractTransition) + 16u) + QObject (0x0x7ff70fd69f00) 0 + primary-for QAbstractTransition (0x0x7ff710038b60) + +Class QEventTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QEventTransition::QPrivateSignal (0x0x7ff70febb060) 0 empty + +Vtable for QEventTransition +QEventTransition::_ZTV16QEventTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI16QEventTransition) +16 (int (*)(...))QEventTransition::metaObject +24 (int (*)(...))QEventTransition::qt_metacast +32 (int (*)(...))QEventTransition::qt_metacall +40 (int (*)(...))QEventTransition::~QEventTransition +48 (int (*)(...))QEventTransition::~QEventTransition +56 (int (*)(...))QEventTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QEventTransition::eventTest +120 (int (*)(...))QEventTransition::onTransition + +Class QEventTransition + size=16 align=8 + base size=16 base align=8 +QEventTransition (0x0x7ff710038bc8) 0 + vptr=((& QEventTransition::_ZTV16QEventTransition) + 16u) + QAbstractTransition (0x0x7ff710038c30) 0 + primary-for QEventTransition (0x0x7ff710038bc8) + QObject (0x0x7ff70febb000) 0 + primary-for QAbstractTransition (0x0x7ff710038c30) + +Class QFinalState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFinalState::QPrivateSignal (0x0x7ff70febb120) 0 empty + +Vtable for QFinalState +QFinalState::_ZTV11QFinalState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QFinalState) +16 (int (*)(...))QFinalState::metaObject +24 (int (*)(...))QFinalState::qt_metacast +32 (int (*)(...))QFinalState::qt_metacall +40 (int (*)(...))QFinalState::~QFinalState +48 (int (*)(...))QFinalState::~QFinalState +56 (int (*)(...))QFinalState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QFinalState::onEntry +120 (int (*)(...))QFinalState::onExit + +Class QFinalState + size=16 align=8 + base size=16 base align=8 +QFinalState (0x0x7ff710038c98) 0 + vptr=((& QFinalState::_ZTV11QFinalState) + 16u) + QAbstractState (0x0x7ff710038d00) 0 + primary-for QFinalState (0x0x7ff710038c98) + QObject (0x0x7ff70febb0c0) 0 + primary-for QAbstractState (0x0x7ff710038d00) + +Class QHistoryState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QHistoryState::QPrivateSignal (0x0x7ff70febb1e0) 0 empty + +Vtable for QHistoryState +QHistoryState::_ZTV13QHistoryState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QHistoryState) +16 (int (*)(...))QHistoryState::metaObject +24 (int (*)(...))QHistoryState::qt_metacast +32 (int (*)(...))QHistoryState::qt_metacall +40 (int (*)(...))QHistoryState::~QHistoryState +48 (int (*)(...))QHistoryState::~QHistoryState +56 (int (*)(...))QHistoryState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QHistoryState::onEntry +120 (int (*)(...))QHistoryState::onExit + +Class QHistoryState + size=16 align=8 + base size=16 base align=8 +QHistoryState (0x0x7ff710038d68) 0 + vptr=((& QHistoryState::_ZTV13QHistoryState) + 16u) + QAbstractState (0x0x7ff710038dd0) 0 + primary-for QHistoryState (0x0x7ff710038d68) + QObject (0x0x7ff70febb180) 0 + primary-for QAbstractState (0x0x7ff710038dd0) + +Class QSignalTransition::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QSignalTransition::QPrivateSignal (0x0x7ff70febb2a0) 0 empty + +Vtable for QSignalTransition +QSignalTransition::_ZTV17QSignalTransition: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI17QSignalTransition) +16 (int (*)(...))QSignalTransition::metaObject +24 (int (*)(...))QSignalTransition::qt_metacast +32 (int (*)(...))QSignalTransition::qt_metacall +40 (int (*)(...))QSignalTransition::~QSignalTransition +48 (int (*)(...))QSignalTransition::~QSignalTransition +56 (int (*)(...))QSignalTransition::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QSignalTransition::eventTest +120 (int (*)(...))QSignalTransition::onTransition + +Class QSignalTransition + size=16 align=8 + base size=16 base align=8 +QSignalTransition (0x0x7ff710038e38) 0 + vptr=((& QSignalTransition::_ZTV17QSignalTransition) + 16u) + QAbstractTransition (0x0x7ff710038ea0) 0 + primary-for QSignalTransition (0x0x7ff710038e38) + QObject (0x0x7ff70febb240) 0 + primary-for QAbstractTransition (0x0x7ff710038ea0) + +Class QState::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QState::QPrivateSignal (0x0x7ff70febb360) 0 empty + +Vtable for QState +QState::_ZTV6QState: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI6QState) +16 (int (*)(...))QState::metaObject +24 (int (*)(...))QState::qt_metacast +32 (int (*)(...))QState::qt_metacall +40 (int (*)(...))QState::~QState +48 (int (*)(...))QState::~QState +56 (int (*)(...))QState::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QState::onEntry +120 (int (*)(...))QState::onExit + +Class QState + size=16 align=8 + base size=16 base align=8 +QState (0x0x7ff710038f08) 0 + vptr=((& QState::_ZTV6QState) + 16u) + QAbstractState (0x0x7ff710038f70) 0 + primary-for QState (0x0x7ff710038f08) + QObject (0x0x7ff70febb300) 0 + primary-for QAbstractState (0x0x7ff710038f70) + +Class QStateMachine::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QStateMachine::QPrivateSignal (0x0x7ff70febb480) 0 empty + +Vtable for QStateMachine::SignalEvent +QStateMachine::SignalEvent::_ZTVN13QStateMachine11SignalEventE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN13QStateMachine11SignalEventE) +16 (int (*)(...))QStateMachine::SignalEvent::~SignalEvent +24 (int (*)(...))QStateMachine::SignalEvent::~SignalEvent + +Class QStateMachine::SignalEvent + size=48 align=8 + base size=48 base align=8 +QStateMachine::SignalEvent (0x0x7ff70ff22138) 0 + vptr=((& QStateMachine::SignalEvent::_ZTVN13QStateMachine11SignalEventE) + 16u) + QEvent (0x0x7ff70febb4e0) 0 + primary-for QStateMachine::SignalEvent (0x0x7ff70ff22138) + +Vtable for QStateMachine::WrappedEvent +QStateMachine::WrappedEvent::_ZTVN13QStateMachine12WrappedEventE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN13QStateMachine12WrappedEventE) +16 (int (*)(...))QStateMachine::WrappedEvent::~WrappedEvent +24 (int (*)(...))QStateMachine::WrappedEvent::~WrappedEvent + +Class QStateMachine::WrappedEvent + size=40 align=8 + base size=40 base align=8 +QStateMachine::WrappedEvent (0x0x7ff70ff221a0) 0 + vptr=((& QStateMachine::WrappedEvent::_ZTVN13QStateMachine12WrappedEventE) + 16u) + QEvent (0x0x7ff70febb540) 0 + primary-for QStateMachine::WrappedEvent (0x0x7ff70ff221a0) + +Vtable for QStateMachine +QStateMachine::_ZTV13QStateMachine: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI13QStateMachine) +16 (int (*)(...))QStateMachine::metaObject +24 (int (*)(...))QStateMachine::qt_metacast +32 (int (*)(...))QStateMachine::qt_metacall +40 (int (*)(...))QStateMachine::~QStateMachine +48 (int (*)(...))QStateMachine::~QStateMachine +56 (int (*)(...))QStateMachine::event +64 (int (*)(...))QStateMachine::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QStateMachine::onEntry +120 (int (*)(...))QStateMachine::onExit +128 (int (*)(...))QStateMachine::beginSelectTransitions +136 (int (*)(...))QStateMachine::endSelectTransitions +144 (int (*)(...))QStateMachine::beginMicrostep +152 (int (*)(...))QStateMachine::endMicrostep + +Class QStateMachine + size=16 align=8 + base size=16 base align=8 +QStateMachine (0x0x7ff70ff22000) 0 + vptr=((& QStateMachine::_ZTV13QStateMachine) + 16u) + QState (0x0x7ff70ff22068) 0 + primary-for QStateMachine (0x0x7ff70ff22000) + QAbstractState (0x0x7ff70ff220d0) 0 + primary-for QState (0x0x7ff70ff22068) + QObject (0x0x7ff70febb420) 0 + primary-for QAbstractState (0x0x7ff70ff220d0) + +Vtable for QException +QException::_ZTV10QException: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI10QException) +16 (int (*)(...))QException::~QException +24 (int (*)(...))QException::~QException +32 (int (*)(...))std::exception::what +40 (int (*)(...))QException::raise +48 (int (*)(...))QException::clone + +Class QException + size=8 align=8 + base size=8 base align=8 +QException (0x0x7ff70ff22208) 0 nearly-empty + vptr=((& QException::_ZTV10QException) + 16u) + std::exception (0x0x7ff70febb5a0) 0 nearly-empty + primary-for QException (0x0x7ff70ff22208) + +Vtable for QUnhandledException +QUnhandledException::_ZTV19QUnhandledException: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI19QUnhandledException) +16 (int (*)(...))QUnhandledException::~QUnhandledException +24 (int (*)(...))QUnhandledException::~QUnhandledException +32 (int (*)(...))std::exception::what +40 (int (*)(...))QUnhandledException::raise +48 (int (*)(...))QUnhandledException::clone + +Class QUnhandledException + size=8 align=8 + base size=8 base align=8 +QUnhandledException (0x0x7ff70ff22270) 0 nearly-empty + vptr=((& QUnhandledException::_ZTV19QUnhandledException) + 16u) + QException (0x0x7ff70ff222d8) 0 nearly-empty + primary-for QUnhandledException (0x0x7ff70ff22270) + std::exception (0x0x7ff70febb600) 0 nearly-empty + primary-for QException (0x0x7ff70ff222d8) + +Class QtPrivate::ExceptionHolder + size=8 align=8 + base size=8 base align=8 +QtPrivate::ExceptionHolder (0x0x7ff70febb660) 0 + +Class QtPrivate::ExceptionStore + size=8 align=8 + base size=8 base align=8 +QtPrivate::ExceptionStore (0x0x7ff70febb720) 0 + +Vtable for QRunnable +QRunnable::_ZTV9QRunnable: 5u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QRunnable) +16 (int (*)(...))__cxa_pure_virtual +24 0u +32 0u + +Class QRunnable + size=16 align=8 + base size=12 base align=8 +QRunnable (0x0x7ff70febb780) 0 + vptr=((& QRunnable::_ZTV9QRunnable) + 16u) + +Class QBasicMutex + size=8 align=8 + base size=8 base align=8 +QBasicMutex (0x0x7ff70febb7e0) 0 + +Class QMutex + size=8 align=8 + base size=8 base align=8 +QMutex (0x0x7ff70ff22410) 0 + QBasicMutex (0x0x7ff70febba20) 0 + +Class QMutexLocker + size=8 align=8 + base size=8 base align=8 +QMutexLocker (0x0x7ff70febba80) 0 + +Class QtPrivate::ResultItem + size=16 align=8 + base size=16 base align=8 +QtPrivate::ResultItem (0x0x7ff70febbb40) 0 + +Class QtPrivate::ResultIteratorBase + size=16 align=8 + base size=12 base align=8 +QtPrivate::ResultIteratorBase (0x0x7ff70febbba0) 0 + +Vtable for QtPrivate::ResultStoreBase +QtPrivate::ResultStoreBase::_ZTVN9QtPrivate15ResultStoreBaseE: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTIN9QtPrivate15ResultStoreBaseE) +16 (int (*)(...))QtPrivate::ResultStoreBase::~ResultStoreBase +24 (int (*)(...))QtPrivate::ResultStoreBase::~ResultStoreBase + +Class QtPrivate::ResultStoreBase + size=48 align=8 + base size=44 base align=8 +QtPrivate::ResultStoreBase (0x0x7ff70febbd20) 0 + vptr=((& QtPrivate::ResultStoreBase::_ZTVN9QtPrivate15ResultStoreBaseE) + 16u) + +Vtable for QFutureInterfaceBase +QFutureInterfaceBase::_ZTV20QFutureInterfaceBase: 4u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI20QFutureInterfaceBase) +16 (int (*)(...))QFutureInterfaceBase::~QFutureInterfaceBase +24 (int (*)(...))QFutureInterfaceBase::~QFutureInterfaceBase + +Class QFutureInterfaceBase + size=16 align=8 + base size=16 base align=8 +QFutureInterfaceBase (0x0x7ff70febbde0) 0 + vptr=((& QFutureInterfaceBase::_ZTV20QFutureInterfaceBase) + 16u) + +Class QFutureWatcherBase::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QFutureWatcherBase::QPrivateSignal (0x0x7ff70fc9f180) 0 empty + +Vtable for QFutureWatcherBase +QFutureWatcherBase::_ZTV18QFutureWatcherBase: 16u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI18QFutureWatcherBase) +16 (int (*)(...))QFutureWatcherBase::metaObject +24 (int (*)(...))QFutureWatcherBase::qt_metacast +32 (int (*)(...))QFutureWatcherBase::qt_metacall +40 0u +48 0u +56 (int (*)(...))QFutureWatcherBase::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QFutureWatcherBase::connectNotify +104 (int (*)(...))QFutureWatcherBase::disconnectNotify +112 (int (*)(...))__cxa_pure_virtual +120 (int (*)(...))__cxa_pure_virtual + +Class QFutureWatcherBase + size=16 align=8 + base size=16 base align=8 +QFutureWatcherBase (0x0x7ff70ff22c98) 0 + vptr=((& QFutureWatcherBase::_ZTV18QFutureWatcherBase) + 16u) + QObject (0x0x7ff70fc9f120) 0 + primary-for QFutureWatcherBase (0x0x7ff70ff22c98) + +Class QReadWriteLock + size=8 align=8 + base size=8 base align=8 +QReadWriteLock (0x0x7ff70fc9f2a0) 0 + +Class QReadLocker + size=8 align=8 + base size=8 base align=8 +QReadLocker (0x0x7ff70fc9f540) 0 + +Class QWriteLocker + size=8 align=8 + base size=8 base align=8 +QWriteLocker (0x0x7ff70fc9f5a0) 0 + +Class QSemaphore + size=8 align=8 + base size=8 base align=8 +QSemaphore (0x0x7ff70fc9f600) 0 + +Class QThread::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QThread::QPrivateSignal (0x0x7ff70fc9f6c0) 0 empty + +Vtable for QThread +QThread::_ZTV7QThread: 15u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI7QThread) +16 (int (*)(...))QThread::metaObject +24 (int (*)(...))QThread::qt_metacast +32 (int (*)(...))QThread::qt_metacall +40 (int (*)(...))QThread::~QThread +48 (int (*)(...))QThread::~QThread +56 (int (*)(...))QThread::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QThread::run + +Class QThread + size=16 align=8 + base size=16 base align=8 +QThread (0x0x7ff70fd07270) 0 + vptr=((& QThread::_ZTV7QThread) + 16u) + QObject (0x0x7ff70fc9f660) 0 + primary-for QThread (0x0x7ff70fd07270) + +Class QThreadPool::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QThreadPool::QPrivateSignal (0x0x7ff70fc9f780) 0 empty + +Vtable for QThreadPool +QThreadPool::_ZTV11QThreadPool: 14u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI11QThreadPool) +16 (int (*)(...))QThreadPool::metaObject +24 (int (*)(...))QThreadPool::qt_metacast +32 (int (*)(...))QThreadPool::qt_metacall +40 (int (*)(...))QThreadPool::~QThreadPool +48 (int (*)(...))QThreadPool::~QThreadPool +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify + +Class QThreadPool + size=16 align=8 + base size=16 base align=8 +QThreadPool (0x0x7ff70fd072d8) 0 + vptr=((& QThreadPool::_ZTV11QThreadPool) + 16u) + QObject (0x0x7ff70fc9f720) 0 + primary-for QThreadPool (0x0x7ff70fd072d8) + +Class QThreadStorageData + size=4 align=4 + base size=4 base align=4 +QThreadStorageData (0x0x7ff70fc9f7e0) 0 + +Class QWaitCondition + size=8 align=8 + base size=8 base align=8 +QWaitCondition (0x0x7ff70fc9f8a0) 0 + +Class QBitArray + size=8 align=8 + base size=8 base align=8 +QBitArray (0x0x7ff70fc9fd80) 0 + +Class QBitRef + size=16 align=8 + base size=12 base align=8 +QBitRef (0x0x7ff70fa64000) 0 + +Class QByteArrayMatcher::Data + size=272 align=8 + base size=272 base align=8 +QByteArrayMatcher::Data (0x0x7ff70fa64240) 0 + +Class QByteArrayMatcher + size=1040 align=8 + base size=1040 base align=8 +QByteArrayMatcher (0x0x7ff70fa641e0) 0 + +Class QCollatorSortKey + size=8 align=8 + base size=8 base align=8 +QCollatorSortKey (0x0x7ff70fa643c0) 0 + +Class QCollator + size=8 align=8 + base size=8 base align=8 +QCollator (0x0x7ff70fa64480) 0 + +Class QCommandLineOption + size=8 align=8 + base size=8 base align=8 +QCommandLineOption (0x0x7ff70fb2e240) 0 + +Class QCommandLineParser + size=8 align=8 + base size=8 base align=8 +QCommandLineParser (0x0x7ff70fb2e420) 0 + +Class QCryptographicHash + size=8 align=8 + base size=8 base align=8 +QCryptographicHash (0x0x7ff70fb2e480) 0 + +Class QElapsedTimer + size=16 align=8 + base size=16 base align=8 +QElapsedTimer (0x0x7ff70fb2e4e0) 0 + +Class QPoint + size=8 align=4 + base size=8 base align=4 +QPoint (0x0x7ff70fb2e540) 0 + +Class QPointF + size=16 align=8 + base size=16 base align=8 +QPointF (0x0x7ff70fb2e6c0) 0 + +Class QLine + size=16 align=4 + base size=16 base align=4 +QLine (0x0x7ff70fb2e840) 0 + +Class QLineF + size=32 align=8 + base size=32 base align=8 +QLineF (0x0x7ff70fb2e9c0) 0 + +Class QLinkedListData + size=32 align=8 + base size=25 base align=8 +QLinkedListData (0x0x7ff70fb2eb40) 0 + +Class QMargins + size=16 align=4 + base size=16 base align=4 +QMargins (0x0x7ff70f8a3300) 0 + +Class QMarginsF + size=32 align=8 + base size=32 base align=8 +QMarginsF (0x0x7ff70f8a3480) 0 + +Class QMessageAuthenticationCode + size=8 align=8 + base size=8 base align=8 +QMessageAuthenticationCode (0x0x7ff70f8a3600) 0 + +Class QSize + size=8 align=4 + base size=8 base align=4 +QSize (0x0x7ff70f8a36c0) 0 + +Class QSizeF + size=16 align=8 + base size=16 base align=8 +QSizeF (0x0x7ff70f8a3900) 0 + +Class QRect + size=16 align=4 + base size=16 base align=4 +QRect (0x0x7ff70f8a3b40) 0 + +Class QRectF + size=32 align=8 + base size=32 base align=8 +QRectF (0x0x7ff70f8a3cc0) 0 + +Class QRegularExpression + size=8 align=8 + base size=8 base align=8 +QRegularExpression (0x0x7ff70f8a3e40) 0 + +Class QRegularExpressionMatch + size=8 align=8 + base size=8 base align=8 +QRegularExpressionMatch (0x0x7ff70f72c2a0) 0 + +Class QRegularExpressionMatchIterator + size=8 align=8 + base size=8 base align=8 +QRegularExpressionMatchIterator (0x0x7ff70f72c480) 0 + +Class QAbstractConcatenable + size=1 align=1 + base size=0 base align=1 +QAbstractConcatenable (0x0x7ff70f72c840) 0 empty + +Class QTextBoundaryFinder + size=48 align=8 + base size=48 base align=8 +QTextBoundaryFinder (0x0x7ff70f4102a0) 0 + +Class QTimeLine::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QTimeLine::QPrivateSignal (0x0x7ff70f410480) 0 empty + +Vtable for QTimeLine +QTimeLine::_ZTV9QTimeLine: 15u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI9QTimeLine) +16 (int (*)(...))QTimeLine::metaObject +24 (int (*)(...))QTimeLine::qt_metacast +32 (int (*)(...))QTimeLine::qt_metacall +40 (int (*)(...))QTimeLine::~QTimeLine +48 (int (*)(...))QTimeLine::~QTimeLine +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QTimeLine::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QTimeLine::valueForTime + +Class QTimeLine + size=16 align=8 + base size=16 base align=8 +QTimeLine (0x0x7ff70f4410d0) 0 + vptr=((& QTimeLine::_ZTV9QTimeLine) + 16u) + QObject (0x0x7ff70f410420) 0 + primary-for QTimeLine (0x0x7ff70f4410d0) + +Class QTimeZone::OffsetData + size=32 align=8 + base size=28 base align=8 +QTimeZone::OffsetData (0x0x7ff70f410540) 0 + +Class QTimeZone + size=8 align=8 + base size=8 base align=8 +QTimeZone (0x0x7ff70f4104e0) 0 + +Class QVersionNumber::SegmentStorage + size=8 align=8 + base size=8 base align=8 +QVersionNumber::SegmentStorage (0x0x7ff70f4108a0) 0 + +Class QVersionNumber + size=8 align=8 + base size=8 base align=8 +QVersionNumber (0x0x7ff70f410840) 0 + +Class QXmlStreamStringRef + size=16 align=8 + base size=16 base align=8 +QXmlStreamStringRef (0x0x7ff70f53e660) 0 + +Class QXmlStreamAttribute + size=80 align=8 + base size=73 base align=8 +QXmlStreamAttribute (0x0x7ff70f20d360) 0 + +Class QXmlStreamAttributes + size=8 align=8 + base size=8 base align=8 +QXmlStreamAttributes (0x0x7ff70f20b8f0) 0 + QVector (0x0x7ff70f20d600) 0 + +Class QXmlStreamNamespaceDeclaration + size=40 align=8 + base size=40 base align=8 +QXmlStreamNamespaceDeclaration (0x0x7ff70f20d660) 0 + +Class QXmlStreamNotationDeclaration + size=56 align=8 + base size=56 base align=8 +QXmlStreamNotationDeclaration (0x0x7ff70f20d7e0) 0 + +Class QXmlStreamEntityDeclaration + size=88 align=8 + base size=88 base align=8 +QXmlStreamEntityDeclaration (0x0x7ff70f20d960) 0 + +Vtable for QXmlStreamEntityResolver +QXmlStreamEntityResolver::_ZTV24QXmlStreamEntityResolver: 6u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI24QXmlStreamEntityResolver) +16 (int (*)(...))QXmlStreamEntityResolver::~QXmlStreamEntityResolver +24 (int (*)(...))QXmlStreamEntityResolver::~QXmlStreamEntityResolver +32 (int (*)(...))QXmlStreamEntityResolver::resolveEntity +40 (int (*)(...))QXmlStreamEntityResolver::resolveUndeclaredEntity + +Class QXmlStreamEntityResolver + size=8 align=8 + base size=8 base align=8 +QXmlStreamEntityResolver (0x0x7ff70f20dae0) 0 nearly-empty + vptr=((& QXmlStreamEntityResolver::_ZTV24QXmlStreamEntityResolver) + 16u) + +Class QXmlStreamReader + size=8 align=8 + base size=8 base align=8 +QXmlStreamReader (0x0x7ff70f20db40) 0 + +Class QXmlStreamWriter + size=8 align=8 + base size=8 base align=8 +QXmlStreamWriter (0x0x7ff70f20dc60) 0 + +Class QGeoAddress + size=8 align=8 + base size=8 base align=8 +QGeoAddress (0x0x7ff70f20dd80) 0 + +Class QGeoCoordinate + size=8 align=8 + base size=8 base align=8 +QGeoCoordinate (0x0x7ff70f32b0c0) 0 + +Class QGeoShape + size=8 align=8 + base size=8 base align=8 +QGeoShape (0x0x7ff70f32b3c0) 0 + +Class QGeoAreaMonitorInfo + size=8 align=8 + base size=8 base align=8 +QGeoAreaMonitorInfo (0x0x7ff70f32b6c0) 0 + +Class QGeoPositionInfo + size=8 align=8 + base size=8 base align=8 +QGeoPositionInfo (0x0x7ff70f32b780) 0 + +Class QGeoPositionInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoPositionInfoSource::QPrivateSignal (0x0x7ff70f32b840) 0 empty + +Vtable for QGeoPositionInfoSource +QGeoPositionInfoSource::_ZTV22QGeoPositionInfoSource: 23u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI22QGeoPositionInfoSource) +16 (int (*)(...))QGeoPositionInfoSource::metaObject +24 (int (*)(...))QGeoPositionInfoSource::qt_metacast +32 (int (*)(...))QGeoPositionInfoSource::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoPositionInfoSource::setUpdateInterval +120 (int (*)(...))QGeoPositionInfoSource::setPreferredPositioningMethods +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual + +Class QGeoPositionInfoSource + size=24 align=8 + base size=24 base align=8 +QGeoPositionInfoSource (0x0x7ff70f20bf70) 0 + vptr=((& QGeoPositionInfoSource::_ZTV22QGeoPositionInfoSource) + 16u) + QObject (0x0x7ff70f32b7e0) 0 + primary-for QGeoPositionInfoSource (0x0x7ff70f20bf70) + +Class QGeoAreaMonitorSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoAreaMonitorSource::QPrivateSignal (0x0x7ff70f32ba20) 0 empty + +Vtable for QGeoAreaMonitorSource +QGeoAreaMonitorSource::_ZTV21QGeoAreaMonitorSource: 23u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI21QGeoAreaMonitorSource) +16 (int (*)(...))QGeoAreaMonitorSource::metaObject +24 (int (*)(...))QGeoAreaMonitorSource::qt_metacast +32 (int (*)(...))QGeoAreaMonitorSource::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoAreaMonitorSource::setPositionInfoSource +120 (int (*)(...))QGeoAreaMonitorSource::positionInfoSource +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual +160 (int (*)(...))__cxa_pure_virtual +168 (int (*)(...))__cxa_pure_virtual +176 (int (*)(...))__cxa_pure_virtual + +Class QGeoAreaMonitorSource + size=24 align=8 + base size=24 base align=8 +QGeoAreaMonitorSource (0x0x7ff70f20bb60) 0 + vptr=((& QGeoAreaMonitorSource::_ZTV21QGeoAreaMonitorSource) + 16u) + QObject (0x0x7ff70f32b9c0) 0 + primary-for QGeoAreaMonitorSource (0x0x7ff70f20bb60) + +Class QGeoCircle + size=8 align=8 + base size=8 base align=8 +QGeoCircle (0x0x7ff70f20bc98) 0 + QGeoShape (0x0x7ff70f32ba80) 0 + +Class QGeoLocation + size=8 align=8 + base size=8 base align=8 +QGeoLocation (0x0x7ff70f32bd20) 0 + +Class QGeoSatelliteInfo + size=8 align=8 + base size=8 base align=8 +QGeoSatelliteInfo (0x0x7ff70f004060) 0 + +Class QGeoSatelliteInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QGeoSatelliteInfoSource::QPrivateSignal (0x0x7ff70f004120) 0 empty + +Vtable for QGeoSatelliteInfoSource +QGeoSatelliteInfoSource::_ZTV23QGeoSatelliteInfoSource: 20u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QGeoSatelliteInfoSource) +16 (int (*)(...))QGeoSatelliteInfoSource::metaObject +24 (int (*)(...))QGeoSatelliteInfoSource::qt_metacast +32 (int (*)(...))QGeoSatelliteInfoSource::qt_metacall +40 0u +48 0u +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QGeoSatelliteInfoSource::setUpdateInterval +120 (int (*)(...))__cxa_pure_virtual +128 (int (*)(...))__cxa_pure_virtual +136 (int (*)(...))__cxa_pure_virtual +144 (int (*)(...))__cxa_pure_virtual +152 (int (*)(...))__cxa_pure_virtual + +Class QGeoSatelliteInfoSource + size=24 align=8 + base size=24 base align=8 +QGeoSatelliteInfoSource (0x0x7ff70effc068) 0 + vptr=((& QGeoSatelliteInfoSource::_ZTV23QGeoSatelliteInfoSource) + 16u) + QObject (0x0x7ff70f0040c0) 0 + primary-for QGeoSatelliteInfoSource (0x0x7ff70effc068) + +Vtable for QGeoPositionInfoSourceFactory +QGeoPositionInfoSourceFactory::_ZTV29QGeoPositionInfoSourceFactory: 7u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI29QGeoPositionInfoSourceFactory) +16 0u +24 0u +32 (int (*)(...))__cxa_pure_virtual +40 (int (*)(...))__cxa_pure_virtual +48 (int (*)(...))__cxa_pure_virtual + +Class QGeoPositionInfoSourceFactory + size=8 align=8 + base size=8 base align=8 +QGeoPositionInfoSourceFactory (0x0x7ff70f0041e0) 0 nearly-empty + vptr=((& QGeoPositionInfoSourceFactory::_ZTV29QGeoPositionInfoSourceFactory) + 16u) + +Class QGeoRectangle + size=8 align=8 + base size=8 base align=8 +QGeoRectangle (0x0x7ff70effc0d0) 0 + QGeoShape (0x0x7ff70f0042a0) 0 + +Class QNmeaPositionInfoSource::QPrivateSignal + size=1 align=1 + base size=0 base align=1 +QNmeaPositionInfoSource::QPrivateSignal (0x0x7ff70f0046c0) 0 empty + +Vtable for QNmeaPositionInfoSource +QNmeaPositionInfoSource::_ZTV23QNmeaPositionInfoSource: 24u entries +0 (int (*)(...))0 +8 (int (*)(...))(& _ZTI23QNmeaPositionInfoSource) +16 (int (*)(...))QNmeaPositionInfoSource::metaObject +24 (int (*)(...))QNmeaPositionInfoSource::qt_metacast +32 (int (*)(...))QNmeaPositionInfoSource::qt_metacall +40 (int (*)(...))QNmeaPositionInfoSource::~QNmeaPositionInfoSource +48 (int (*)(...))QNmeaPositionInfoSource::~QNmeaPositionInfoSource +56 (int (*)(...))QObject::event +64 (int (*)(...))QObject::eventFilter +72 (int (*)(...))QObject::timerEvent +80 (int (*)(...))QObject::childEvent +88 (int (*)(...))QObject::customEvent +96 (int (*)(...))QObject::connectNotify +104 (int (*)(...))QObject::disconnectNotify +112 (int (*)(...))QNmeaPositionInfoSource::setUpdateInterval +120 (int (*)(...))QGeoPositionInfoSource::setPreferredPositioningMethods +128 (int (*)(...))QNmeaPositionInfoSource::lastKnownPosition +136 (int (*)(...))QNmeaPositionInfoSource::supportedPositioningMethods +144 (int (*)(...))QNmeaPositionInfoSource::minimumUpdateInterval +152 (int (*)(...))QNmeaPositionInfoSource::error +160 (int (*)(...))QNmeaPositionInfoSource::startUpdates +168 (int (*)(...))QNmeaPositionInfoSource::stopUpdates +176 (int (*)(...))QNmeaPositionInfoSource::requestUpdate +184 (int (*)(...))QNmeaPositionInfoSource::parsePosInfoFromNmeaData + +Class QNmeaPositionInfoSource + size=32 align=8 + base size=32 base align=8 +QNmeaPositionInfoSource (0x0x7ff70effc270) 0 + vptr=((& QNmeaPositionInfoSource::_ZTV23QNmeaPositionInfoSource) + 16u) + QGeoPositionInfoSource (0x0x7ff70effc2d8) 0 + primary-for QNmeaPositionInfoSource (0x0x7ff70effc270) + QObject (0x0x7ff70f004660) 0 + primary-for QGeoPositionInfoSource (0x0x7ff70effc2d8) + diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt new file mode 100644 index 0000000..8430084 --- /dev/null +++ b/tests/auto/cmake/CMakeLists.txt @@ -0,0 +1,19 @@ + +cmake_minimum_required(VERSION 2.8) + +project(qmake_cmake_files) + +enable_testing() + +find_package(Qt5Core REQUIRED) + +include("${_Qt5CTestMacros}") + +set(qt_module_includes + Location QPlaceCategory + Positioning QGeoRectangle +) + +test_module_includes( + ${qt_module_includes} +) diff --git a/tests/auto/cmake/cmake.pro b/tests/auto/cmake/cmake.pro new file mode 100644 index 0000000..69540e9 --- /dev/null +++ b/tests/auto/cmake/cmake.pro @@ -0,0 +1,7 @@ + +# Cause make to do nothing. +TEMPLATE = subdirs + +CMAKE_QT_MODULES_UNDER_TEST = location positioning + +CONFIG += ctest_testcase diff --git a/tests/auto/declarative_core/declarative_core.pro b/tests/auto/declarative_core/declarative_core.pro new file mode 100644 index 0000000..30c2b7f --- /dev/null +++ b/tests/auto/declarative_core/declarative_core.pro @@ -0,0 +1,14 @@ +# QML tests in this directory must not depend on an OpenGL context. +# QML tests that do require an OpenGL context must go in ../../declarative_ui. + +TEMPLATE = app +TARGET = tst_declarative_core +CONFIG += qmltestcase +SOURCES += main.cpp + +CONFIG -= app_bundle + +QT += location quick + +OTHER_FILES = *.qml *.js +TESTDATA = $$OTHER_FILES diff --git a/tests/auto/declarative_core/main.cpp b/tests/auto/declarative_core/main.cpp new file mode 100644 index 0000000..041dbb7 --- /dev/null +++ b/tests/auto/declarative_core/main.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +static void initializeLibraryPath() +{ + // Set custom path since CI doesn't install test plugins +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif +} + +Q_COREAPP_STARTUP_FUNCTION(initializeLibraryPath) + +QUICK_TEST_MAIN(declarative_core) diff --git a/tests/auto/declarative_core/tst_address.qml b/tests/auto/declarative_core/tst_address.qml new file mode 100644 index 0000000..94f986c --- /dev/null +++ b/tests/auto/declarative_core/tst_address.qml @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtTest 1.0 +import QtPositioning 5.2 + +TestCase { + id: testCase + + name: "Address" + + Address { + id: address + + street: "742 Evergreen Tce" + district: "Pressboard Estates" + city: "Springfield" + state: "Oregon" + postalCode: "8900" + country: "United States" + countryCode: "USA" + } + + function test_qmlAddressText() { + compare(address.isTextGenerated, true); + compare(address.text, "742 Evergreen Tce
    Springfield, Oregon 8900
    United States"); + var textChangedSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + textChangedSpy.target = address; + textChangedSpy.signalName = "textChanged" + + var isTextGeneratedSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + isTextGeneratedSpy.target = address + isTextGeneratedSpy.signalName = "isTextGeneratedChanged" + + address.countryCode = "FRA"; + compare(address.text, "742 Evergreen Tce
    8900 Springfield
    United States"); + compare(textChangedSpy.count, 1); + textChangedSpy.clear(); + compare(isTextGeneratedSpy.count, 0); + + address.text = "address label"; + compare(address.isTextGenerated, false); + compare(address.text, "address label"); + compare(textChangedSpy.count, 1); + textChangedSpy.clear(); + compare(isTextGeneratedSpy.count, 1); + isTextGeneratedSpy.clear(); + + address.countryCode = "USA"; + compare(address.text, "address label"); + compare(textChangedSpy.count, 0); + textChangedSpy.clear(); + compare(isTextGeneratedSpy.count, 0); + + address.text = ""; + compare(address.isTextGenerated, true); + compare(address.text, "742 Evergreen Tce
    Springfield, Oregon 8900
    United States"); + compare(textChangedSpy.count, 1); + textChangedSpy.clear(); + compare(isTextGeneratedSpy.count, 1); + isTextGeneratedSpy.clear(); + } +} diff --git a/tests/auto/declarative_core/tst_category.qml b/tests/auto/declarative_core/tst_category.qml new file mode 100644 index 0000000..51809dc --- /dev/null +++ b/tests/auto/declarative_core/tst_category.qml @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "Category" + + Category { id: emptyCategory } + + function test_empty() { + compare(emptyCategory.categoryId, ""); + compare(emptyCategory.name, ""); + compare(emptyCategory.visibility, Category.UnspecifiedVisibility); + compare(emptyCategory.status, Category.Ready); + compare(emptyCategory.plugin, null); + verify(emptyCategory.icon); + } + + Category { + id: qmlCategory + + plugin: testPlugin + + categoryId: "test-category-id" + name: "Test Category" + visibility: Category.DeviceVisibility + + icon: Icon { + Component.onCompleted: { + parameters.singleUrl = "http://example.com/icons/test-category.png" + } + } + } + + function test_qmlConstructedCategory() { + compare(qmlCategory.categoryId, "test-category-id"); + compare(qmlCategory.name, "Test Category"); + compare(qmlCategory.visibility, Category.DeviceVisibility); + compare(qmlCategory.status, Category.Ready); + compare(qmlCategory.plugin, testPlugin); + verify(qmlCategory.icon); + compare(qmlCategory.icon.url(), "http://example.com/icons/test-category.png"); + compare(qmlCategory.icon.parameters.singleUrl, "http://example.com/icons/test-category.png"); + compare(qmlCategory.icon.plugin, qmlCategory.plugin); + } + + Category { + id: testCategory + } + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + } + + Plugin { + id: invalidPlugin + } + + Icon { + id: testIcon + } + + Category { + id: saveCategory + + name: "Test Category" + visibility: Place.DeviceVisibility + } + + VisualDataModel { + id: categoryModel + + model: CategoryModel { + plugin: testPlugin + } + delegate: Item { } + } + + function test_setAndGet_data() { + return [ + { tag: "name", property: "name", signal: "nameChanged", value: "Test Category", reset: "" }, + { tag: "categoryId", property: "categoryId", signal: "categoryIdChanged", value: "test-category-id-1", reset: "" }, + { tag: "visibility", property: "visibility", signal: "visibilityChanged", value: Place.PublicVisibility, reset: Place.UnspecifiedVisibility }, + { tag: "plugin", property: "plugin", signal: "pluginChanged", value: testPlugin }, + { tag: "icon", property: "icon", signal: "iconChanged", value: testIcon } + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testCategory, data); + } + + function test_save() { + categoryModel.model.update(); + tryCompare(categoryModel.model, "status", CategoryModel.Ready); + compare(categoryModel.count, 0); + + saveCategory.plugin = testPlugin; + saveCategory.categoryId = "invalid-category-id"; + + saveCategory.save(); + + compare(saveCategory.status, Category.Saving); + verify(saveCategory.errorString().length === 0); + + tryCompare(saveCategory, "status", Category.Error); + verify(saveCategory.errorString().length > 0); + + // try again without an invalid categoryId + saveCategory.categoryId = ""; + saveCategory.save(); + + compare(saveCategory.status, Category.Saving); + + tryCompare(saveCategory, "status", Category.Ready); + verify(saveCategory.errorString().length === 0); + + verify(saveCategory.categoryId !== ""); + + + // Verify that the category was added to the model + categoryModel.model.update(); + compare(categoryModel.model.status, CategoryModel.Loading); + + tryCompare(categoryModel.model, "status", CategoryModel.Ready); + + compare(categoryModel.count, 1); + var modelCategory = categoryModel.model.data(categoryModel.modelIndex(0), + CategoryModel.CategoryRole); + compare(modelCategory.categoryId, saveCategory.categoryId); + compare(modelCategory.name, saveCategory.name); + + + // Remove a category + saveCategory.remove(); + + compare(saveCategory.status, Category.Removing); + + tryCompare(saveCategory, "status", Category.Ready); + verify(saveCategory.errorString().length === 0); + + + // Verify that the category was removed from the model + categoryModel.model.update(); + compare(categoryModel.model.status, CategoryModel.Loading); + + tryCompare(categoryModel.model, "status", CategoryModel.Ready); + + compare(categoryModel.count, 0); + + + // Try again, this time fail because category does not exist + saveCategory.remove(); + + compare(saveCategory.status, Category.Removing); + + tryCompare(saveCategory, "status", Category.Error); + + verify(saveCategory.errorString().length > 0); + } + + function test_saveWithoutPlugin() { + saveCategory.plugin = null; + saveCategory.categoryId = ""; + + saveCategory.save(); + + tryCompare(saveCategory, "status", Category.Error); + + verify(saveCategory.errorString().length > 0); + compare(saveCategory.categoryId, ""); + + saveCategory.plugin = invalidPlugin; + + saveCategory.save(); + + compare(saveCategory.status, Category.Error); + + verify(saveCategory.errorString().length > 0); + compare(saveCategory.categoryId, ""); + } + + function test_removeWithoutPlugin() { + saveCategory.plugin = null; + saveCategory.categoryId = "test-category-id"; + + saveCategory.remove(); + + compare(saveCategory.status, Category.Error); + + verify(saveCategory.errorString().length > 0); + compare(saveCategory.categoryId, "test-category-id"); + + saveCategory.plugin = invalidPlugin; + + saveCategory.remove(); + + compare(saveCategory.status, Category.Error); + + verify(saveCategory.errorString().length > 0); + compare(saveCategory.categoryId, "test-category-id"); + } +} diff --git a/tests/auto/declarative_core/tst_categorymodel.qml b/tests/auto/declarative_core/tst_categorymodel.qml new file mode 100644 index 0000000..0b6e50a --- /dev/null +++ b/tests/auto/declarative_core/tst_categorymodel.qml @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "CategoryModel" + + CategoryModel { + id: testModel + } + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + PluginParameter { + name: "initializePlaceData" + value: true + } + ] + } + + Plugin { + id: uninitializedPlugin + } + + Plugin { + id: nonExistantPlugin + name: "nonExistentName" + } + + function test_setAndGet_data() { + return [ + { tag: "plugin", property: "plugin", signal: "pluginChanged", value: testPlugin }, + { tag: "hierarchical", property: "hierarchical", signal: "hierarchicalChanged", value: false, reset: true }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testModel, data); + } + + function test_hierarchicalModel() { + var modelSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + var categoryModel = Qt.createQmlObject('import QtQuick 2.0; import QtLocation 5.3;' + + 'VisualDataModel { model: CategoryModel {} delegate: Item {} }', + testCase, "VisualDataModel"); + + modelSpy.target = categoryModel.model; + modelSpy.signalName = "statusChanged"; + + compare(categoryModel.model.status, CategoryModel.Null); + compare(categoryModel.count, 0); + + + // set the plugin + categoryModel.model.plugin = testPlugin; + categoryModel.model.update(); + tryCompare(categoryModel.model, "status", CategoryModel.Loading); + compare(modelSpy.count, 1); + + tryCompare(categoryModel.model, "status", CategoryModel.Ready); + compare(modelSpy.count, 2); + compare(categoryModel.model.errorString(), ""); + + var expectedNames = [ "Accommodation", "Park" ]; + + compare(categoryModel.count, expectedNames.length); + + for (var i = 0; i < expectedNames.length; ++i) { + var category = categoryModel.model.data(categoryModel.modelIndex(i), + CategoryModel.CategoryRole); + compare(category.name, expectedNames[i]); + } + + + // check that "Accommodation" has children + categoryModel.rootIndex = categoryModel.modelIndex(0); + + expectedNames = [ "Camping", "Hotel", "Motel" ]; + + compare(categoryModel.count, expectedNames.length); + + for (i = 0; i < expectedNames.length; ++i) { + category = categoryModel.model.data(categoryModel.modelIndex(i), + CategoryModel.CategoryRole); + compare(category.name, expectedNames[i]); + + var parentCategory = categoryModel.model.data(categoryModel.modelIndex(i), + CategoryModel.ParentCategoryRole); + compare(parentCategory.name, "Accommodation"); + } + + categoryModel.rootIndex = categoryModel.parentModelIndex(); + + compare(categoryModel.count, 2); + + + // check that "Park" has no children + categoryModel.rootIndex = categoryModel.modelIndex(1); + + compare(categoryModel.count, 0); + + categoryModel.rootIndex = categoryModel.parentModelIndex(); + + + // clean up + categoryModel.model.plugin = null; + categoryModel.model.update(); + + // check that the model is empty when an error is encountered + tryCompare(categoryModel, "count", 0); + compare(categoryModel.model.status, CategoryModel.Error); + } + + function test_flatModel() { + var modelSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + var categoryModel = Qt.createQmlObject('import QtQuick 2.0; import QtLocation 5.3;' + + 'VisualDataModel { model: CategoryModel {} delegate: Item {} }', + testCase, "VisualDataModel"); + + modelSpy.target = categoryModel.model; + modelSpy.signalName = "statusChanged"; + + compare(categoryModel.model.status, CategoryModel.Null); + compare(categoryModel.count, 0); + + + // set the plugin + categoryModel.model.hierarchical = false; + categoryModel.model.plugin = testPlugin; + + categoryModel.model.update(); + tryCompare(categoryModel.model, "status", CategoryModel.Loading); + compare(modelSpy.count, 1); + + tryCompare(categoryModel.model, "status", CategoryModel.Ready); + compare(modelSpy.count, 2); + + var expectedNames = [ "Accommodation", "Camping", "Hotel", "Motel", "Park" ]; + + compare(categoryModel.count, expectedNames.length); + + for (var i = 0; i < expectedNames.length; ++i) { + var category = categoryModel.model.data(categoryModel.modelIndex(i), + CategoryModel.CategoryRole); + var name = categoryModel.model.data(categoryModel.modelIndex(i), 0); // DisplayRole + + compare(name, expectedNames[i]); + compare(category.name, expectedNames[i]); + } + + + // check that no category has children + for (i = 0; i < categoryModel.count; ++i) { + categoryModel.rootIndex = categoryModel.modelIndex(i); + + compare(categoryModel.count, 0); + + categoryModel.rootIndex = categoryModel.parentModelIndex(); + } + + + // clean up + categoryModel.model.hierarchical = true; + categoryModel.model.plugin = null; + + + // check that the model is empty when an error is encountered + categoryModel.model.update(); + tryCompare(categoryModel, "count", 0); + compare(categoryModel.model.status, CategoryModel.Error); + } + + function test_error() { + var testModel = Qt.createQmlObject('import QtLocation 5.3; CategoryModel {}', testCase, "CategoryModel"); + + var statusChangedSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + statusChangedSpy.target = testModel; + statusChangedSpy.signalName = "statusChanged"; + + //try updating without a plugin instance + testModel.update(); + tryCompare(statusChangedSpy, "count", 2); + compare(testModel.status, CategoryModel.Error); + statusChangedSpy.clear(); + //Aside: there is some difficulty in checking the transition to the Loading state + //since the model transitions from Loading to Error before the next event loop + //iteration. + + //try updating with an uninitialized plugin instance. + testModel.plugin = uninitializedPlugin; + testModel.update(); + tryCompare(statusChangedSpy, "count", 2); + compare(testModel.status, CategoryModel.Error); + statusChangedSpy.clear(); + + //try searching with plugin a instance + //that has been provided a non-existent name + testModel.plugin = nonExistantPlugin; + testModel.update(); + tryCompare(statusChangedSpy, "count", 2); + compare(testModel.status, CategoryModel.Error); + } +} diff --git a/tests/auto/declarative_core/tst_contactdetail.qml b/tests/auto/declarative_core/tst_contactdetail.qml new file mode 100644 index 0000000..a91c19f --- /dev/null +++ b/tests/auto/declarative_core/tst_contactdetail.qml @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "ContactDetail" + + ContactDetail { id: emptyContactDetail } + + function test_empty() { + compare(emptyContactDetail.label, ""); + compare(emptyContactDetail.value, ""); + } + + ContactDetail { + id: qmlContactDetail + + label: "Phone" + value: "12345" + } + + function test_qmlConstructedContactDetail() { + compare(qmlContactDetail.label, "Phone"); + compare(qmlContactDetail.value, "12345"); + } + + ContactDetail { + id: testContactDetail + } + + function test_setAndGet_data() { + return [ + { tag: "label", property: "label", signal: "labelChanged", value: "Phone", reset: "" }, + { tag: "value", property: "value", signal: "valueChanged", value: "12345", reset: "" }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testContactDetail, data); + } +} diff --git a/tests/auto/declarative_core/tst_coordinate.qml b/tests/auto/declarative_core/tst_coordinate.qml new file mode 100644 index 0000000..79ff8d0 --- /dev/null +++ b/tests/auto/declarative_core/tst_coordinate.qml @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtPositioning 5.5 + +Item { + id: item + + property variant empty: QtPositioning.coordinate() + property variant base: QtPositioning.coordinate(1.0, 1.0, 5.0) + property variant zero: QtPositioning.coordinate(0, 0) + property variant plusone: QtPositioning.coordinate(0, 1) + property variant minusone: QtPositioning.coordinate(0, -1) + property variant north: QtPositioning.coordinate(3, 0) + + SignalSpy { id: coordSpy; target: item; signalName: "baseChanged" } + + property variant inside: QtPositioning.coordinate(0.5, 0.5) + property variant outside: QtPositioning.coordinate(2, 2) + property variant tl: QtPositioning.coordinate(1, 0) + property variant br: QtPositioning.coordinate(0, 1) + property variant box: QtPositioning.rectangle(tl, br) + + + Address { + id: validTestAddress + street: "53 Brandl St" + city: "Eight Mile Plains" + country: "Australia" + countryCode: "AUS" + } + + Location { + id: testLocation + coordinate: inside + boundingBox: box + address: validTestAddress + } + + Location { + id: invalidLocation + } + + + Item { + id: coordinateItem + property variant coordinate + property int animationDuration: 100 + property var coordinateList: [] + property int coordinateCount: 0 + + CoordinateAnimation { + id: coordinateAnimation + target: coordinateItem + property: "coordinate" + duration: coordinateItem.animationDuration + } + onCoordinateChanged: { + if (!coordinateList) { + coordinateList = [] + } + coordinateList[coordinateCount] = QtPositioning.coordinate(coordinate.latitude,coordinate.longitude) + coordinateCount++ + } + + SignalSpy { id: coordinateAnimationStartSpy; target: coordinateAnimation; signalName: "started" } + SignalSpy { id: coordinateAnimationStopSpy; target: coordinateAnimation; signalName: "stopped" } + SignalSpy { id: coordinateAnimationDirectionSpy; target: coordinateAnimation; signalName: "directionChanged" } + } + + TestCase { + name: "GeoLocation" + + function test_Location_complete() + { + compare (testLocation.coordinate.longitude, inside.longitude) + compare (testLocation.coordinate.latitude, inside.latitude) + + compare (testLocation.boundingBox.contains(inside), true) + compare (testLocation.boundingBox.contains(outside), false) + compare (testLocation.boundingBox.bottomRight.longitude, br.longitude) + compare (testLocation.boundingBox.bottomRight.latitude, br.latitude) + compare (testLocation.boundingBox.topLeft.longitude, tl.longitude) + compare (testLocation.boundingBox.topLeft.latitude, tl.latitude) + + compare (testLocation.address.country, "Australia") + compare (testLocation.address.countryCode, "AUS") + compare (testLocation.address.city, "Eight Mile Plains") + compare (testLocation.address.street, "53 Brandl St") + } + + function test_Location_invalid() + { + compare(invalidLocation.coordinate.isValid, false) + compare(invalidLocation.boundingBox.isEmpty, true) + compare(invalidLocation.boundingBox.isValid, false) + compare(invalidLocation.address.city, "") + } + } + + TestCase { + name: "Coordinate" + + function test_validity() + { + compare(empty.isValid, false) + + empty.longitude = 0.0; + empty.latitude = 0.0; + + compare(empty.isValid, true) + } + + function test_accessors() + { + compare(base.longitude, 1.0) + compare(base.latitude, 1.0) + compare(base.altitude, 5.0) + + coordSpy.clear(); + + base.longitude = 2.0; + base.latitude = 3.0; + base.altitude = 6.0; + + compare(base.longitude, 2.0) + compare(base.latitude, 3.0) + compare(base.altitude, 6.0) + compare(coordSpy.count, 3) + } + + function test_comparison_data() + { + return [ + { tag: "empty", coord1: empty, coord2: QtPositioning.coordinate(), result: true }, + { tag: "zero", coord1: zero, coord2: QtPositioning.coordinate(0, 0), result: true }, + { tag: "plusone", coord1: plusone, coord2: QtPositioning.coordinate(0, 1), result: true }, + { tag: "minusone", coord1: minusone, coord2: QtPositioning.coordinate(0, -1), result: true }, + { tag: "north", coord1: north, coord2: QtPositioning.coordinate(3, 0), result: true }, + { tag: "lat,long.alt", coord1: QtPositioning.coordinate(1.1, 2.2, 3.3), coord2: QtPositioning.coordinate(1.1, 2.2, 3.3), result: true }, + { tag: "not equal1", coord1: plusone, coord2: minusone, result: false }, + { tag: "not equal2", coord1: plusone, coord2: north, result: false } + ] + } + + function test_comparison(data) + { + compare(data.coord1 === data.coord2, data.result) + compare(data.coord1 !== data.coord2, !data.result) + compare(data.coord1 == data.coord2, data.result) + compare(data.coord1 != data.coord2, !data.result) + } + + function test_distance() + { + compare(zero.distanceTo(plusone), zero.distanceTo(minusone)) + compare(2*plusone.distanceTo(zero), plusone.distanceTo(minusone)) + compare(zero.distanceTo(plusone) > 0, true) + } + + function test_azimuth() + { + compare(zero.azimuthTo(north), 0) + compare(zero.azimuthTo(plusone), 90) + compare(zero.azimuthTo(minusone), 270) + compare(minusone.azimuthTo(plusone), 360 - plusone.azimuthTo(minusone)) + } + + function test_atDistanceAndAzimuth() + { + // 112km is approximately one degree of arc + + var coord_0d = zero.atDistanceAndAzimuth(112000, 0) + compare(coord_0d.latitude > 0.95, true) + compare(coord_0d.latitude < 1.05, true) + compare(coord_0d.longitude < 0.05, true) + compare(coord_0d.longitude > -0.05, true) + compare(zero.distanceTo(coord_0d), 112000) + compare(zero.azimuthTo(coord_0d), 0) + + var coord_90d = zero.atDistanceAndAzimuth(112000, 90) + compare(coord_90d.longitude > 0.95, true) + compare(coord_90d.longitude < 1.05, true) + compare(coord_90d.latitude < 0.05, true) + compare(coord_90d.latitude > -0.05, true) + compare(zero.distanceTo(coord_90d), 112000) + compare(zero.azimuthTo(coord_90d), 90) + + var coord_30d = zero.atDistanceAndAzimuth(20000, 30) + compare(coord_30d.longitude > 0, true) + compare(coord_30d.latitude > 0, true) + compare(zero.distanceTo(coord_30d), 20000) + compare(zero.azimuthTo(coord_30d), 30) + + var coord_30d2 = coord_30d.atDistanceAndAzimuth(200, 30) + compare(zero.distanceTo(coord_30d2), 20200) + } + } + + TestCase { + name: "CoordinateAnimation" + + function init() + { + coordinateAnimation.stop() + coordinateAnimationStartSpy.clear() + coordinateAnimationStopSpy.clear() + coordinateAnimationDirectionSpy.clear() + coordinateAnimation.from = QtPositioning.coordinate(50,50) + coordinateAnimation.to = QtPositioning.coordinate(50,50) + coordinateAnimation.direction = CoordinateAnimation.Shortest + coordinateItem.coordinate = QtPositioning.coordinate(50,50) + coordinateItem.coordinateList = [] + coordinateItem.coordinateCount = 0 + } + + function initTestCase() + { + compare(coordinateAnimation.direction, CoordinateAnimation.Shortest) + compare(coordinateAnimationDirectionSpy.count,0) + coordinateAnimation.direction = CoordinateAnimation.Shortest + compare(coordinateAnimationDirectionSpy.count,0) + coordinateAnimation.direction = CoordinateAnimation.West + compare(coordinateAnimationDirectionSpy.count,1) + coordinateAnimation.direction = CoordinateAnimation.East + compare(coordinateAnimationDirectionSpy.count,2) + } + + function toMercator(coord) + { + var pi = Math.PI + var lon = coord.longitude / 360.0 + 0.5; + + var lat = coord.latitude; + lat = 0.5 - (Math.log(Math.tan((pi / 4.0) + (pi / 2.0) * lat / 180.0)) / pi) / 2.0; + lat = Math.max(0.0, lat); + lat = Math.min(1.0, lat); + + return {'latitude': lat, 'longitude': lon}; + } + + function coordinate_animation(from, to, movingEast) + { + var fromMerc = toMercator(from) + var toMerc = toMercator(to) + var delta = (toMerc.latitude - fromMerc.latitude) / (toMerc.longitude - fromMerc.longitude) + + compare(coordinateItem.coordinateList.length, 0); + coordinateAnimation.from = from + coordinateAnimation.to = to + coordinateAnimation.start() + tryCompare(coordinateAnimationStartSpy,"count",1) + tryCompare(coordinateAnimationStopSpy,"count",1) + + //check correct start position + compare(coordinateItem.coordinateList[0], from) + //check correct end position + compare(coordinateItem.coordinateList[coordinateItem.coordinateList.length - 1],to) + + var i + var lastLongitude + for (i in coordinateItem.coordinateList) { + var coordinate = coordinateItem.coordinateList[i] + var mercCoordinate = toMercator(coordinate) + + //check that coordinates from the animation is along a straight line between from and to + var estimatedLatitude = fromMerc.latitude + (mercCoordinate.longitude - fromMerc.longitude) * delta + verify(mercCoordinate.latitude - estimatedLatitude < 0.00000000001); + + //check that each step has moved in the right direction + + if (lastLongitude) { + if (movingEast) { + if (coordinate.longitude > 0 && lastLongitude < 0) + verify(coordinate.longitude < lastLongitude + 360) + else + verify(coordinate.longitude < lastLongitude) + } else { + if (coordinate.longitude < 0 && lastLongitude > 0) + verify(coordinate.longitude + 360 > lastLongitude) + else + verify(coordinate.longitude > lastLongitude) + } + } + lastLongitude = coordinate.longitude + } + } + + function test_default_coordinate_animation() + { + //shortest + coordinate_animation(QtPositioning.coordinate(58.0,12.0), + QtPositioning.coordinate(62.0,24.0), + false) + } + + function test_east_direction_coordinate_animation(data) + { + coordinateAnimation.direction = CoordinateAnimation.East + coordinate_animation(data.from, + data.to, + true) + } + + function test_east_direction_coordinate_animation_data() + { + return [ + { from: QtPositioning.coordinate(58.0,24.0), to: QtPositioning.coordinate(58.0,12.0) }, + { from: QtPositioning.coordinate(58.0,12.0), to: QtPositioning.coordinate(58.0,24.0) }, + ] + } + + + function test_west_direction_coordinate_animation(data) + { + coordinateAnimation.direction = CoordinateAnimation.West + coordinate_animation(data.from, + data.to, + false) + } + + function test_west_direction_coordinate_animation_data() + { + return [ + { from: QtPositioning.coordinate(58.0,24.0),to: QtPositioning.coordinate(58.0,12.0) }, + { from: QtPositioning.coordinate(58.0,12.0),to: QtPositioning.coordinate(58.0,24.0) }, + ] + } + + + } +} diff --git a/tests/auto/declarative_core/tst_editorialmodel.qml b/tests/auto/declarative_core/tst_editorialmodel.qml new file mode 100644 index 0000000..4cb38e5 --- /dev/null +++ b/tests/auto/declarative_core/tst_editorialmodel.qml @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "EditorialModel" + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + PluginParameter { + name: "initializePlaceData" + value: true + } + ] + } + + EditorialModel { + id: testModel + } + + Place { + id: testPlace + name: "Test Place" + } + + Place { + id: parkViewHotel + placeId: "4dcc74ce-fdeb-443e-827c-367438017cf1" + plugin: testPlugin + } + + Place { + id: seaViewHotel + placeId: "8f72057a-54b2-4e95-a7bb-97b4d2b5721e" + plugin: testPlugin + } + + function test_setAndGet_data() { + return [ + { tag: "place", property: "place", signal: "placeChanged", value: testPlace }, + { tag: "batchSize", property: "batchSize", signal: "batchSizeChanged", value: 10, reset: 1 }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testModel, data); + } + + function test_consecutive_fetch_data() { + return [ + { tag: "batchSize 1", batchSize: 1 }, + { tag: "batchSize 2", batchSize: 2 }, + { tag: "batchSize 5", batchSize: 5 }, + { tag: "batchSize 10", batchSize: 10 }, + ]; + } + + function test_consecutive_fetch(data) { + var expectedEditorials = [ + { + "title": "Editorial 1", + "text": "Editorial 1 Text", + "language": "en" + }, + { + "title": "Editorial 2", + "text": "Editorial 2 Text", + "language": "en" + }, + { + "title": "Editorial 3", + "text": "Editorial 3 Text", + "language": "en" + }, + { + "title": "", + "text": "", + "language": "", + }, + { + "title": "Editorial 5", + "text": "Editorial 5 Text", + "language": "en" + } + ] + + var model = createModel(); + Utils.testConsecutiveFetch(testCase, model, parkViewHotel, expectedEditorials, data); + model.destroy(); + } + + function test_reset() { + var model = createModel(); + Utils.testReset(testCase, model, parkViewHotel); + model.destroy(); + } + + function test_fetch_data() { + return [ + { + tag: "fetch all editorials in a single batch", + model: createModel(), + batchSize: 10, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 5 + }, + { + tag: "fetch from a place with no editorials", + model: createModel(), + batchSize: 1, + place: seaViewHotel, + expectedTotalCount: 0, + expectedCount: 0 + }, + { + tag: "fetch with batch size one less than the total", + model: createModel(), + batchSize: 4, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 4 + }, + { + tag: "fetch with batch size equal to the total", + model: createModel(), + batchSize: 5, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 5 + }, + { + tag: "fetch with batch size larger than the total", + model: createModel(), + batchSize: 6, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 5 + } + ] + } + + function test_fetch(data) { + Utils.testFetch(testCase, data); + data.model.destroy(); + } + + function createModel() { + return Qt.createQmlObject('import QtLocation 5.3; EditorialModel {}', + testCase, "editorialModel"); + } +} diff --git a/tests/auto/declarative_core/tst_geocoding.qml b/tests/auto/declarative_core/tst_geocoding.qml new file mode 100644 index 0000000..1eadf87 --- /dev/null +++ b/tests/auto/declarative_core/tst_geocoding.qml @@ -0,0 +1,641 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import QtPositioning 5.2 + +Item { + Plugin { id: testPlugin1; name: "qmlgeo.test.plugin"; allowExperimental: true} + Plugin { id: errorPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true + parameters: [ + PluginParameter { name: "error"; value: "1"}, + PluginParameter { name: "errorString"; value: "This error was expected. No worries !"} + ] + } + + + property variant coordinate1: QtPositioning.coordinate(51, 41) + property variant coordinate2: QtPositioning.coordinate(52, 42) + property variant coordinate3: QtPositioning.coordinate(53, 43) + property variant emptyCoordinate: QtPositioning.coordinate() + + property variant boundingBox1: QtPositioning.rectangle(coordinate1, coordinate2) + property variant boundingBox2: QtPositioning.rectangle(coordinate1, coordinate3) + property variant boundingCircle1: QtPositioning.circle(coordinate1, 100) + property variant boundingCircle2: QtPositioning.circle(coordinate2, 100) + + property variant emptyBox: QtPositioning.rectangle() + + GeocodeModel {id: emptyModel} + + Address {id: emptyAddress} + SignalSpy {id: querySpy; target: emptyModel; signalName: "queryChanged"} + SignalSpy {id: autoUpdateSpy; target: emptyModel; signalName: "autoUpdateChanged"} + SignalSpy {id: pluginSpy; target: emptyModel ; signalName: "pluginChanged"} + SignalSpy {id: boundsSpy; target: emptyModel; signalName: "boundsChanged"} + SignalSpy {id: limitSpy; target: emptyModel; signalName: "limitChanged"} + SignalSpy {id: offsetSpy; target: emptyModel; signalName: "offsetChanged"} + + TestCase { + id: testCase1 + name: "GeocodeModel" + function test_model_defaults_and_setters() { + // Query: address + compare (querySpy.count, 0) + emptyModel.query = address1 + compare (querySpy.count, 1) + compare (emptyModel.query.street, address1.street) + emptyModel.query = address1 + compare (querySpy.count, 1) + compare (emptyModel.query.street, address1.street) + // Query: coordinate + emptyModel.query = coordinate1 + compare (querySpy.count, 2) + compare (emptyModel.query.latitude, coordinate1.latitude) + emptyModel.query = coordinate1 + compare (querySpy.count, 2) + compare (emptyModel.query.latitude, coordinate1.latitude) + // Query: string + emptyModel.query = "Kuortane, Finland" + compare (querySpy.count, 3) + compare (emptyModel.query, "Kuortane, Finland") + emptyModel.query = "Kuortane, Finland" + compare (querySpy.count, 3) + compare (emptyModel.query, "Kuortane, Finland") + + // limit and offset + compare (limitSpy.count, 0) + compare (offsetSpy.count, 0) + compare(emptyModel.limit, -1) + compare(emptyModel.offset, 0) + emptyModel.limit = 2 + compare (limitSpy.count, 1) + emptyModel.limit = 2 + compare (limitSpy.count, 1) + emptyModel.offset = 10 + compare (offsetSpy.count, 1) + emptyModel.offset = 10 + compare (offsetSpy.count, 1) + + // bounding box + compare(boundsSpy.count, 0) + emptyModel.bounds = boundingBox1 + compare(boundsSpy.count, 1) + compare(emptyModel.bounds.topLeft.latitude, boundingBox1.topLeft.latitude) + compare(emptyModel.bounds.bottomRight.longitude, boundingBox1.bottomRight.longitude) + emptyModel.bounds = boundingBox1 + compare(boundsSpy.count, 1) + compare(emptyModel.bounds.topLeft.latitude, boundingBox1.topLeft.latitude) + compare(emptyModel.bounds.bottomRight.longitude, boundingBox1.bottomRight.longitude) + emptyModel.bounds = boundingBox2 + compare(boundsSpy.count, 2) + compare(emptyModel.bounds.topLeft.latitude, boundingBox2.topLeft.latitude) + compare(emptyModel.bounds.bottomRight.longitude, boundingBox2.bottomRight.longitude) + emptyModel.bounds = QtPositioning.rectangle(); + compare(boundsSpy.count, 3) + + + // bounding circle + boundsSpy.clear() + emptyModel.bounds = boundingCircle1 + compare(boundsSpy.count, 1) + compare(emptyModel.bounds.center.latitude, coordinate1.latitude) + emptyModel.bounds = boundingCircle1 + compare(boundsSpy.count, 1) + compare(emptyModel.bounds.center.latitude, coordinate1.latitude) + emptyModel.bounds = boundingCircle2 + compare(boundsSpy.count, 2) + compare(emptyModel.bounds.center.latitude, coordinate2.latitude) + var dynamicCircle = QtPositioning.circle(QtPositioning.coordinate(8, 9)); + emptyModel.bounds = dynamicCircle + compare(boundsSpy.count, 3) + compare(emptyModel.bounds.center.latitude, dynamicCircle.center.latitude) + + // status + compare (emptyModel.status, GeocodeModel.Null) + + // error + compare (emptyModel.errorString, "") + compare (emptyModel.error, GeocodeModel.NoError) + + // count + compare( emptyModel.count, 0) + + // auto update + compare (autoUpdateSpy.count, 0) + compare (emptyModel.autoUpdate, false) + emptyModel.autoUpdate = true + compare (emptyModel.autoUpdate, true) + compare (autoUpdateSpy.count, 1) + emptyModel.autoUpdate = true + compare (emptyModel.autoUpdate, true) + compare (autoUpdateSpy.count, 1) + + // mustn't crash even we don't have plugin + emptyModel.update() + + // Plugin + compare(pluginSpy.count, 0) + emptyModel.plugin = testPlugin1 + compare(pluginSpy.count, 1) + compare(emptyModel.plugin, testPlugin1) + emptyModel.plugin = testPlugin1 + compare(pluginSpy.count, 1) + emptyModel.plugin = errorPlugin + compare(pluginSpy.count, 2) + } + // Test that model acts gracefully when plugin is not set or is invalid + // (does not support geocoding) + GeocodeModel {id: errorModel; plugin: errorPlugin} + GeocodeModel {id: errorModelNoPlugin} + SignalSpy {id: countInvalidSpy; target: errorModel; signalName: "countChanged"} + SignalSpy {id: errorSpy; target: errorModel; signalName: "errorChanged"} + function test_error_plugin() { + // test plugin not set + compare(errorModelNoPlugin.error,GeocodeModel.NoError) + errorModelNoPlugin.update() + compare(errorModelNoPlugin.error,GeocodeModel.EngineNotSetError) + console.log(errorModelNoPlugin.errorString) + + //plugin set but otherwise not offering anything + compare(errorModel.error,GeocodeModel.EngineNotSetError) + compare(errorModel.errorString,"This error was expected. No worries !") + errorSpy.clear() + errorModel.update() + compare(errorModel.error,GeocodeModel.EngineNotSetError) + compare(errorModel.errorString,qsTr("Cannot geocode, geocode manager not set.")) + compare(errorSpy.count, 1) + errorSpy.clear() + errorModel.cancel() + compare(errorModel.error,GeocodeModel.NoError) + compare(errorModel.errorString,"") + compare(errorSpy.count, 1) + errorSpy.clear() + errorModel.reset() + compare(errorModel.error,GeocodeModel.NoError) + compare(errorModel.errorString,"") + compare(errorSpy.count, 0) + errorSpy.clear() + errorModel.update() + compare(errorModel.error,GeocodeModel.EngineNotSetError) + compare(errorModel.errorString,qsTr("Cannot geocode, geocode manager not set.")) + compare(errorSpy.count, 1) + errorSpy.clear() + var location = errorModel.get(-1) + compare(location, null) + } + + } + Address {id: address1; street: "wellknown street"; city: "expected city"; county: "2"} + Address {id: errorAddress1; street: "error"; county: "2"} // street is the error reason + + property variant rcoordinate1: QtPositioning.coordinate(51, 2) + property variant errorCoordinate1: QtPositioning.coordinate(73, 2) // (latiude mod 70) is the error code + property variant slackCoordinate1: QtPositioning.coordinate(60, 3) + Address {id: slackAddress1; street: "Slacker st"; city: "Lazy town"; county: "4"} + + property variant automaticCoordinate1: QtPositioning.coordinate(60, 3) + Address {id: automaticAddress1; street: "Auto st"; city: "Detroit"; county: "4"} + + Plugin { + id: testPlugin2; + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "supported"; value: true}, + PluginParameter { name: "finishRequestImmediately"; value: true}, + PluginParameter { name: "validateWellKnownValues"; value: true} + ] + } + + Plugin { + id: immediatePlugin; + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "supported"; value: true}, + PluginParameter { name: "finishRequestImmediately"; value: true}, + PluginParameter { name: "validateWellKnownValues"; value: false} + ] + } + + Plugin { + id: slackPlugin; + allowExperimental: true + name: "qmlgeo.test.plugin" + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "supported"; value: true}, + PluginParameter { name: "finishRequestImmediately"; value: false}, + PluginParameter { name: "validateWellKnownValues"; value: false} + ] + } + + Plugin { + id: autoPlugin; + allowExperimental: true + name: "qmlgeo.test.plugin" + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "supported"; value: true}, + PluginParameter { name: "finishRequestImmediately"; value: false}, + PluginParameter { name: "validateWellKnownValues"; value: false} + ] + } + + GeocodeModel {id: testModel; plugin: testPlugin2} + SignalSpy {id: locationsSpy; target: testModel; signalName: "locationsChanged"} + SignalSpy {id: countSpy; target: testModel; signalName: "countChanged"} + SignalSpy {id: testQuerySpy; target: testModel; signalName: "queryChanged"} + SignalSpy {id: testStatusSpy; target: testModel; signalName: "statusChanged"} + + GeocodeModel {id: slackModel; plugin: slackPlugin; } + SignalSpy {id: locationsSlackSpy; target: slackModel; signalName: "locationsChanged"} + SignalSpy {id: countSlackSpy; target: slackModel; signalName: "countChanged"} + SignalSpy {id: querySlackSpy; target: slackModel; signalName: "queryChanged"} + SignalSpy {id: errorStringSlackSpy; target: slackModel; signalName: "errorChanged"} + SignalSpy {id: errorSlackSpy; target: slackModel; signalName: "errorChanged"} + SignalSpy {id: pluginSlackSpy; target: slackModel; signalName: "pluginChanged"} + + GeocodeModel {id: immediateModel; plugin: immediatePlugin} + SignalSpy {id: locationsImmediateSpy; target: immediateModel; signalName: "locationsChanged"} + SignalSpy {id: countImmediateSpy; target: immediateModel; signalName: "countChanged"} + SignalSpy {id: queryImmediateSpy; target: immediateModel; signalName: "queryChanged"} + SignalSpy {id: statusImmediateSpy; target: immediateModel; signalName: "statusChanged"} + SignalSpy {id: errorStringImmediateSpy; target: immediateModel; signalName: "errorChanged"} + SignalSpy {id: errorImmediateSpy; target: immediateModel; signalName: "errorChanged"} + + GeocodeModel {id: automaticModel; plugin: autoPlugin; query: automaticAddress1; autoUpdate: true} + SignalSpy {id: automaticLocationsSpy; target: automaticModel; signalName: "locationsChanged"} + + TestCase { + name: "GeocodeModelGeocoding" + function clear_slack_model() { + slackModel.reset() + locationsSlackSpy.clear() + countSlackSpy.clear() + querySlackSpy.clear() + errorStringSlackSpy.clear() + errorSlackSpy.clear() + slackModel.limit = -1 + slackModel.offset = 0 + } + function clear_immediate_model() { + immediateModel.reset() + locationsImmediateSpy.clear() + countImmediateSpy.clear() + queryImmediateSpy.clear() + errorStringImmediateSpy.clear() + errorImmediateSpy.clear() + statusImmediateSpy.clear() + immediateModel.limit = -1 + immediateModel.offset = 0 + } + function test_reset() { + clear_immediate_model(); + immediateModel.query = errorAddress1 + immediateModel.update() + compare (immediateModel.errorString, errorAddress1.street) + compare (immediateModel.error, GeocodeModel.CommunicationError) + compare (immediateModel.count, 0) + compare (statusImmediateSpy.count, 2) + compare (immediateModel.status, GeocodeModel.Error) + immediateModel.reset() + compare (immediateModel.errorString, "") + compare (immediateModel.error, GeocodeModel.NoError) + compare (immediateModel.status, GeocodeModel.Null) + // Check that ongoing req is aborted + clear_slack_model() + slackModel.query = slackAddress1 + slackAddress1.county = "5" + slackModel.update() + tryCompare(countSlackSpy, "count", 0) + compare (locationsSlackSpy.count, 0) + compare (slackModel.count, 0) + slackModel.reset() + tryCompare(countSlackSpy, "count", 0) + compare (locationsSlackSpy.count, 0) + compare (slackModel.count, 0) + // Check that results are cleared + slackModel.update() + tryCompare(slackModel, "count", 5) + slackModel.reset() + compare (slackModel.count, 0) + // Check that changing plugin resets any ongoing requests + clear_slack_model() + slackModel.query = slackAddress1 + slackAddress1.county = "7" + compare (pluginSlackSpy.count, 0) + slackModel.update() + tryCompare(countSlackSpy, "count", 0) + slackModel.plugin = errorPlugin + tryCompare(countSlackSpy, "count", 0) + compare (pluginSlackSpy.count, 1) + // switch back and check that works + slackModel.plugin = slackPlugin + compare (pluginSlackSpy.count, 2) + slackModel.update() + tryCompare(countSlackSpy, "count", 0) + tryCompare(countSlackSpy, "count", 1) + } + function test_error_geocode() { + // basic immediate geocode error + clear_immediate_model() + immediateModel.query = errorAddress1 + immediateModel.update() + compare (errorStringImmediateSpy.count, 1) + compare (immediateModel.errorString, errorAddress1.street) + compare (immediateModel.error, GeocodeModel.CommunicationError) // county of the address (2) + compare (immediateModel.count, 0) + compare (statusImmediateSpy.count, 2) + compare (immediateModel.status, GeocodeModel.Error) + // basic delayed geocode error + clear_slack_model() + slackModel.query = errorAddress1 + errorAddress1.street = "error code 2" + slackModel.update() + compare (errorStringSlackSpy.count, 0) + compare (errorSlackSpy.count, 0) + tryCompare (errorStringSlackSpy, "count", 1) + tryCompare (errorSlackSpy, "count", 1) + compare (slackModel.errorString, errorAddress1.street) + compare (slackModel.error, GeocodeModel.CommunicationError) + compare (slackModel.count, 0) + // Check that we recover + slackModel.query = address1 + slackModel.update() + tryCompare(countSlackSpy, "count", 1) + compare (slackModel.count, 2) + compare (errorStringSlackSpy.count, 2) + compare (errorSlackSpy.count, 2) + compare (slackModel.errorString, "") + compare (slackModel.error, GeocodeModel.NoError) + } + + function test_error_reverse_geocode() { + // basic immediate geocode error + clear_immediate_model() + immediateModel.query = errorCoordinate1 + immediateModel.update() + if (immediateModel.errorString != "") + compare (errorStringImmediateSpy.count, 1) // the previous error is cleared upon update() + else + compare (errorImmediateSpy.count, 1) + compare (immediateModel.errorString, "error") + compare (immediateModel.error, GeocodeModel.ParseError) + compare (immediateModel.count, 0) + compare (statusImmediateSpy.count, 2) + compare (immediateModel.status, GeocodeModel.Error) + // basic delayed geocode error + clear_slack_model() + slackModel.query = errorCoordinate1 + slackModel.update() + compare (errorStringSlackSpy.count, 0) + compare (errorSlackSpy.count, 0) + if (slackModel.errorString != "") + tryCompare (errorStringSlackSpy, "count", 2) + else + tryCompare (errorStringSlackSpy, "count", 1) + compare (slackModel.errorString, "error") + compare (slackModel.error, GeocodeModel.ParseError) + compare (slackModel.count, 0) + // Check that we recover + slackModel.query = rcoordinate1 + slackModel.update() + tryCompare(countSlackSpy, "count", 1) + compare (slackModel.count, 2) + compare (errorStringSlackSpy.count, 2) + compare (errorSlackSpy.count, 2) + compare (slackModel.errorString, "") + compare (slackModel.error, GeocodeModel.NoError) + } + function test_address_geocode() { + testQuerySpy.clear() + locationsSpy.clear() + testStatusSpy.clear() + testModel.reset() + countSpy.clear() + compare (locationsSpy.count, 0) + compare (testModel.errorString, "") + compare (testModel.error, GeocodeModel.NoError) + compare (testModel.count, 0) + testModel.query = address1 + compare (testQuerySpy.count, 1) + testModel.update() + tryCompare (locationsSpy, "count", 1) // 5 sec + compare (testModel.errorString, "") + compare (testModel.error, GeocodeModel.NoError) + compare (testModel.count, 2) + compare (testQuerySpy.count, 1) + compare (testStatusSpy.count, 2) + compare (testModel.status, GeocodeModel.Ready) + compare (testModel.get(0).address.street, "wellknown street") + compare (testModel.get(0).address.city, "expected city") + } + + function test_freetext_geocode() { + testQuerySpy.clear() + locationsSpy.clear() + testStatusSpy.clear() + testModel.reset() + countSpy.clear() + compare (locationsSpy.count, 0) + compare (testModel.errorString, "") + compare (testModel.error, GeocodeModel.NoError) + compare (testModel.count, 0) + testModel.limit = 5 // number of places echoed back + testModel.offset = 10 // 'county' set in the places + // Test successful case + testModel.query = "Freetext geocode" + compare(testQuerySpy.count, 1) + testModel.update(); + tryCompare (locationsSpy, "count", 1) // 5 sec + tryCompare(countSpy, "count", 1) + tryCompare(testModel, "count", 5) + compare(testModel.get(0).address.county, "10") + // Test error case + testModel.query = "2" // tells plugin to echo error '2' + compare(testQuerySpy.count, 2) + testModel.update(); + tryCompare (locationsSpy, "count", 2) // 5 sec + tryCompare(countSpy, "count", 2) + tryCompare(testModel, "count", 0) + compare(testModel.errorString, "2") + compare (testModel.error, GeocodeModel.CommunicationError) + testModel.reset() + tryCompare(countSpy, "count", 2) + compare (testModel.count, 0) + } + + function test_delayed_freetext_geocode() { + clear_slack_model() + slackModel.limit = 5 // number of places echoed back + slackModel.offset = 10 // 'county' set in the places + // Basic successful case + slackModel.query = "freetext geocode" + compare (querySlackSpy.count, 1) + slackModel.update() + tryCompare(countSlackSpy, "count", 0) + compare (locationsSlackSpy.count, 0) + compare (slackModel.count, 0) + tryCompare(countSlackSpy, "count", 1); //waits up to 5s + compare (slackModel.count, 5) + compare (locationsSlackSpy.count, 1) + // Frequent updates, previous requests are aborted + slackModel.reset() + locationsSlackSpy.clear() + countSlackSpy.clear() + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + tryCompare(countSlackSpy, "count", 1); //waits up to 5s + compare (locationsSlackSpy.count, 1) + compare(slackModel.count, 5) // limit + } + + function test_geocode_auto_updates() { + compare (automaticModel.count, 4) // should be something already + compare (automaticLocationsSpy.count, 1) + // change query and its contents and verify that autoupdate occurs + automaticAddress1.county = 6 + tryCompare(automaticLocationsSpy, "count", 2) + compare (automaticModel.count, 6) + automaticAddress1.street = "The Avenue" + tryCompare(automaticLocationsSpy, "count", 3) + compare (automaticModel.count, 6) + automaticModel.query = automaticCoordinate1 + tryCompare(automaticLocationsSpy, "count", 4) + compare (automaticModel.count, 3) + } + + function test_delayed_geocode() { + // basic delayed response + slackModel.reset() + querySlackSpy.clear() + countSlackSpy.clear() + locationsSlackSpy.clear() + slackModel.query = slackAddress1 + slackAddress1.county = "7" + compare (querySlackSpy.count, 1) + slackModel.update() + tryCompare(countSlackSpy, "count", 0) + compare (locationsSlackSpy.count, 0) + compare (slackModel.count, 0) + tryCompare(countSlackSpy, "count", 1); //waits up to 5s + compare (locationsSlackSpy.count, 1) + compare (slackModel.count, 7) // slackAddress1.county) + // Frequent updates, previous requests are aborted + slackModel.reset() + locationsSlackSpy.clear() + countSlackSpy.clear() + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + tryCompare(countSlackSpy, "count", 1); //waits up to 5s + compare (locationsSlackSpy.count, 1) + compare(slackModel.count, 7) // slackAddress1.county + } + function test_reverse_geocode() { + testModel.reset() + testQuerySpy.clear() + locationsSpy.clear() + testStatusSpy.clear() + countSpy.clear() + compare (testModel.errorString, "") + compare (testModel.error, GeocodeModel.NoError) + compare (testModel.count, 0) + compare (testQuerySpy.count, 0) + testModel.query = rcoordinate1 + compare (testQuerySpy.count, 1) + testModel.update() + tryCompare (locationsSpy, "count", 1) // 5 sec + tryCompare(countSpy, "count", 1) + compare (testModel.errorString, "") + compare (testModel.error, GeocodeModel.NoError) + compare (testModel.count, 2) + testModel.reset() + tryCompare(countSpy, "count", 2) + compare (testModel.count, 0) + } + function test_delayed_reverse_geocode() { + clear_slack_model() + slackModel.query = slackCoordinate1 + compare (querySlackSpy.count, 1) + slackModel.update() + tryCompare(countSlackSpy, "count", 0) + compare (locationsSlackSpy.count, 0) + compare (slackModel.count, 0) + + tryCompare(countSlackSpy, "count", 1); //waits up to 5s + compare (locationsSlackSpy.count, 1) + compare (slackModel.count, 3) // slackCoordinate1.longitude + // Frequent updates, previous requests are aborted + slackModel.reset() + locationsSlackSpy.clear() + countSlackSpy.clear() + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + slackModel.update() + tryCompare(locationsSlackSpy, "count", 0) + compare(countSlackSpy.count, 0) + + tryCompare(countSlackSpy, "count", 1); //waits up to 5s + compare(locationsSlackSpy.count, 1) + compare(slackModel.count, 3) // slackCoordinate1.longitude + } + } +} diff --git a/tests/auto/declarative_core/tst_imagemodel.qml b/tests/auto/declarative_core/tst_imagemodel.qml new file mode 100644 index 0000000..2fa5093 --- /dev/null +++ b/tests/auto/declarative_core/tst_imagemodel.qml @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "ImageModel" + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + PluginParameter { + name: "initializePlaceData" + value: true + } + ] + } + + ImageModel { + id: testModel + } + + Place { + id: testPlace + name: "Test Place" + } + + Place { + id: parkViewHotel + placeId: "4dcc74ce-fdeb-443e-827c-367438017cf1" + plugin: testPlugin + } + + Place { + id: seaViewHotel + placeId: "8f72057a-54b2-4e95-a7bb-97b4d2b5721e" + plugin: testPlugin + } + + function test_setAndGet_data() { + return [ + { tag: "place", property: "place", signal: "placeChanged", value: testPlace }, + { tag: "batchSize", property: "batchSize", signal: "batchSizeChanged", value: 10, reset: 1 }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testModel, data); + } + + function test_consecutive_fetch_data() { + return [ + { tag: "batchSize 1", batchSize: 1 }, + { tag: "batchSize 2", batchSize: 2 }, + { tag: "batchSize 5", batchSize: 5 }, + { tag: "batchSize 10", batchSize: 10 }, + ]; + } + + function test_consecutive_fetch(data) { + var expectedImages = [ + { + "url": "http://somewhere.com/image1.png", + "imageId": "0001", + "mimeType": "image/png" + }, + { + "url": "http://somewhere.com/image2.png", + "imageId": "0002", + "mimeType": "image/png" + }, + { + "url": "http://somewhere.com/image3.png", + "imageId": "0003", + "mimeType": "image/png" + }, + { + "url": "", + "imageId": "", + "mimeType": "" + }, + { + "url": "http://somewhere.com/image5.png", + "imageId": "0005", + "mimeType": "image/png" + } + ] + + var model = createModel(); + Utils.testConsecutiveFetch(testCase, model, parkViewHotel, expectedImages, data); + model.destroy(); + } + + function test_reset() { + var model = createModel(); + Utils.testReset(testCase, model, parkViewHotel); + model.destroy(); + } + + function test_fetch_data() { + return [ + { + tag: "fetch all images in a single batch", + model: createModel(), + batchSize: 10, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 5 + }, + { + tag: "fetch from a place with no images", + model: createModel(), + batchSize: 1, + place: seaViewHotel, + expectedTotalCount: 0, + expectedCount: 0 + }, + { + tag: "fetch with batch size one less than the total", + model: createModel(), + batchSize: 4, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 4 + }, + { + tag: "fetch with batch size equal to the total", + model: createModel(), + batchSize: 5, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 5 + }, + { + tag: "fetch with batch size larger than the total", + model: createModel(), + batchSize: 6, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 5 + } + ] + } + + function test_fetch(data) { + Utils.testFetch(testCase, data); + data.model.destroy(); + } + + function createModel() { + return Qt.createQmlObject('import QtLocation 5.3; ImageModel {}', + testCase, "imageModel"); + } +} diff --git a/tests/auto/declarative_core/tst_place.qml b/tests/auto/declarative_core/tst_place.qml new file mode 100644 index 0000000..459b8f3 --- /dev/null +++ b/tests/auto/declarative_core/tst_place.qml @@ -0,0 +1,627 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import QtPositioning 5.2 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "Place" + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + } + + Place { + id: favoritePlace + name: "Favorite Place" + } + + Place { id: emptyPlace } + + Place { id: emptyPlace2 } + + Place { id: testPlace } + + Place { + id: savePlace + + name: "Test place" + + visibility: Place.DeviceVisibility + + location: Location { + address: Address { + country: "country" + countryCode: "cc" + state: "state" + county: "county" + city: "city" + district: "district" + street: "123 Fake Street" + postalCode: "1234" + } + + coordinate { + latitude: 10 + longitude: 10 + altitude: 100 + } + + boundingBox { + center: QtPositioning.coordinate(10, 10, 100) + width: 100 + height: 100 + } + } + + ratings: Ratings { + average: 3.5 + count: 10 + } + + supplier: Supplier { + name: "Supplier 1" + supplierId: "supplier-id-1" + url: "http://www.example.com/supplier-id-1/" + icon: Icon{ + plugin: testPlugin + Component.onCompleted: { + parameters.singleUrl = "http://www.example.com/supplier-id-1/icon" + } + } + } + + categories: [ + Category { + name: "Category 1" + categoryId: "category-id-1" + plugin: testPlugin + }, + Category { + name: "Category 2" + categoryId: "category-id-2" + plugin: testPlugin + } + ] + + icon: Icon { + Component.onCompleted: { + savePlace.icon.parameters.singleUrl = "http://example.com/test-place.png"; + } + } + } + + Place { + id: dummyPlace + placeId: "487" + name: "dummyPlace" + visibility: Place.PublicVisibility + } + + // compares two places property by property + function compare_place(place1, place2) { + // check simple properties + var simpleProperties = ["name", "placeId", "primaryPhone", "primaryFax", "primaryEmail", + "primaryUrl", "visibility"]; + for (x in simpleProperties) { + if (place1[simpleProperties[x]] !== place2[simpleProperties[x]]) + return false; + } + + // check categories + if (place1.categories.length !== place2.categories.length) + return false; + for (var i = 0; i < place1.categories.length; ++i) { + // fixme, what if the order of the two lists are not the same + if (place1.categories[i].categoryId !== place2.categories[i].categoryId) + return false; + if (place1.categories[i].name !== place2.categories[i].name) + return false; + } + + // check supplier + if (place1.supplier === null && place2.supplier !== null) + return false; + if (place1.supplier !== null && place2.supplier === null) + return false; + if (place1.supplier !== null && place2.supplier !== null) { + if (place1.supplier.supplierId !== place2.supplier.supplierId) + return false; + if (place1.supplier.name !== place2.supplier.name) + return false; + if (place1.supplier.url !== place2.supplier.url) + return false; + + // check supplier icon + if (place1.supplier.icon === null && place2.supplier.icon !== null) + return false; + if (place1.supplier.icon !== null && place2.supplier.icon === null) + return false; + if (place1.supplier.icon !== null && place2.supplier.icon !== null) { + if (place1.supplier.icon.parameters.keys().length !== place2.supplier.icon.parameters.keys().length) { + return false; + } + + var keys = place1.supplier.icon.parameters.keys() + place2.supplier.icon.parameters.keys(); + for (var i = 0; i < keys.length; ++i) { + if (place1.supplier.icon.parameters[keys[i]] != place2.supplier.icon.parameters[keys[i]]) { + return false; + } + } + + if (place1.supplier.icon.plugin !== place2.supplier.icon.plugin) + return false; + } + } + + // check ratings + if (place1. ratings === null && place2.ratings !== null) + return false; + if (place1.ratings !== null && place2.ratings === null) + return false; + if (place1.ratings !== null && place2.ratings !== null) { + if (place1.ratings.average !== place2.ratings.average) + return false; + if (place1.ratings.count !== place2.ratings.count) + return false; + } + + // check location + if (place1.location === null && place2.location !== null) + return false; + if (place1.location !== null && place2.location === null) + return false; + if (place1.location !== null && place2.location !== null) { + if (place1.location.address.country !== place2.location.address.country) + return false; + if (place1.location.address.countryCode !== place2.location.address.countryCode) + return false; + if (place1.location.address.state !== place2.location.address.state) + return false; + if (place1.location.address.county !== place2.location.address.county) + return false; + if (place1.location.address.city !== place2.location.address.city) + return false; + if (place1.location.address.district !== place2.location.address.district) + return false; + if (place1.location.address.street !== place2.location.address.street) + return false; + if (place1.location.address.postalCode !== place2.location.address.postalCode) + return false; + + if (place1.location.coordinate !== place2.location.coordinate) + return false; + if (place1.location.boundingBox !== place2.location.boundingBox) + return false; + } + + // check icon + if (place1.icon === null && place2.icon !== null) { + return false; + } + if (place1.icon !== null && place2.icon === null) { + return false; + } + if (place1.icon !== null && place2.icon !== null) { + if (place1.icon.plugin !== place2.icon.plugin) { + console.log(place1.icon.plugin + " " + place2.icon.plugin); + return false; + } + + if (place1.icon.parameters.keys().length !== place2.icon.parameters.keys().length) { + return false; + } + + var keys = place1.icon.parameters.keys() + place2.icon.parameters.keys(); + for (var i = 0; i < keys.length; ++i) { + if (place1.icon.parameters[keys[i]] + != place2.icon.parameters[keys[i]]) { + return false; + } + } + } + + // check extended attributes + + return true; + } + + function test_emptyPlace() { + // basic properties + compare(emptyPlace.plugin, null); + compare(emptyPlace.categories.length, 0); + compare(emptyPlace.name, ""); + compare(emptyPlace.placeId, ""); + compare(emptyPlace.detailsFetched, false); + compare(emptyPlace.status, Place.Ready); + compare(emptyPlace.primaryPhone, ""); + compare(emptyPlace.primaryFax, ""); + compare(emptyPlace.primaryEmail, ""); + compare(emptyPlace.primaryWebsite, ""); + compare(emptyPlace.visibility, Place.UnspecifiedVisibility); + compare(emptyPlace.attribution, ""); + + // complex properties + compare(emptyPlace.ratings.average, 0); + compare(emptyPlace.location.address.street, ''); + compare(emptyPlace.location.address.district, ''); + compare(emptyPlace.location.address.city, ''); + compare(emptyPlace.location.address.county, ''); + compare(emptyPlace.location.address.state, ''); + compare(emptyPlace.location.address.country, ''); + + compare(emptyPlace.icon.plugin, null); + + compare(emptyPlace.supplier.name, ''); + compare(emptyPlace.supplier.supplierId, ''); + compare(emptyPlace.supplier.url, ''); + + compare(emptyPlace.supplier.icon.plugin, null); + + compare(emptyPlace.reviewModel.totalCount, -1); + compare(emptyPlace.imageModel.totalCount, -1); + compare(emptyPlace.editorialModel.totalCount, -1); + compare(emptyPlace.categories.length, 0); + + verify(compare_place(emptyPlace, emptyPlace)); + verify(compare_place(emptyPlace, emptyPlace2)); + } + + function test_setAndGet_data() { + return [ + { tag: "name", property: "name", signal: "nameChanged", value: "Test Place", reset: "" }, + { tag: "placeId", property: "placeId", signal: "placeIdChanged", value: "test-place-id-1", reset: "" }, + { tag: "visibility", property: "visibility", signal: "visibilityChanged", value: Place.PublicVisibility, reset: Place.UnspecifiedVisibility }, + { tag: "attribution", property: "attribution", signal: "attributionChanged", value: "Place data from...", reset: "" }, + { tag: "favorite", property: "favorite", signal: "favoriteChanged", value: favoritePlace } + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testPlace, data); + } + + function test_categories() { + var categories = new Array(2); + categories[0] = Qt.createQmlObject('import QtLocation 5.3; Category { categoryId: "cat-id-1"; name: "Category 1" }', testCase, "Category1"); + categories[1] = Qt.createQmlObject('import QtLocation 5.3; Category { categoryId: "cat-id-2"; name: "Category 2" }', testCase, "Category2"); + + var signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = testPlace; + signalSpy.signalName = "categoriesChanged"; + + // set categories to something new + testPlace.categories = categories; + compare(testPlace.categories.length, categories.length); + + for (var i = 0; i < categories.length; ++i) { + compare(testPlace.categories[i].categoryId, categories[i].categoryId); + compare(testPlace.categories[i].name, categories[i].name); + } + + compare(signalSpy.count, 2); + + // set categories to the same (signal spy should not increase?) + testPlace.categories = categories; + compare(testPlace.categories.length, categories.length); + + for (var i = 0; i < categories.length; ++i) { + compare(testPlace.categories[i].categoryId, categories[i].categoryId); + compare(testPlace.categories[i].name, categories[i].name); + } + + compare(signalSpy.count, 5); // clear + append + append + + // reset by assignment + testPlace.categories = new Array(0); + compare(testPlace.categories.length, 0); + compare(signalSpy.count, 6); + + signalSpy.destroy(); + } + + function test_supplier() { + var supplier = Qt.createQmlObject('import QtLocation 5.3; Supplier { supplierId: "sup-id-1"; name: "Category 1" }', testCase, "Supplier1"); + + var signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = testPlace; + signalSpy.signalName = "supplierChanged"; + + // set supplier to something new + testPlace.supplier = supplier; + compare(testPlace.supplier, supplier); + + compare(testPlace.supplier.supplierId, supplier.supplierId); + compare(testPlace.supplier.name, supplier.name); + + compare(signalSpy.count, 1); + + // set supplier to the same + testPlace.supplier = supplier; + compare(testPlace.supplier, supplier); + + compare(testPlace.supplier.supplierId, supplier.supplierId); + compare(testPlace.supplier.name, supplier.name); + + compare(signalSpy.count, 1); + + // reset by assignment + testPlace.supplier = null; + compare(testPlace.supplier, null); + compare(signalSpy.count, 2); + + signalSpy.destroy(); + } + + function test_location() { + var location = Qt.createQmlObject('import QtPositioning 5.2; Location { coordinate: QtPositioning.coordinate(10.0, 20.0) }', testCase, "Location1"); + + var signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = testPlace; + signalSpy.signalName = "locationChanged"; + + testPlace.location = location; + compare(testPlace.location.coordinate.latitude, 10.0); + compare(signalSpy.count, 1); + + testPlace.location = location; + compare(testPlace.location.coordinate.latitude, 10.0); + compare(signalSpy.count, 1); + + testPlace.location = null; + compare(testPlace.location, null); + compare(signalSpy.count, 2); + + location.destroy(); + signalSpy.destroy(); + } + + function test_ratings() { + var ratings = Qt.createQmlObject('import QtLocation 5.3; Ratings { average: 3; count: 100 }', testCase, "Rating1"); + + var signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = testPlace; + signalSpy.signalName = "ratingsChanged"; + + testPlace.ratings = ratings; + compare(testPlace.ratings.average, 3); + compare(testPlace.ratings.count, 100); + compare(signalSpy.count, 1); + + testPlace.ratings = ratings; + compare(testPlace.ratings.average, 3); + compare(testPlace.ratings.count, 100); + compare(signalSpy.count, 1); + + testPlace.ratings = null; + compare(testPlace.ratings, null); + compare(signalSpy.count, 2); + + ratings.destroy(); + signalSpy.destroy(); + } + + function test_extendedAttributes() { + verify(testPlace.extendedAttributes); + + testPlace.extendedAttributes["foo"] = Qt.createQmlObject('import QtLocation 5.3; PlaceAttribute { text: "Foo"; label: "Foo label" }', testCase, 'PlaceAttribute'); + + verify(testPlace.extendedAttributes.foo); + compare(testPlace.extendedAttributes.foo.text, "Foo"); + compare(testPlace.extendedAttributes.foo.label, "Foo label"); + + testPlace.extendedAttributes["foo"] = null; + verify(!testPlace.extendedAttributes.foo); + } + + function test_contactDetailsProperty() { + verify(testPlace.contactDetails); + + testPlace.contactDetails["phone"] = Qt.createQmlObject('import QtLocation 5.3; ContactDetail { label: "Test Label"; value: "Detail Value" }', testCase, 'ContactDetail'); + + verify(testPlace.contactDetails.phone); + compare(testPlace.contactDetails.phone[0].label, "Test Label"); + compare(testPlace.contactDetails.phone[0].value, "Detail Value"); + + testPlace.contactDetails["phone"] = null; + verify(!testPlace.contactDetails.phone); + } + + function test_saveload() { + // Save a place + var signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = savePlace; + signalSpy.signalName = "statusChanged"; + + savePlace.plugin = testPlugin; + savePlace.icon.plugin = testPlugin; + savePlace.placeId = "invalid-place-id"; + + savePlace.save(); + + compare(savePlace.status, Place.Saving); + + tryCompare(savePlace, "status", Place.Error); + + // try again without an invalid placeId + savePlace.placeId = ""; + savePlace.save(); + + compare(savePlace.status, Place.Saving); + + tryCompare(savePlace, "status", Place.Ready); + + verify(savePlace.placeId !== ""); + + signalSpy.destroy(); + + + // Read a place + var readPlace = Qt.createQmlObject('import QtLocation 5.3; Place { }', testCase, "test_saveload"); + + signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = readPlace; + signalSpy.signalName = "statusChanged"; + + readPlace.plugin = testPlugin; + + readPlace.getDetails(); + + compare(readPlace.status, Place.Fetching); + tryCompare(readPlace, "status", Place.Error); + + readPlace.placeId = "invalid-id"; + + readPlace.getDetails(); + + compare(readPlace.status, Place.Fetching); + tryCompare(readPlace, "status", Place.Error); + + readPlace.placeId = savePlace.placeId; + + // verify that read place is not currently the same as what we saved + verify(!compare_place(readPlace, savePlace)); + + readPlace.getDetails(); + + compare(readPlace.status, Place.Fetching); + tryCompare(readPlace, "status", Place.Ready); + + // verify that read place is the same as what we saved + verify(compare_place(readPlace, savePlace)); + + signalSpy.destroy(); + + + // Remove a place + var removePlace = Qt.createQmlObject('import QtLocation 5.3; Place { }', testCase, "test_saveload"); + + signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = removePlace; + signalSpy.signalName = "statusChanged"; + + removePlace.plugin = testPlugin; + + removePlace.remove(); + + compare(removePlace.status, Place.Removing); + tryCompare(removePlace, "status", Place.Error); + + removePlace.placeId = "invalid-id"; + + removePlace.remove(); + + compare(removePlace.status, Place.Removing); + tryCompare(removePlace, "status", Place.Error); + + removePlace.placeId = savePlace.placeId; + + removePlace.remove(); + + compare(removePlace.status, Place.Removing); + tryCompare(removePlace, "status", Place.Ready); + + removePlace.getDetails(); + + compare(removePlace.status, Place.Fetching); + tryCompare(removePlace, "status", Place.Error); + + signalSpy.destroy(); + } + + function test_copy() { + var place = Qt.createQmlObject('import QtLocation 5.3; Place { }', this); + place.plugin = testPlugin; + place.copyFrom(dummyPlace); + compare(place.placeId, ""); + compare(place.name, "dummyPlace"); + compare(place.visibility, Place.UnspecifiedVisibility); + } + + function test_contactDetails(data) { + var place = Qt.createQmlObject('import QtLocation 5.3; Place {}', this); + + var signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = place; + signalSpy.signalName = data.signalName; + + var detail1 = Qt.createQmlObject('import QtLocation 5.3; ContactDetail {}', this); + detail1.label = "Detail1"; + detail1.value = "555-detail1"; + + place.contactDetails[data.contactType] = detail1; + compare(place.contactDetails[data.contactType].length, 1); + compare(place.contactDetails[data.contactType][0].label, "Detail1"); + compare(place.contactDetails[data.contactType][0].value, "555-detail1"); + + compare(place[data.primaryValue], "555-detail1"); + compare(signalSpy.count, 1); + signalSpy.clear(); + + var listView = Qt.createQmlObject('import QtQuick 2.0; ListView { delegate:Text{text:modelData.label + ":" + modelData.value } }', this); + listView.model = place.contactDetails[data.contactType]; + compare(listView.count, 1); + + var detail2 = Qt.createQmlObject('import QtLocation 5.3; ContactDetail {}', this); + detail2.label = "Detail2"; + detail2.value = "555-detail2"; + + var details = new Array(); + details.push(detail2); + details.push(detail1); + + place.contactDetails[data.contactType] = details; + compare(place.contactDetails[data.contactType].length, 2); + compare(place.contactDetails[data.contactType][0].label, "Detail2"); + compare(place.contactDetails[data.contactType][0].value, "555-detail2"); + compare(place.contactDetails[data.contactType][1].label, "Detail1"); + compare(place.contactDetails[data.contactType][1].value, "555-detail1"); + + compare(place[data.primaryValue], "555-detail2"); + compare(signalSpy.count, 1); + signalSpy.clear(); + listView.model = place.contactDetails[data.contactType]; + compare(listView.count, 2); + } + + function test_contactDetails_data() { + return [ + { tag: "phone", contactType: "phone", signalName: "primaryPhoneChanged", primaryValue: "primaryPhone"}, + { tag: "fax", contactType: "fax", signalName: "primaryFaxChanged", primaryValue: "primaryFax"}, + { tag: "email", contactType: "email", signalName: "primaryEmailChanged", primaryValue: "primaryEmail"}, + { tag: "website", contactType: "website", signalName: "primaryWebsiteChanged", primaryValue: "primaryWebsite"} + ]; + } +} diff --git a/tests/auto/declarative_core/tst_placeattribute.qml b/tests/auto/declarative_core/tst_placeattribute.qml new file mode 100644 index 0000000..ae61aed --- /dev/null +++ b/tests/auto/declarative_core/tst_placeattribute.qml @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "PlaceAttribute" + + PlaceAttribute { + id: testAttribute + } + + function test_setAndGet_data() { + return [ + { tag: "label", property: "label", signal: "labelChanged", value: "Test Label", reset: "" }, + { tag: "text", property: "text", signal: "textChanged", value: "Test Text", reset: "" }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testAttribute, data); + } +} diff --git a/tests/auto/declarative_core/tst_placeicon.qml b/tests/auto/declarative_core/tst_placeicon.qml new file mode 100644 index 0000000..c0f099d --- /dev/null +++ b/tests/auto/declarative_core/tst_placeicon.qml @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "Icon" + + Icon { id: emptyIcon } + + function test_empty() { + compare(emptyIcon.plugin, null); + compare(emptyIcon.parameters.keys().length, 0) + } + + + Icon { + id: qmlIconSingleUrl + } + + function test_qmlSingleUrlIcon() { + qmlIconSingleUrl.parameters.singleUrl = "http://example.com/icon.png" + + var u = qmlIconSingleUrl.url(Qt.size(64, 64)); + compare(u, "http://example.com/icon.png"); + + u = qmlIconSingleUrl.url(Qt.size(20, 20)); + compare(u, "http://example.com/icon.png"); + + qmlIconSingleUrl.parameters.singleUrl = "/home/user/icon.png" + u = qmlIconSingleUrl.url(Qt.size(20, 20)); + compare(u, "file:///home/user/icon.png"); + } + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + } + + Icon { + id: qmlIconParams + plugin: testPlugin + } + + function test_qmlIconParams() { + compare(qmlIconParams.plugin, testPlugin); + qmlIconParams.parameters.s = "http://example.com/icon_small.png" + qmlIconParams.parameters.m = "http://example.com/icon_medium.png" + qmlIconParams.parameters.l = "http://example.com/icon_large.png" + + compare(qmlIconParams.url(Qt.size(10, 10)), "http://example.com/icon_small.png"); + compare(qmlIconParams.url(Qt.size(20, 20)), "http://example.com/icon_small.png"); + compare(qmlIconParams.url(Qt.size(24, 24)), "http://example.com/icon_small.png"); + compare(qmlIconParams.url(Qt.size(25, 25)), "http://example.com/icon_medium.png"); + compare(qmlIconParams.url(Qt.size(30, 30)), "http://example.com/icon_medium.png"); + compare(qmlIconParams.url(Qt.size(39, 39)), "http://example.com/icon_medium.png"); + compare(qmlIconParams.url(Qt.size(40, 40)), "http://example.com/icon_large.png"); + compare(qmlIconParams.url(Qt.size(50, 50)), "http://example.com/icon_large.png"); + compare(qmlIconParams.url(Qt.size(60, 60)), "http://example.com/icon_large.png"); + } + + Icon { + id: testIcon + } + + function test_setAndGet_data() { + return [ + { tag: "plugin", property: "plugin", signal: "pluginChanged", value: testPlugin }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testIcon, data); + } +} diff --git a/tests/auto/declarative_core/tst_placesearchmodel.qml b/tests/auto/declarative_core/tst_placesearchmodel.qml new file mode 100644 index 0000000..2d3c599 --- /dev/null +++ b/tests/auto/declarative_core/tst_placesearchmodel.qml @@ -0,0 +1,293 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import QtPositioning 5.2 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "PlaceSearchModel" + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + PluginParameter { + name: "initializePlaceData" + value: true + } + ] + } + + Plugin { + id: favoritePlugin + name: "foo" + } + + Plugin { + id: uninitializedPlugin + } + + Category { + id: testCategory1 + categoryId: "da3606c1-3448-43b3-a4a3-ca24b12dd94a" + name: "Test Category 1" + } + + Category { + id: testCategory2 + categoryId: "bb8ead84-ec2a-48a9-9c8f-d4ffd3134b21" + name: "Test Category 2" + } + + function compareArray(a, b) { + if (a.length !== b.length) + return false; + + for (var i = 0; i < a.length; ++i) { + if (b.indexOf(a[i]) < 0) + return false; + } + + return true; + } + + function test_setAndGet_data() { + var testSearchArea = QtPositioning.circle(QtPositioning.coordinate(10, 20), 5000); + + return [ + { tag: "plugin", property: "plugin", signal: "pluginChanged", value: testPlugin }, + { tag: "searchArea", property: "searchArea", signal: "searchAreaChanged", value: testSearchArea, reset: QtPositioning.shape() }, + { tag: "limit", property: "limit", signal: "limitChanged", value: 10, reset: -1 }, + + { tag: "searchTerm", property: "searchTerm", signal: "searchTermChanged", value: "Test term", reset: "" }, + { tag: "recommendationId", property: "recommendationId", signal: "recommendationIdChanged", value: "Test-place-id", reset: "" }, + { tag: "relevanceHint", property: "relevanceHint", signal: "relevanceHintChanged", value: PlaceSearchModel.DistanceHint, reset: PlaceSearchModel.UnspecifiedHint }, + { tag: "visibilityScope", property: "visibilityScope", signal: "visibilityScopeChanged", value: Place.DeviceVisibility, reset: Place.UnspecifiedVisibility }, + { tag: "favoritesPlugin", property: "favoritesPlugin", signal: "favoritesPluginChanged", value: favoritePlugin }, + { tag: "category", property: "categories", signal: "categoriesChanged", value: testCategory1, expectedValue: [ testCategory1 ], reset: [], array: true }, + { tag: "categories", property: "categories", signal: "categoriesChanged", value: [ testCategory1, testCategory2 ], reset: [], array: true }, + ]; + } + + function test_setAndGet(data) { + var testModel = Qt.createQmlObject('import QtLocation 5.3; PlaceSearchModel {}', testCase, "PlaceSearchModel"); + Utils.testObjectProperties(testCase, testModel, data); + delete testModel; + } + + function test_search_data() { + var park = Qt.createQmlObject('import QtLocation 5.3; Category {name: "Park"; categoryId: "c2e1252c-b997-44fc-8165-e53dd00f66a7"}', testCase, "Category"); + return [ + { + tag: "searchTerm, multiple results", + property: "searchTerm", + value: "view", + reset: "", + places: [ + "4dcc74ce-fdeb-443e-827c-367438017cf1", + "8f72057a-54b2-4e95-a7bb-97b4d2b5721e" + ] + }, + { + tag: "searchTerm, single result", + property: "searchTerm", + value: "park", + reset: "", + places: [ + "4dcc74ce-fdeb-443e-827c-367438017cf1" + ] + }, + { + tag: "categories, single result", + property: "categories", + value: [ park ], + places: [ + "dacb2181-3f67-4e6a-bd4d-635e99ad5b03" + ] + }, + { + tag: "recommendations", + property: "recommendationId", + value: "4dcc74ce-fdeb-443e-827c-367438017cf1", + reset: "", + places: [ + "8f72057a-54b2-4e95-a7bb-97b4d2b5721e", + "dacb2181-3f67-4e6a-bd4d-635e99ad5b03" + ] + }, + { + tag: "no recommendations", + property: "recommendationId", + value: "8f72057a-54b2-4e95-a7bb-97b4d2b5721e", + reset: "", + places: [ ] + } + ]; + } + + function test_search(data) { + var testModel = Qt.createQmlObject('import QtLocation 5.3; PlaceSearchModel {}', testCase, "PlaceSearchModel"); + testModel.plugin = testPlugin; + + var statusChangedSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + statusChangedSpy.target = testModel; + statusChangedSpy.signalName = "statusChanged"; + + var countChangedSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + countChangedSpy.target = testModel; + countChangedSpy.signalName = "rowCountChanged"; + + compare(testModel.status, PlaceSearchModel.Null); + + testModel[data.property] = data.value; + testModel.update(); + + compare(testModel.status, PlaceSearchModel.Loading); + compare(statusChangedSpy.count, 1); + + tryCompare(testModel, "status", PlaceSearchModel.Ready); + compare(statusChangedSpy.count, 2); + + if (data.places.length > 0) + compare(countChangedSpy.count, 1); + else + compare(countChangedSpy.count, 0); + + for (var i = 0; i < testModel.count; ++i) { + compare(testModel.data(i, "type"), PlaceSearchModel.PlaceResult); + + var place = testModel.data(i, "place"); + + verify(data.places.indexOf(place.placeId) >= 0); + } + + testModel.reset(); + + compare(statusChangedSpy.count, 3); + compare(testModel.status, PlaceSearchModel.Null); + if (data.places.length > 0) + compare(countChangedSpy.count, 2); + else + compare(countChangedSpy.count, 0); + compare(testModel.count, 0); + + countChangedSpy.destroy(); + statusChangedSpy.destroy(); + + if (data.reset === undefined) { + testModel[data.property] = null; + } else { + testModel[data.property] = data.reset; + } + + delete testModel; + delete statusChangedSpy; + delete countChangedSpy; + } + + function test_cancel() { + var testModel = Qt.createQmlObject('import QtLocation 5.3; PlaceSearchModel {}', testCase, "PlaceSearchModel"); + testModel.plugin = testPlugin; + + var statusChangedSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + statusChangedSpy.target = testModel; + statusChangedSpy.signalName = "statusChanged"; + + //try cancelling from an initially null state + compare(testModel.status, PlaceSearchModel.Null); + testModel.searchTerm = "view"; + testModel.update(); + tryCompare(testModel, "status", PlaceSearchModel.Loading); + testModel.cancel(); + tryCompare(testModel, "status", PlaceSearchModel.Ready); + compare(statusChangedSpy.count, 2); + + testModel.update(); + tryCompare(testModel, "status", PlaceSearchModel.Loading); + tryCompare(testModel, "status", PlaceSearchModel.Ready); + compare(statusChangedSpy.count, 4); + + var numResults = testModel.count; + verify(numResults > 0); + + //try cancelling from an initially ready state + testModel.update(); + tryCompare(testModel, "status", PlaceSearchModel.Loading); + testModel.cancel(); + tryCompare(testModel, "status", PlaceSearchModel.Ready); + compare(testModel.count, numResults); + compare(statusChangedSpy.count, 6); + + //chack that an encountering an error will cause the model + //to clear its data + testModel.plugin = null; + testModel.update(); + tryCompare(testModel, "count", 0); + compare(testModel.status, PlaceSearchModel.Error); + + delete testModel; + delete statusChangedSpy; + } + + function test_error() { + var testModel = Qt.createQmlObject('import QtLocation 5.3; PlaceSearchModel {}', testCase, "PlaceSearchModel"); + + var statusChangedSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + statusChangedSpy.target = testModel; + statusChangedSpy.signalName = "statusChanged"; + + //try searching without a plugin instance + testModel.update(); + tryCompare(statusChangedSpy, "count", 2); + compare(testModel.status, PlaceSearchModel.Error); + statusChangedSpy.clear(); + //Aside: there is some difficulty in checking the transition to the Loading state + //since the model transitions from Loading to Error before the next event loop + //iteration. + + //try searching with an uninitialized plugin instance. + testModel.plugin = uninitializedPlugin; + testModel.update(); + tryCompare(statusChangedSpy, "count", 2); + compare(testModel.status, PlaceSearchModel.Error); + statusChangedSpy.clear(); + + //try searching with plugin a instance + //that has been provided a non-existent name + testModel.plugin = favoritePlugin; + testModel.update(); + tryCompare(statusChangedSpy, "count", 2); + compare(testModel.status, PlaceSearchModel.Error); + } +} diff --git a/tests/auto/declarative_core/tst_placesearchsuggestionmodel.qml b/tests/auto/declarative_core/tst_placesearchsuggestionmodel.qml new file mode 100644 index 0000000..1334756 --- /dev/null +++ b/tests/auto/declarative_core/tst_placesearchsuggestionmodel.qml @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import QtPositioning 5.2 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "PlaceSearchSuggestionModel" + + PlaceSearchSuggestionModel { + id: testModel + } + + PlaceSearchSuggestionModel { + id: testModelError + } + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + } + + Plugin { + id: nonExistantPlugin + name: "nonExistantName" + } + + Plugin { + id: uninitializedPlugin + } + + function test_setAndGet_data() { + var testSearchArea = QtPositioning.circle(QtPositioning.coordinate(10, 20), 5000); + return [ + { tag: "plugin", property: "plugin", signal: "pluginChanged", value: testPlugin }, + { tag: "searchArea", property: "searchArea", signal: "searchAreaChanged", value: testSearchArea, reset: QtPositioning.shape() }, + { tag: "limit", property: "limit", signal: "limitChanged", value: 10, reset: -1 }, + + { tag: "searchTerm", property: "searchTerm", signal: "searchTermChanged", value: "Test term", reset: "" }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testModel, data); + } + + SignalSpy { id: statusChangedSpy; target: testModel; signalName: "statusChanged" } + SignalSpy { id: suggestionsChangedSpy; target: testModel; signalName: "suggestionsChanged" } + + function test_suggestions() { + compare(statusChangedSpy.count, 0); + testModel.plugin = testPlugin; + + compare(testModel.status, PlaceSearchSuggestionModel.Null); + + testModel.searchTerm = "test"; + testModel.update(); + + compare(testModel.status, PlaceSearchSuggestionModel.Loading); + compare(statusChangedSpy.count, 1); + + tryCompare(testModel, "status", PlaceSearchSuggestionModel.Ready); + compare(statusChangedSpy.count, 2); + + var expectedSuggestions = [ "test1", "test2", "test3" ]; + + compare(suggestionsChangedSpy.count, 1); + compare(testModel.suggestions, expectedSuggestions); + + testModel.reset(); + + compare(statusChangedSpy.count, 3); + compare(testModel.status, PlaceSearchSuggestionModel.Null); + compare(suggestionsChangedSpy.count, 2); + compare(testModel.suggestions, []); + + testModel.update(); + + compare(statusChangedSpy.count, 4); + compare(testModel.status, PlaceSearchSuggestionModel.Loading); + + testModel.cancel(); + + compare(statusChangedSpy.count, 5); + compare(testModel.status, PlaceSearchSuggestionModel.Ready); + + //check that an encountering an error will cause the model + //to clear its data + testModel.plugin = null; + testModel.update(); + tryCompare(testModel.suggestions, "length", 0); + compare(testModel.status, PlaceSearchSuggestionModel.Error); + } + + SignalSpy { id: statusChangedSpyError; target: testModelError; signalName: "statusChanged" } + + function test_error() { + compare(statusChangedSpyError.count, 0); + //try searching without a plugin instance + testModelError.update(); + tryCompare(statusChangedSpyError, "count", 2); + compare(testModelError.status, PlaceSearchSuggestionModel.Error); + statusChangedSpyError.clear(); + //Aside: there is some difficulty in checking the transition to the Loading state + //since the model transitions from Loading to Error before the next event loop + //iteration. + + //try searching with an uninitialized plugin instance. + testModelError.plugin = uninitializedPlugin; + testModelError.update(); + tryCompare(statusChangedSpyError, "count", 2); + compare(testModelError.status, PlaceSearchSuggestionModel.Error); + statusChangedSpyError.clear(); + + //try searching with plugin a instance + //that has been provided a non-existent name + testModelError.plugin = nonExistantPlugin; + testModelError.update(); + tryCompare(statusChangedSpyError, "count", 2); + compare(testModelError.status, PlaceSearchSuggestionModel.Error); + } +} diff --git a/tests/auto/declarative_core/tst_plugin.qml b/tests/auto/declarative_core/tst_plugin.qml new file mode 100644 index 0000000..3dabba0 --- /dev/null +++ b/tests/auto/declarative_core/tst_plugin.qml @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 + +Item { + + Plugin { id: unattachedPlugin } + Plugin { id: herePlugin; name: "here"} + Plugin { id: invalidPlugin; name: "invalid"; allowExperimental: true } + Plugin { id: testPlugin; + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "supported"; value: true}, + PluginParameter { name: "finishRequestImmediately"; value: true}, + PluginParameter { name: "validateWellKnownValues"; value: true} + ] + } + SignalSpy {id: invalidAttachedSpy; target: invalidPlugin; signalName: "attached"} + + Plugin { + id: requiredPlugin + allowExperimental: true + required { + mapping: Plugin.OfflineMappingFeature; + geocoding: Plugin.OfflineGeocodingFeature; + places: Plugin.AnyPlacesFeatures; + } + } + + TestCase { + name: "Plugin properties" + function test_plugin() { + verify (invalidPlugin.availableServiceProviders.length > 0) + verify (invalidPlugin.availableServiceProviders.indexOf('qmlgeo.test.plugin') > -1) // at least test plugin must be present + + // invalid plugins should have no features + verify(invalidPlugin.isAttached) + verify(!(invalidPlugin.supportsMapping())) + verify(!(invalidPlugin.supportsGeocoding())) + verify(!(invalidPlugin.supportsRouting())) + verify(!(invalidPlugin.supportsPlaces())) + + if (invalidPlugin.availableServiceProviders.indexOf('qmlgeo.test.plugin') > -1) { + verify(testPlugin.isAttached) + verify(testPlugin.supportsMapping()) + verify(testPlugin.supportsGeocoding()) + verify(testPlugin.supportsPlaces()) + verify(testPlugin.supportsRouting()) + } + + if (invalidPlugin.availableServiceProviders.indexOf('here')) { + verify(herePlugin.isAttached) + verify(herePlugin.supportsMapping(Plugin.OnlineMappingFeature)) + verify(herePlugin.supportsGeocoding(Plugin.OnlineGeocodingFeature)) + verify(herePlugin.supportsRouting(Plugin.OnlineRoutingFeature)) + } + + verify(!unattachedPlugin.isAttached) + + // test changing name of plugin + invalidAttachedSpy.clear() + compare(invalidAttachedSpy.count, 0) + invalidPlugin.name = 'qmlgeo.test.plugin' + tryCompare(invalidAttachedSpy, 'count', 1) + verify(invalidPlugin.isAttached) + + verify(invalidPlugin.supportsMapping()) + verify(invalidPlugin.supportsGeocoding()) + verify(invalidPlugin.supportsRouting()) + verify(invalidPlugin.supportsPlaces()) + + invalidPlugin.name = '' + compare(invalidAttachedSpy.count, 2) + + verify(!invalidPlugin.supportsMapping()) + verify(!invalidPlugin.supportsGeocoding()) + verify(!invalidPlugin.supportsRouting()) + verify(!invalidPlugin.supportsPlaces()) + } + + function test_required() { + // the required plugin should either get here or qmlgeo.test.plugin + // either way the name will be non-empty and it'll meet the spec + verify(requiredPlugin.name !== "") + verify(requiredPlugin.supportsMapping(requiredPlugin.required.mapping)) + verify(requiredPlugin.supportsGeocoding(requiredPlugin.required.geocoding)) + verify(requiredPlugin.supportsPlaces(requiredPlugin.required.places)) + } + + function test_placesFeatures() { + verify(testPlugin.supportsPlaces(Plugin.SavePlaceFeature)) + verify(testPlugin.supportsPlaces(Plugin.SaveCategoryFeature)) + verify(testPlugin.supportsPlaces(Plugin.SearchSuggestionsFeature)) + verify(!testPlugin.supportsPlaces(Plugin.RemovePlaceFeature)) + } + + function test_locale() { + compare(herePlugin.locales, [Qt.locale().name]); + + //try assignment of a single locale + herePlugin.locales = "fr_FR"; + compare(herePlugin.locales, ["fr_FR"]); + + //try assignment of multiple locales + herePlugin.locales = ["fr_FR","en_US"]; + compare(herePlugin.locales, ["fr_FR","en_US"]); + + //check that assignment of empty locale list defaults to system locale + herePlugin.locales = []; + compare(herePlugin.locales, [Qt.locale().name]); + } + } +} diff --git a/tests/auto/declarative_core/tst_plugin_error.qml b/tests/auto/declarative_core/tst_plugin_error.qml new file mode 100644 index 0000000..50b0359 --- /dev/null +++ b/tests/auto/declarative_core/tst_plugin_error.qml @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 + +Item { + + Plugin { id: testPlugin; + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "error"; value: "1"}, + PluginParameter { name: "errorString"; value: "This error was expected. No worries !"} + ] + } + + Map { + id: map + } + + SignalSpy {id: errorSpy; target: map; signalName: "errorChanged"} + + TestCase { + name: "MappingManagerError" + function test_error() { + verify (map.error === Map.NoError); + map.plugin = testPlugin; + verify (map.error === Map.NotSupportedError); + verify (map.errorString == "This error was expected. No worries !"); + compare(errorSpy.count, 1); + } + } +} diff --git a/tests/auto/declarative_core/tst_position.qml b/tests/auto/declarative_core/tst_position.qml new file mode 100644 index 0000000..1bde087 --- /dev/null +++ b/tests/auto/declarative_core/tst_position.qml @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtPositioning 5.3 + +TestCase { + id: testCase + + name: "Position" + + Position { id: defaultPosition } + + SignalSpy { id: latitudeValidSpy; target: defaultPosition; signalName: "latitudeValidChanged" } + SignalSpy { id: longitudeValidSpy; target: defaultPosition; signalName: "longitudeValidChanged" } + SignalSpy { id: altitudeValidSpy; target: defaultPosition; signalName: "altitudeValidChanged" } + SignalSpy { id: timestampSpy; target: defaultPosition; signalName: "timestampChanged" } + SignalSpy { id: speedSpy; target: defaultPosition; signalName: "speedChanged" } + SignalSpy { id: speedValidSpy; target: defaultPosition; signalName: "speedValidChanged" } + SignalSpy { id: coordinateSpy; target: defaultPosition; signalName: "coordinateChanged" } + SignalSpy { id: horizontalAccuracySpy; target: defaultPosition; signalName: "horizontalAccuracyChanged" } + SignalSpy { id: horizontalAccuracyValidSpy; target: defaultPosition; signalName: "horizontalAccuracyValidChanged" } + SignalSpy { id: verticalAccuracySpy; target: defaultPosition; signalName: "verticalAccuracyChanged" } + SignalSpy { id: verticalAccuracyValidSpy; target: defaultPosition; signalName: "verticalAccuracyValidChanged" } + SignalSpy { id: directionSpy; target: defaultPosition; signalName: "directionChanged" } + SignalSpy { id: verticalSpeedSpy; target: defaultPosition; signalName: "verticalSpeedChanged" } + + function test_defaults() { + compare(defaultPosition.latitudeValid, false); + compare(defaultPosition.longitudeValid, false); + compare(defaultPosition.altitudeValid, false); + compare(defaultPosition.speedValid, false); + compare(defaultPosition.horizontalAccuracyValid, false); + compare(defaultPosition.verticalAccuracyValid, false); + verify(!defaultPosition.directionValid); + verify(isNaN(defaultPosition.direction)); + verify(!defaultPosition.verticalSpeedValid); + verify(isNaN(defaultPosition.verticalSpeed)); + } + + function test_modifiers() { + latitudeValidSpy.clear(); + longitudeValidSpy.clear(); + altitudeValidSpy.clear(); + timestampSpy.clear(); + speedSpy.clear(); + speedValidSpy.clear(); + coordinateSpy.clear(); + horizontalAccuracySpy.clear(); + horizontalAccuracyValidSpy.clear(); + verticalAccuracySpy.clear(); + verticalAccuracyValidSpy.clear(); + directionSpy.clear(); + verticalSpeedSpy.clear(); + + defaultPosition.horizontalAccuracy = 10; + compare(horizontalAccuracySpy.count, 1); + compare(horizontalAccuracyValidSpy.count, 1); + compare(defaultPosition.horizontalAccuracy, 10); + compare(defaultPosition.horizontalAccuracyValid, true); + + defaultPosition.verticalAccuracy = 10; + compare(verticalAccuracySpy.count, 1); + compare(verticalAccuracyValidSpy.count, 1); + compare(defaultPosition.verticalAccuracy, 10); + compare(defaultPosition.verticalAccuracyValid, true); + + // some extra precautions + compare(horizontalAccuracyValidSpy.count, 1); + compare(speedSpy.count, 0); + compare(speedValidSpy.count, 0); + } +} diff --git a/tests/auto/declarative_core/tst_positionsource.qml b/tests/auto/declarative_core/tst_positionsource.qml new file mode 100644 index 0000000..a663f3a --- /dev/null +++ b/tests/auto/declarative_core/tst_positionsource.qml @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtPositioning 5.2 + +TestCase { + id: testCase + + name: "PositionSource" + + PositionSource { id: defaultSource } + PositionSource + { + id: activeDefaultSource + active: true + } + + SignalSpy { id: defaultSourceSpy; target: defaultSource; signalName: "positionChanged" } + + function test_activeDefaultSource() { + wait(0); + verify(activeDefaultSource.name !== ""); + compare(activeDefaultSource.active, true); + } + + function test_invalidSource() { + activeDefaultSource.name = "invalid_positioning_source"; + verify(!activeDefaultSource.active); + verify(!activeDefaultSource.valid); + } + + function test_defaults() { + // at least the test.source plugin should be available + verify(defaultSource.name != ""); + compare(defaultSource.active, false); + } + + function test_inactive() { + defaultSourceSpy.clear(); + compare(defaultSourceSpy.count, 0); + wait(1000); + compare(defaultSourceSpy.count, 0); + } + + PositionSource { id: testSetSource; name: "nonexistent bogus plugin" } + SignalSpy { id: testingSourcePluginSpy; target: testSetSource; signalName: "nameChanged" } + + function test_setplugin() { + testingSourcePluginSpy.clear(); + + // On construction, if the provided source name is invalid, the default source will be + // used. Test that the source is valid as expected. + verify(testSetSource.name !== ""); + //we don't really know what the default source is named. + //It may not be "test.source" + var defaultSourceName = testSetSource.name; + verify(testSetSource.valid); + + // Test that setting name to "" will still use the default. + testSetSource.name = ""; + compare(testingSourcePluginSpy.count, 0); + compare(testSetSource.name, defaultSourceName); + verify(testSetSource.valid); + + testSetSource.name = "test.source"; + if (defaultSourceName === "test.source") + compare(testingSourcePluginSpy.count, 0); + compare(testSetSource.name, "test.source"); + verify(testSetSource.valid); + testingSourcePluginSpy.clear(); + + testSetSource.name = "bogus"; + compare(testingSourcePluginSpy.count, 1); + verify(!testSetSource.valid); + } + + PositionSource { id: testingSource; name: "test.source"; updateInterval: 1000 } + SignalSpy { id: updateSpy; target: testingSource; signalName: "positionChanged" } + SignalSpy { id: directionValidSpy; target: testingSource.position; signalName: "directionValidChanged" } + SignalSpy { id: directionSpy; target: testingSource.position; signalName: "directionChanged" } + + function test_updateInterval() { + testingSource.updateInterval = 1000; + compare(testingSource.updateInterval, 1000); + testingSource.updateInterval = 1200; + compare(testingSource.updateInterval, 1200); + testingSource.updateInterval = 800; + compare(testingSource.updateInterval, 1000); + } + + function test_preferredPositioningMethods() { + testingSource.preferredPositioningMethods = PositionSource.AllPositioningMethods; + compare(testingSource.preferredPositioningMethods, PositionSource.AllPositioningMethods); + testingSource.preferredPositioningMethods = PositionSource.SatellitePositioningMethods; + compare(testingSource.preferredPositioningMethods, PositionSource.SatellitePositioningMethods); + testingSource.preferredPositioningMethods = PositionSource.NonSatellitePositioningMethods; + compare(testingSource.preferredPositioningMethods, PositionSource.NonSatellitePositioningMethods); + } + + function test_updates() { + updateSpy.clear(); + + compare(directionValidSpy.count, 0) + compare(directionSpy.count, 0) + + testingSource.active = true; + + tryCompare(updateSpy, "count", 1, 1500); + compare(testingSource.position.coordinate.longitude, 0.1); + compare(testingSource.position.coordinate.latitude, 0.1); + compare(directionValidSpy.count, 1) + compare(directionSpy.count, 1) + fuzzyCompare(testingSource.position.direction, 45, 0.1) + verify(!testingSource.position.speedValid) + verify(isNaN(testingSource.position.speed)) + + tryCompare(updateSpy, "count", 2, 1500); + compare(testingSource.position.coordinate.longitude, 0.2); + compare(testingSource.position.coordinate.latitude, 0.2); + compare(directionValidSpy.count, 1) + compare(directionSpy.count, 2) + fuzzyCompare(testingSource.position.direction, 45, 0.1) + verify(testingSource.position.speedValid) + verify(testingSource.position.speed > 10000) + + testingSource.active = false; + wait(2500); + compare(updateSpy.count, 2); + compare(testingSource.position.coordinate.longitude, 0.2); + compare(testingSource.position.coordinate.latitude, 0.2); + compare(directionValidSpy.count, 1) + compare(directionSpy.count, 2) + fuzzyCompare(testingSource.position.direction, 45, 0.1) + verify(testingSource.position.speedValid) + verify(testingSource.position.speed > 10000) + } +} diff --git a/tests/auto/declarative_core/tst_ratings.qml b/tests/auto/declarative_core/tst_ratings.qml new file mode 100644 index 0000000..5628432 --- /dev/null +++ b/tests/auto/declarative_core/tst_ratings.qml @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "Ratings" + + Ratings { id: emptyRatings } + + function test_empty() { + compare(emptyRatings.average, 0.0); + compare(emptyRatings.maximum, 0.0); + compare(emptyRatings.count, 0); + } + + Ratings { + id: qmlRatings + + average: 3.5 + maximum: 5.0 + count: 7 + } + + function test_qmlConstructedRatings() { + compare(qmlRatings.average, 3.5); + compare(qmlRatings.maximum, 5.0); + compare(qmlRatings.count, 7); + } + + Ratings { + id: testRatings + } + + function test_setAndGet_data() { + return [ + { tag: "average", property: "average", signal: "averageChanged", value: 4.5, reset: 0.0 }, + { tag: "maximum", property: "maximum", signal: "maximumChanged", value: 5.0, reset: 0.0 }, + { tag: "count", property: "count", signal: "countChanged", value: 10, reset: 0 }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testRatings, data); + } +} diff --git a/tests/auto/declarative_core/tst_reviewmodel.qml b/tests/auto/declarative_core/tst_reviewmodel.qml new file mode 100644 index 0000000..192026f --- /dev/null +++ b/tests/auto/declarative_core/tst_reviewmodel.qml @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + name: "ReviewModel" + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + PluginParameter { + name: "initializePlaceData" + value: true + } + ] + } + + ReviewModel { + id: testModel + } + + Place { + id: testPlace + name: "Test Place" + } + + Place { + id: parkViewHotel + placeId: "4dcc74ce-fdeb-443e-827c-367438017cf1" + plugin: testPlugin + } + + Place { + id: seaViewHotel + placeId: "8f72057a-54b2-4e95-a7bb-97b4d2b5721e" + plugin: testPlugin + } + + function test_setAndGet_data() { + return [ + { tag: "place", property: "place", signal: "placeChanged", value: testPlace }, + { tag: "batchSize", property: "batchSize", signal: "batchSizeChanged", value: 10, reset: 1 }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testModel, data); + } + + function test_consecutive_fetch_data() { + return [ + { tag: "batchSize 1", batchSize: 1 }, + { tag: "batchSize 2", batchSize: 2 }, + { tag: "batchSize 5", batchSize: 5 }, + { tag: "batchSize 10", batchSize: 10 }, + ]; + } + + function test_consecutive_fetch(data) { + //Note: in javascript the months go from 0(Jan) to 11(Dec) + var expectedReviews = [ + { + "title": "Park View Review 1", + "text": "Park View Review 1 Text", + "dateTime": new Date(2004, 8, 22, 13, 1), + "language": "en", + "rating": 3.5, + "reviewId": "0001" + }, + { + "title": "Park View Review 2", + "text": "Park View Review 2 Text", + "dateTime": new Date(2005, 8, 14, 4, 17), + "language": "en", + "rating": 1, + "reviewId": "0002" + }, + { + "title": "Park View Review 3", + "text": "Park View Review 3 Text", + "dateTime": new Date(2005, 9, 14, 4, 12), + "language": "en", + "rating": 5, + "reviewId": "0003" + }, + { + "title": "", + "text": "", + "dateTime": new Date(""), + "language": "", + "rating": 0, + "reviewId": "" + }, + { + "title": "Park View Review 5", + "text": "Park View Review 5 Text", + "dateTime": new Date(2005, 10, 20, 14, 53), + "language": "en", + "rating": 2.3, + "reviewId": "0005" + } + ] + + var model = createModel(); + Utils.testConsecutiveFetch(testCase, model, parkViewHotel, expectedReviews, data); + model.destroy(); + } + + function test_reset() { + var model = createModel(); + Utils.testReset(testCase, model, parkViewHotel); + model.destroy(); + } + + function test_fetch_data() { + return [ + { + tag: "fetch all reviews in a single batch", + model: createModel(), + batchSize: 10, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 5 + }, + { + tag: "fetch from a place with no reviews", + model: createModel(), + batchSize: 1, + place: seaViewHotel, + expectedTotalCount: 0, + expectedCount: 0 + }, + { + tag: "fetch with batch size one less than the total", + model: createModel(), + batchSize: 4, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 4 + }, + { + tag: "fetch with batch size equal to the total", + model: createModel(), + batchSize: 5, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 5 + }, + { + tag: "fetch with batch size larger than the total", + model: createModel(), + batchSize: 6, + place: parkViewHotel, + expectedTotalCount: 5, + expectedCount: 5 + } + ] + } + + function test_fetch(data) { + Utils.testFetch(testCase, data); + data.model.destroy(); + } + + function createModel() { + return Qt.createQmlObject('import QtLocation 5.3; ReviewModel {}', + testCase, "reviewModel"); + } +} diff --git a/tests/auto/declarative_core/tst_routing.qml b/tests/auto/declarative_core/tst_routing.qml new file mode 100644 index 0000000..fdbfe7b --- /dev/null +++ b/tests/auto/declarative_core/tst_routing.qml @@ -0,0 +1,805 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import QtPositioning 5.2 + +Item { + Plugin { id: testPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true } + Plugin { id: errorPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true + parameters: [ + PluginParameter { name: "error"; value: "1"}, + PluginParameter { name: "errorString"; value: "This error was expected. No worries !"} + ] + } + + property variant coordinate1: QtPositioning.coordinate(51, 0) + property variant coordinate2: QtPositioning.coordinate(52, 0) + + property variant boundingBox1: QtPositioning.rectangle() + property variant boundingBox2: QtPositioning.rectangle() + + property variant circle1: QtPositioning.circle() + property variant circle2: QtPositioning.circle(tr, 4000) + + Component.onCompleted: { + boundingBox1.topLeft = bl + boundingBox1.bottomRight = bl + boundingBox1.width = 10 + + boundingBox2.topLeft = bl + boundingBox2.bottomRight = bl + boundingBox2.width = 20 + } + + property variant bl: QtPositioning.coordinate(0, 0) + property variant tl: QtPositioning.coordinate(1, 0) + property variant tr: QtPositioning.coordinate(1, 1) + property variant br: QtPositioning.coordinate(0, 1) + property variant ntr: QtPositioning.coordinate(3, 3) + + property variant unitBox: QtPositioning.rectangle(tl, br) + + Route {id: emptyRoute} + TestCase { + name: "RouteManeuver RouteSegment and MapRoute" + RouteSegment {id: emptySegment} + RouteManeuver {id: emptyManeuver} + + // TODO enable when we have map route + //MapRoute {id: emptyMapRoute} + + property variant emptyBox: QtPositioning.rectangle() + + property variant emptyCoordinate: QtPositioning.coordinate() + + // TODO enable when we have map route + /* + SignalSpy {id: mapRouteDetailLevelSpy; target: emptyMapRoute; signalName: "detailLevelChanged"} + SignalSpy {id: mapRouteColorSpy; target: emptyMapRoute.border; signalName: "colorChanged"} + SignalSpy {id: mapRouteWidthSpy; target: emptyMapRoute.border; signalName: "widthChanged"} + SignalSpy {id: mapRouteRouteSpy; target: emptyMapRoute; signalName: "routeChanged"} + function test_maproute_defaults() { + compare(mapRouteRouteSpy.count, 0) + compare(mapRouteColorSpy.count, 0) + compare(mapRouteDetailLevelSpy.count, 0) + compare (emptyMapRoute.detailLevel, 6) + emptyMapRoute.border.color = 'green' + emptyMapRoute.detailLevel = 3 + compare(mapRouteRouteSpy.count, 0) + compare(mapRouteColorSpy.count, 1) + compare(mapRouteDetailLevelSpy.count, 1) + emptyMapRoute.border.color = 'green' + emptyMapRoute.detailLevel = 3 + compare(mapRouteColorSpy.count, 1) + compare(mapRouteDetailLevelSpy.count, 1) + emptyMapRoute.route = emptyRoute + compare(mapRouteRouteSpy.count, 1) + compare(emptyMapRoute.route, emptyRoute) + // width + compare(mapRouteWidthSpy.count, 0) + emptyMapRoute.border.width = 123 + compare(mapRouteWidthSpy.count, 1) + compare(emptyMapRoute.border.width, 123) + emptyMapRoute.border.width = 123 + compare(mapRouteWidthSpy.count, 1) + emptyMapRoute.border.width = -1 + compare(mapRouteWidthSpy.count, 1) + compare(emptyMapRoute.border.width, 123) + emptyMapRoute.border.width = 0 + compare(mapRouteWidthSpy.count, 1) + compare(emptyMapRoute.border.width, 123) + } + */ + + function test_route_defaults() { + compare(emptyRoute.travelTime, 0) + compare(emptyRoute.distance,0) + compare(emptyRoute.path.length,0) + compare(emptyRoute.segments.length,0) + compare(emptyRoute.bounds.topLeft.latitude, emptyBox.topLeft.latitude) + compare(emptyRoute.bounds.bottomRight.longitude, emptyBox.bottomRight.longitude) + } + + function test_routesegment_defaults() { + compare(emptySegment.travelTime, 0) + compare(emptySegment.distance, 0) + compare(emptySegment.path.length, 0) + compare(emptySegment.maneuver.valid, emptyManeuver.valid) + compare(emptySegment.maneuver.instructionText, emptyManeuver.instructionText) + compare(emptySegment.maneuver.waypointValid, emptyManeuver.waypointValid) + } + function test_maneuver_defaults() { + compare(emptyManeuver.valid, false) + compare(emptyManeuver.instructionText, "") + compare(emptyManeuver.direction, RouteManeuver.NoDirection) + compare(emptyManeuver.timeToNextInstruction,0) + compare(emptyManeuver.distanceToNextInstruction,0) + compare(emptyManeuver.waypoint.latitude, emptyCoordinate.latitude) + compare(emptyManeuver.waypoint.longitude, emptyCoordinate.longitude) + compare(emptyManeuver.position.latitude, emptyCoordinate.latitude) + compare(emptyManeuver.position.longitude, emptyCoordinate.longitude) + } + } + + TestCase { + name: "MapRouteModel and MapRouteQuery" + RouteModel {id: emptyModel} + RouteQuery {id: emptyQuery} + + function test_model_default_properties() { + compare (emptyModel.autoUpdate, false, "Automatic update") + compare (emptyModel.status, RouteModel.Null, "Model status") + compare (emptyModel.errorString, "", "Model error") + compare (emptyModel.error, RouteModel.NoError) + compare (emptyModel.count, 0, "Model count") + emptyModel.get(192) // don't do stupid + + compare (emptyQuery.numberAlternativeRoutes, 0, "Number of alternative routes") + compare (emptyQuery.travelModes, RouteQuery.CarTravel, "Travel mode") + compare (emptyQuery.routeOptimizations, RouteQuery.FastestRoute, "Route optimization") + compare (emptyQuery.segmentDetail, RouteQuery.BasicSegmentData) + compare (emptyQuery.maneuverDetail, RouteQuery.BasicManeuvers) + compare (emptyQuery.waypoints.length, 0, "Waypoints") + compare (emptyQuery.excludedAreas.length, 0, "excluded areas") + // Bug in QtQml. Todo, enable when QList support is done + //compare (emptyQuery.featureTypes.length, 0, "Feature types") + } + + SignalSpy {id: autoUpdateSpy; target: emptyModel; signalName: "autoUpdateChanged"} + SignalSpy {id: pluginSpy; target: emptyModel ; signalName: "pluginChanged"} + + SignalSpy {id: travelModesSpy; target: emptyQuery; signalName: "travelModesChanged"} + SignalSpy {id: waypointsSpy; target: emptyQuery; signalName: "waypointsChanged"} + SignalSpy {id: exclusionSpy; target: emptyQuery; signalName: "excludedAreasChanged"} + SignalSpy {id: featureTypesSpy; target: emptyQuery; signalName: "featureTypesChanged"} + SignalSpy {id: segmentDetailSpy; target: emptyQuery; signalName: "segmentDetailChanged"} + SignalSpy {id: maneuverDetailSpy; target: emptyQuery; signalName: "maneuverDetailChanged"} + SignalSpy {id: numberAlterNativeRoutesSpy; target: emptyQuery; signalName: "numberAlternativeRoutesChanged"} + SignalSpy {id: routeOptimizationsSpy; target: emptyQuery; signalName: "routeOptimizationsChanged"} + SignalSpy {id: queryDetailsChangedSpy; target: emptyQuery; signalName: "queryDetailsChanged"} + function test_model_setters() { + // Autoupdate + compare(autoUpdateSpy.count, 0) + emptyModel.autoUpdate = true + compare(autoUpdateSpy.count, 1) + compare(emptyModel.autoUpdate, true) + emptyModel.autoUpdate = true // mustn't retrigger 'changed' -signal + compare(autoUpdateSpy.count, 1) + emptyModel.autoUpdate = false + compare(autoUpdateSpy.count, 2) + + // Travelmodes + compare(travelModesSpy.count, 0) + emptyQuery.travelModes = RouteQuery.BicycleTravel + compare(travelModesSpy.count, 1) + compare(emptyQuery.travelModes, RouteQuery.BicycleTravel) + emptyQuery.travelModes = RouteQuery.BicycleTravel | RouteQuery.PedestrianTravel + compare(emptyQuery.travelModes, RouteQuery.BicycleTravel | RouteQuery.PedestrianTravel) + compare(travelModesSpy.count, 2) + compare(queryDetailsChangedSpy.count, 2) + + // Basic adding and removing of waypoint + queryDetailsChangedSpy.clear() + compare(waypointsSpy.count, 0) + emptyQuery.addWaypoint(coordinate1) + compare(waypointsSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + emptyQuery.addWaypoint(coordinate1) + compare(waypointsSpy.count, 2) + compare(queryDetailsChangedSpy.count, 2) + compare(emptyQuery.waypoints.length, 2) + emptyQuery.removeWaypoint(coordinate1) + compare(waypointsSpy.count, 3) + compare(queryDetailsChangedSpy.count, 3) + compare(emptyQuery.waypoints.length, 1) + emptyQuery.removeWaypoint(coordinate2) // coordinate2 isn't in the list, must not impact + compare(waypointsSpy.count, 3) + compare(queryDetailsChangedSpy.count, 3) + emptyQuery.removeWaypoint(coordinate1) + compare(waypointsSpy.count, 4) + emptyQuery.removeWaypoint(coordinate1) // doesn't exist anymore, must not impact + compare(waypointsSpy.count, 4) + compare(emptyQuery.waypoints.length, 0) + // Check correct ordering of waypoints + waypointsSpy.clear() + emptyQuery.addWaypoint(coordinate1) + emptyQuery.addWaypoint(coordinate2) + emptyQuery.addWaypoint(coordinate1) + emptyQuery.addWaypoint(coordinate2) + compare(waypointsSpy.count, 4) + compare(emptyQuery.waypoints[0], coordinate1) + compare(emptyQuery.waypoints[1], coordinate2) + compare(emptyQuery.waypoints[2], coordinate1) + compare(emptyQuery.waypoints[3], coordinate2) + emptyQuery.removeWaypoint(coordinate1) // remove one from the middle, check that one added last is removed + compare(emptyQuery.waypoints[0], coordinate1) + compare(emptyQuery.waypoints[1], coordinate2) + compare(emptyQuery.waypoints[2], coordinate2) + waypointsSpy.clear() + emptyQuery.clearWaypoints() + compare(emptyQuery.waypoints.length, 0) + compare(waypointsSpy.count, 1) + + // Altering the waypoint contents should trigger signal + emptyQuery.clearWaypoints() + queryDetailsChangedSpy.clear(); + emptyQuery.addWaypoint(coordinate1) + compare(queryDetailsChangedSpy.count, 1); + + // verify coordinate is disconnected + emptyQuery.removeWaypoint(coordinate1) + compare (queryDetailsChangedSpy.count, 2) + + // verify that the same coordinate can be added to the waypoints + emptyQuery.addWaypoint(coordinate1) + compare(queryDetailsChangedSpy.count, 3); + emptyQuery.addWaypoint(coordinate1) + compare(queryDetailsChangedSpy.count, 4); + compare (emptyQuery.waypoints.length, 2) + queryDetailsChangedSpy.clear() + + // verify that removing duplicate coordinate leaves remaining ones + emptyQuery.removeWaypoint(coordinate1) + compare (queryDetailsChangedSpy.count, 1) + compare (emptyQuery.waypoints.length, 1) + + // verify that clearing works + emptyQuery.clearWaypoints() + compare(queryDetailsChangedSpy.count, 2); + compare (emptyQuery.waypoints.length, 0) + + // Excluded areas + queryDetailsChangedSpy.clear() + compare(exclusionSpy.count, 0) + emptyQuery.addExcludedArea(boundingBox1) + compare(exclusionSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + emptyQuery.addExcludedArea(boundingBox1) + // doesn't make sense to put same area twice + compare(exclusionSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + compare(emptyQuery.excludedAreas.length, 1) + emptyQuery.removeExcludedArea(boundingBox1) + compare(exclusionSpy.count, 2) + compare(queryDetailsChangedSpy.count, 2) + compare(emptyQuery.excludedAreas.length, 0) + emptyQuery.removeExcludedArea(boundingBox2) // boundingBox2 isn't in the list, must not impact + compare(exclusionSpy.count, 2) + compare(queryDetailsChangedSpy.count, 2) + emptyQuery.removeExcludedArea(boundingBox1) // doesn't exist anymore, must not impact + compare(exclusionSpy.count, 2) + compare(queryDetailsChangedSpy.count, 2) + // Check correct ordering of exclusion + exclusionSpy.clear() + emptyQuery.addExcludedArea(boundingBox1) + emptyQuery.addExcludedArea(boundingBox2) + emptyQuery.addExcludedArea(boundingBox1) + emptyQuery.addExcludedArea(boundingBox2) + compare(exclusionSpy.count, 2) + compare(emptyQuery.excludedAreas[0], boundingBox1) + compare(emptyQuery.excludedAreas[1], boundingBox2) + emptyQuery.removeExcludedArea(boundingBox1) // remove first and check all geos ok + compare(emptyQuery.excludedAreas[0], boundingBox2) + exclusionSpy.clear() + emptyQuery.clearExcludedAreas() + compare(emptyQuery.excludedAreas.length, 0) + compare(exclusionSpy.count, 1) + + // verify that clearing works + emptyQuery.addExcludedArea(unitBox); + compare(emptyQuery.excludedAreas.length, 1); + queryDetailsChangedSpy.clear(); + emptyQuery.clearExcludedAreas(); + compare(queryDetailsChangedSpy.count, 1); + compare(emptyQuery.excludedAreas.length, 0) + + // Feature types and weights + queryDetailsChangedSpy.clear() + // Bug in QtQml. Todo, enable when QList support is done + //compare(emptyQuery.featureTypes.length, 0) + compare(featureTypesSpy.count, 0) + emptyQuery.setFeatureWeight(RouteQuery.TollFeature, RouteQuery.AvoidFeatureWeight); + compare(featureTypesSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + emptyQuery.setFeatureWeight(RouteQuery.HighwayFeature, RouteQuery.PreferFeatureWeight); + compare(featureTypesSpy.count, 2) + compare(queryDetailsChangedSpy.count, 2) + // Bug in QtQml. Todo, enable when QList support is done + //compare(emptyQuery.featureTypes.length, 2) + //compare(emptyQuery.featureTypes[0], RouteQuery.TollFeature) + //compare(emptyQuery.featureTypes[1], RouteQuery.HighwayFeature) + // Verify feature weights are as set + compare(emptyQuery.featureWeight(RouteQuery.TollFeature), RouteQuery.AvoidFeatureWeight); + compare(emptyQuery.featureWeight(RouteQuery.HighwayFeature), RouteQuery.PreferFeatureWeight); + // Neutralize a weight, feature should disappear + emptyQuery.setFeatureWeight(RouteQuery.TollFeature, RouteQuery.NeutralFeatureWeight); + compare(featureTypesSpy.count, 3) + compare(queryDetailsChangedSpy.count, 3) + // Bug in QtQml. Todo, enable when QList support is done + //compare(emptyQuery.featureTypes.length, 1) + compare(emptyQuery.featureWeight(RouteQuery.TollFeature), RouteQuery.NeutralFeatureWeight); + compare(emptyQuery.featureWeight(RouteQuery.HighwayFeature), RouteQuery.PreferFeatureWeight); + // Bug in QtQml. Todo, enable when QList support is done + //compare(emptyQuery.featureTypes[0], RouteQuery.HighwayFeature) + //compare(emptyQuery.featureWeight(emptyQuery.featureTypes[0]), RouteQuery.PreferFeatureWeight) + compare(featureTypesSpy.count, 3) + compare(queryDetailsChangedSpy.count, 3) + // Bug in QtQml. Todo, enable when QList support is done + //compare(emptyQuery.featureTypes.length, 1) + + // Put some feature weights and then reset them with NoFeature + emptyQuery.setFeatureWeight(RouteQuery.FerryFeature, RouteQuery.RequireFeatureWeight); + emptyQuery.setFeatureWeight(RouteQuery.MotorPoolLaneFeature, RouteQuery.DisallowFeatureWeight); + compare(featureTypesSpy.count, 5) + compare(queryDetailsChangedSpy.count, 5) + // Bug in QtQml. Todo, enable when QList support is done + //compare(emptyQuery.featureTypes.length, 3) + emptyQuery.setFeatureWeight(RouteQuery.NoFeature, RouteQuery.NeutralFeatureWeight) + compare(featureTypesSpy.count, 6) + compare(queryDetailsChangedSpy.count, 6) + // Bug in QtQml. Todo, enable when QList support is done + //compare(emptyQuery.featureTypes.length, 0) + + // Segment details + queryDetailsChangedSpy.clear() + compare(segmentDetailSpy.count, 0) + compare(emptyQuery.segmentDetail, RouteQuery.BasicSegmentData) + emptyQuery.segmentDetail = RouteQuery.NoSegmentData + compare(segmentDetailSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + compare(emptyQuery.segmentDetail, RouteQuery.NoSegmentData) + emptyQuery.segmentDetail = RouteQuery.NoSegmentData + compare(segmentDetailSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + compare(emptyQuery.segmentDetail, RouteQuery.NoSegmentData) + + // Maneuver details + queryDetailsChangedSpy.clear() + compare(maneuverDetailSpy.count, 0) + compare(emptyQuery.maneuverDetail, RouteQuery.BasicManeuvers) + emptyQuery.maneuverDetail = RouteQuery.NoManeuvers + compare(maneuverDetailSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + compare(emptyQuery.maneuverDetail, RouteQuery.NoManeuvers) + emptyQuery.maneuverDetail = RouteQuery.NoManeuvers + compare(maneuverDetailSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + compare(emptyQuery.maneuverDetail, RouteQuery.NoManeuvers) + + // NumberAlternativeRoutes + queryDetailsChangedSpy.clear() + compare(numberAlterNativeRoutesSpy.count, 0) + compare(emptyQuery.numberAlternativeRoutes, 0) + emptyQuery.numberAlternativeRoutes = 2 + compare(numberAlterNativeRoutesSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + compare(emptyQuery.numberAlternativeRoutes, 2) + emptyQuery.numberAlternativeRoutes = 2 + compare(numberAlterNativeRoutesSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + compare(emptyQuery.numberAlternativeRoutes, 2) + + // Route optimization + queryDetailsChangedSpy.clear() + compare(routeOptimizationsSpy.count, 0) + compare(emptyQuery.routeOptimizations, RouteQuery.FastestRoute) + emptyQuery.routeOptimizations = RouteQuery.ShortestRoute + compare(routeOptimizationsSpy.count, 1) + compare(queryDetailsChangedSpy.count, 1) + compare(emptyQuery.routeOptimizations, RouteQuery.ShortestRoute) + emptyQuery.routeOptimizations = RouteQuery.ShortestRoute | RouteQuery.MostScenicRoute + compare(routeOptimizationsSpy.count, 2) + compare(queryDetailsChangedSpy.count, 2) + compare(emptyQuery.routeOptimizations, RouteQuery.ShortestRoute | RouteQuery.MostScenicRoute) + + // Must act gracefully + emptyModel.reset() + emptyModel.update() + + // Plugin + compare(pluginSpy.count, 0) + emptyModel.plugin = testPlugin + compare(pluginSpy.count, 1) + compare(emptyModel.plugin, testPlugin) + emptyModel.plugin = testPlugin + compare(pluginSpy.count, 1) + emptyModel.plugin = errorPlugin + compare(pluginSpy.count, 2) + + // Must act gracefully + emptyModel.reset() + emptyModel.update() + } + // Test that model acts gracefully when plugin is not set or is invalid + // (does not support routing) + RouteModel {id: errorModel; plugin: errorPlugin} + RouteModel {id: errorModelNoPlugin} + SignalSpy {id: countInvalidSpy; target: errorModel; signalName: "countChanged"} + SignalSpy {id: errorSpy; target: errorModel; signalName: "errorChanged"} + function test_error_plugin() { + // test plugin not set + compare(errorModelNoPlugin.error,RouteModel.NoError) + errorModelNoPlugin.update() + compare(errorModelNoPlugin.error,RouteModel.EngineNotSetError) + console.log(errorModelNoPlugin.errorString) + + //plugin set but otherwise not offering anything + compare(errorModel.error,RouteModel.EngineNotSetError) + compare(errorModel.errorString,"This error was expected. No worries !") + errorSpy.clear() + errorModel.update() + compare(errorModel.error,RouteModel.EngineNotSetError) + compare(errorModel.errorString,qsTr("Cannot route, route manager not set.")) + compare(errorSpy.count, 1) + errorSpy.clear() + errorModel.cancel() + compare(errorModel.error,RouteModel.NoError) + compare(errorModel.errorString,"") + compare(errorSpy.count, 1) + errorSpy.clear() + errorModel.reset() + compare(errorModel.error,RouteModel.NoError) + compare(errorModel.errorString,"") + compare(errorSpy.count, 0) + errorSpy.clear() + errorModel.update() + compare(errorModel.error,RouteModel.EngineNotSetError) + compare(errorModel.errorString,qsTr("Cannot route, route manager not set.")) + compare(errorSpy.count, 1) + errorSpy.clear() + var data = errorModel.get(-1) + compare(data, null) + } + } + + Plugin { + id: testPlugin_immediate; + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "gc_supported"; value: true}, + PluginParameter { name: "gc_finishRequestImmediately"; value: true}, + PluginParameter { name: "gc_validateWellKnownValues"; value: true} + ] + } + + Plugin { + id: testPlugin_slacker; + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "gc_finishRequestImmediately"; value: false} + ] + } + + Plugin { + id: bacicRoutingPlugin_slacker; + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "gc_finishRequestImmediately"; value: false} + ] + } + + property variant rcoordinate1: QtPositioning.coordinate(50, 50) + property variant rcoordinate2: QtPositioning.coordinate(51, 52) + property variant rcoordinate3: QtPositioning.coordinate(53, 54) + property variant rcoordinate4: QtPositioning.coordinate(55, 56) + property variant rcoordinate5: QtPositioning.coordinate(57, 58) + + property variant fcoordinate1: QtPositioning.coordinate(60, 60) + property variant fcoordinate2: QtPositioning.coordinate(61, 62) + property variant fcoordinate3: QtPositioning.coordinate(63, 64) + property variant fcoordinate4: QtPositioning.coordinate(65, 66) + property variant fcoordinate5: QtPositioning.coordinate(67, 68) + + property variant f2coordinate1: QtPositioning.coordinate(60, 60) + property variant f2coordinate2: QtPositioning.coordinate(61, 62) + property variant f2coordinate3: QtPositioning.coordinate(63, 64) + + RouteQuery {id: routeQuery} + RouteQuery { + id: filledRouteQuery + numberAlternativeRoutes: 0 + waypoints: [ + { latitude: 60, longitude: 60 }, + { latitude: 61, longitude: 62 }, + { latitude: 63, longitude: 64 }, + { latitude: 65, longitude: 66 }, + { latitude: 67, longitude: 68 } + ] + } + RouteQuery { + id: filledRouteQuery2 + waypoints: [ + f2coordinate1, + f2coordinate2, + f2coordinate3 + ] + } + RouteModel { + id: routeModelAutomatic; + plugin: testPlugin_slacker; + query: filledRouteQuery; + autoUpdate: true + } + + SignalSpy {id: automaticRoutesSpy; target: routeModelAutomatic; signalName: "routesChanged" } + + RouteModel {id: routeModel; plugin: testPlugin_immediate; query: routeQuery } + SignalSpy {id: testRoutesSpy; target: routeModel; signalName: "routesChanged"} + SignalSpy {id: testCountSpy; target: routeModel; signalName: "countChanged" } + SignalSpy {id: testStatusSpy; target: routeModel; signalName: "statusChanged"} + SignalSpy {id: testErrorStringSpy; target: routeModel; signalName: "errorChanged"} + SignalSpy {id: testErrorSpy; target: routeModel; signalName: "errorChanged"} + SignalSpy {id: testWaypointsSpy; target: routeQuery; signalName: "waypointsChanged"} + + RouteModel {id: routeModelSlack; plugin: bacicRoutingPlugin_slacker; query: routeQuery } + SignalSpy {id: testRoutesSlackSpy; target: routeModelSlack; signalName: "routesChanged"} + SignalSpy {id: testCountSlackSpy; target: routeModelSlack; signalName: "countChanged" } + SignalSpy {id: testStatusSlackSpy; target: routeModelSlack; signalName: "statusChanged"} + SignalSpy {id: testErrorStringSlackSpy; target: routeModelSlack; signalName: "errorChanged"} + SignalSpy {id: testErrorSlackSpy; target: routeModelSlack; signalName: "errorChanged"} + SignalSpy {id: testPluginSlackSpy; target: routeModelSlack; signalName: "pluginChanged"} + + TestCase { + name: "Routing" + function clear_immediate_model() { + routeModel.reset() + testRoutesSpy.clear() + testCountSpy.clear() + testStatusSpy.clear() + testErrorStringSpy.clear() + testErrorSpy.clear() + } + function clear_slacker_model() { + routeModelSlack.reset() + testRoutesSlackSpy.clear() + testCountSlackSpy.clear() + testStatusSlackSpy.clear() + testErrorStringSlackSpy.clear() + testErrorSlackSpy.clear() + } + + function test_reset() { + clear_immediate_model(); + routeQuery.numberAlternativeRoutes = 72 // 'altroutes - 70' is the echoed errorcode + routeModel.update() + verify (testErrorStringSpy.count > 0) + verify (testErrorSpy.count > 0) + compare (routeModel.errorString, "error") + compare (routeModel.error, RouteModel.CommunicationError) + compare (routeModel.count, 0) + compare (testStatusSpy.count, 2) + compare (routeModel.status, RouteModel.Error) + routeModel.reset() + compare (routeModel.status, RouteModel.Null) + compare (routeModel.errorString, "") + compare (routeModel.error, RouteModel.NoError) + // Check that ongoing req is aborted + clear_slacker_model() + routeQuery.numberAlternativeRoutes = 3 + routeModelSlack.update() + wait (100) + routeModelSlack.reset() + wait (200) + compare (routeModelSlack.count, 0) + // Check that results are cleared + routeModelSlack.update() + tryCompare(routeModelSlack, "count", 3) // numberALternativeRoutes + routeModelSlack.reset() + compare (routeModelSlack.count, 0) + // Check that changing plugin resets any ongoing requests + clear_slacker_model() + routeQuery.numberAlternativeRoutes = 3 + compare (testPluginSlackSpy.count, 0) + routeModelSlack.update() + wait (100) + routeModelSlack.plugin = testPlugin_immediate + wait (200) + compare (routeModelSlack.count, 0) // should be no updates + compare (testPluginSlackSpy.count, 1) + // test that works + routeModelSlack.update() + compare (routeModelSlack.count, 3) + // return back + routeModelSlack.plugin = testPlugin_slacker + } + + function test_error_routing() { + // Basic immediate error + clear_immediate_model(); + routeQuery.numberAlternativeRoutes = 72 // 'altroutes - 70' is the echoed errorcode + routeModel.update() + compare (testErrorStringSpy.count, 1) + compare (testErrorSpy.count, 1) + compare (routeModel.errorString, "error") + compare (routeModel.error, RouteModel.CommunicationError) + compare (routeModel.count, 0) + compare (testStatusSpy.count, 2) + compare (routeModel.status, RouteModel.Error) + // Basic delayed error + clear_slacker_model() + routeQuery.numberAlternativeRoutes = 72 + routeModelSlack.update() + compare (testErrorStringSlackSpy.count, 0) + compare (testErrorSlackSpy.count, 0) + if (routeModelSlack.errorString == "") + tryCompare(testErrorStringSlackSpy, "count", 1) + else + tryCompare(testErrorStringSlackSpy, "count", 2) + compare (routeModelSlack.errorString, "error") + compare (routeModelSlack.error, RouteModel.CommunicationError) + compare (routeModelSlack.count, 0) + // check that we recover + routeQuery.numberAlternativeRoutes = 1 + routeModelSlack.update() + tryCompare(routeModelSlack, "count", 1) + compare (testCountSlackSpy.count, 1) + compare (routeModelSlack.errorString, "") + compare (routeModelSlack.error, RouteModel.NoError) + } + function test_basic_routing() { + compare (testRoutesSpy.count, 0) + compare (routeModel.errorString, "") + compare (routeModel.error, RouteModel.NoError) + compare (testCountSpy.count, 0) + compare (routeModel.count, 0) + compare (routeQuery.waypoints.length, 0) + compare (testWaypointsSpy.count, 0) + routeQuery.addWaypoint(rcoordinate1) + routeQuery.addWaypoint(rcoordinate2) + routeQuery.addWaypoint(rcoordinate3) + routeQuery.addWaypoint(rcoordinate4) + routeQuery.addWaypoint(rcoordinate5) + compare (testWaypointsSpy.count, 5) + compare (routeQuery.waypoints.length, 5) + routeQuery.numberAlternativeRoutes = 1 // how many routes to get back, > 70 indicates error + routeModel.update() + tryCompare (testRoutesSpy, "count", 1) // 5 sec + tryCompare (testCountSpy, "count", 1) + compare (routeModel.count, 1) + // the test plugin echoes waypoints back as the path of the route: + compare (routeQuery.waypoints.length, 5) + compare (routeModel.get(0).path.length, 5) + compare (routeModel.get(0).path[0].latitude, routeQuery.waypoints[0].latitude) + // check reset() functionality + routeModel.reset() + tryCompare (testRoutesSpy, "count", 2) // 5 sec + tryCompare (testCountSpy, "count", 2) + compare (routeModel.count, 0) + + // delayed responses + compare (testRoutesSlackSpy.count, 0) + compare (routeModelSlack.errorString, "") + compare (routeModel.error, RouteModel.NoError) + compare (testCountSlackSpy.count, 0) + compare (routeModelSlack.count, 0) + routeModelSlack.update() + wait (100) + compare (testRoutesSlackSpy.count, 0) + compare (testCountSlackSpy.count, 0) + tryCompare(testRoutesSlackSpy, "count", 1) + compare (testCountSlackSpy.count, 1) + compare(routeModelSlack.count, 1) + compare (routeModelSlack.get(0).path.length, 5) + compare (routeModelSlack.get(0).path[0].latitude, routeQuery.waypoints[0].latitude) + + // Frequent updates, previous requests are aborted + routeModelSlack.reset() + testRoutesSlackSpy.clear() + testCountSlackSpy.clear() + routeModelSlack.update() + wait (100) + compare(testRoutesSlackSpy.count, 0) + compare(testCountSlackSpy.count, 0) + routeModelSlack.update() + wait (100) + compare(testRoutesSlackSpy.count, 0) + compare(testCountSlackSpy.count, 0) + routeModelSlack.update() + wait (100) + compare(testRoutesSlackSpy.count, 0) + compare(testCountSlackSpy.count, 0) + routeModelSlack.update() + wait (100) + compare(testRoutesSlackSpy.count, 0) + compare(testCountSlackSpy.count, 0) + tryCompare(testRoutesSlackSpy, "count", 1) + compare(testCountSlackSpy.count, 1) + compare(routeModelSlack.count, 1) + + // Autoupdate + automaticRoutesSpy.clear(); + filledRouteQuery.numberAlternativeRoutes = 1 // 'altroutes - 70' is the echoed errorcode + tryCompare (automaticRoutesSpy, "count", 1) // 5 sec + compare(routeModelAutomatic.count, 1) // There should be a route already + compare (routeModelAutomatic.get(0).path.length, 5) + compare (routeModelAutomatic.get(0).path[0].latitude, filledRouteQuery.waypoints[0].latitude) + + // Remove a waypoint and check that autoupdate works + filledRouteQuery.removeWaypoint(fcoordinate2) + tryCompare (automaticRoutesSpy, "count", 2) + compare (routeModelAutomatic.get(0).path.length, 4) + compare (routeModelAutomatic.get(0).path[0].latitude, fcoordinate1.latitude) + + // Add a waypoint and check that autoupdate works + filledRouteQuery.addWaypoint(fcoordinate2); + tryCompare (automaticRoutesSpy, "count", 3) + compare(routeModelAutomatic.count, 1); + compare(routeModelAutomatic.get(0).path.length, 5); + compare(routeModelAutomatic.get(0).path[0].latitude, filledRouteQuery.waypoints[0].latitude); + + // Change contents of a coordinate and check that autoupdate works + filledRouteQuery.waypoints = [ + { latitude: fcoordinate1.latitude + 1, longitude: fcoordinate1.longitude }, + { latitude: 61, longitude: 62 }, + { latitude: 63, longitude: 64 }, + { latitude: 65, longitude: 66 }, + { latitude: 67, longitude: 68 } + ]; + tryCompare (automaticRoutesSpy, "count", 4) + compare(routeModelAutomatic.get(0).path[0].latitude, fcoordinate1.latitude + 1) // new value should be echoed + + // Change query + routeModelAutomatic.query = filledRouteQuery2 + filledRouteQuery2.numberAlternativeRoutes = 3 + tryCompare (automaticRoutesSpy, "count", 5) + compare (routeModelAutomatic.get(0).path.length, 3) + + // Verify that the old query is disconnected internally ie. does not trigger update + filledRouteQuery.waypoints = [ + { latitude: fcoordinate1.latitude + 2, longitude: fcoordinate1.longitude }, + { latitude: 61, longitude: 62 }, + { latitude: 63, longitude: 64 }, + { latitude: 65, longitude: 66 }, + { latitude: 67, longitude: 68 } + ]; + wait(800) // wait to hope no further updates comes through + compare (automaticRoutesSpy.count, 5) + compare(routeModelAutomatic.get(0).path.length, 3); + } + + function test_route_query_handles_destroyed_qml_objects() { + var coordinate = QtPositioning.coordinate(11, 52); + routeQuery.addWaypoint(coordinate); + wait(300); + routeQuery.clearWaypoints(); + } + } +} + + + + diff --git a/tests/auto/declarative_core/tst_supplier.qml b/tests/auto/declarative_core/tst_supplier.qml new file mode 100644 index 0000000..3fcb0d1 --- /dev/null +++ b/tests/auto/declarative_core/tst_supplier.qml @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "Supplier" + + Supplier { id: emptySupplier } + + function test_empty() { + compare(emptySupplier.supplierId, ""); + compare(emptySupplier.name, ""); + compare(emptySupplier.url, ""); + verify(emptySupplier.icon); + } + + Supplier { + id: qmlSupplier + + supplierId: "test-supplier-id" + name: "Test Supplier" + url: "http://example.com/test-supplier-id" + + icon: Icon { + Component.onCompleted: { + parameters.singleUrl = "http://example.com/icons/test-supplier.png" + } + } + } + + function test_qmlConstructedSupplier() { + compare(qmlSupplier.supplierId, "test-supplier-id"); + compare(qmlSupplier.name, "Test Supplier"); + compare(qmlSupplier.url, "http://example.com/test-supplier-id"); + verify(qmlSupplier.icon); + compare(qmlSupplier.icon.parameters.singleUrl, "http://example.com/icons/test-supplier.png"); + } + + Supplier { + id: testSupplier + } + + Plugin { + id: testPlugin + name: "qmlgeo.test.plugin" + allowExperimental: true + } + + Plugin { + id: invalidPlugin + } + + Icon { + id: testIcon + } + + function test_setAndGet_data() { + return [ + { tag: "name", property: "name", signal: "nameChanged", value: "Test Supplier", reset: "" }, + { tag: "supplierId", property: "supplierId", signal: "supplierIdChanged", value: "test-supplier-id-1", reset: "" }, + { tag: "url", property: "url", signal: "urlChanged", value: "http://example.com/test-supplier-id-1", reset: "" }, + { tag: "icon", property: "icon", signal: "iconChanged", value: testIcon } + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testSupplier, data); + } +} diff --git a/tests/auto/declarative_core/tst_user.qml b/tests/auto/declarative_core/tst_user.qml new file mode 100644 index 0000000..1453c41 --- /dev/null +++ b/tests/auto/declarative_core/tst_user.qml @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtTest 1.0 +import QtLocation 5.3 +import "utils.js" as Utils + +TestCase { + id: testCase + + name: "User" + + User { id: emptyUser } + + function test_empty() { + compare(emptyUser.userId, ""); + compare(emptyUser.name, ""); + } + + User { + id: qmlUser + + userId: "testuser" + name: "Test User" + } + + function test_qmlConstructedUser() { + compare(qmlUser.userId, "testuser"); + compare(qmlUser.name, "Test User"); + } + + User { + id: testUser + } + + function test_setAndGet_data() { + return [ + { tag: "userId", property: "userId", signal: "userIdChanged", value: "testuser", reset: "" }, + { tag: "name", property: "name", signal: "nameChanged", value: "Test User", reset: "" }, + ]; + } + + function test_setAndGet(data) { + Utils.testObjectProperties(testCase, testUser, data); + } +} diff --git a/tests/auto/declarative_core/utils.js b/tests/auto/declarative_core/utils.js new file mode 100644 index 0000000..5370bab --- /dev/null +++ b/tests/auto/declarative_core/utils.js @@ -0,0 +1,182 @@ +.pragma library + +function compareArray(a, b) { + if (a.length !== b.length) + return false; + + for (var i = 0; i < a.length; ++i) { + var aMatched = false; + var bMatched = false; + + for (var j = 0; j < b.length; ++j) { + if (a[i] === b[j]) + aMatched = true; + if (b[i] === a[j]) + bMatched = true; + if (aMatched && bMatched) + break; + } + + if (!aMatched || !bMatched) + return false; + } + + return true; +} + +function testObjectProperties(testCase, testObject, data) { + var signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = testObject; + signalSpy.signalName = data.signal; + + // set property to something new + testObject[data.property] = data.value; + if (data.array) { + if (data.expectedValue) { + testCase.verify(compareArray(testObject[data.property], data.expectedValue)); + testCase.compare(signalSpy.count, 1 + data.expectedValue.length); + } else { + testCase.verify(compareArray(testObject[data.property], data.value)); + testCase.compare(signalSpy.count, 1 + data.value.length); + } + + } else { + testCase.compare(testObject[data.property], data.value); + testCase.compare(signalSpy.count, 1); + } + + signalSpy.clear(); + + // set property to same value + testObject[data.property] = data.value; + if (data.array) { + if (data.expectedValue) { + testCase.verify(compareArray(testObject[data.property], data.expectedValue)); + testCase.compare(signalSpy.count, 1 + data.expectedValue.length); + } else { + testCase.verify(compareArray(testObject[data.property], data.value)); + testCase.compare(signalSpy.count, 1 + data.value.length); + } + + } else { + testCase.compare(testObject[data.property], data.value); + testCase.compare(signalSpy.count, 0); + } + + signalSpy.clear(); + + // reset property + if (data.reset === undefined) { + testObject[data.property] = null; + testCase.compare(testObject[data.property], null); + } else { + testObject[data.property] = data.reset; + if (data.array) + testCase.verify(compareArray(testObject[data.property], data.reset)); + else + testCase.compare(testObject[data.property], data.reset); + } + testCase.compare(signalSpy.count, 1); + signalSpy.destroy(); +} + +function compareObj(testCase, obj1, obj2) { + for (var propertyName in obj2) { + if (obj1[propertyName] !== undefined) { + if (propertyName === "dateTime" && isNaN(obj2["dateTime"].getTime())) + testCase.verify(isNaN(obj1["dateTime"].getTime())); + else + testCase.compare(obj1[propertyName], obj2[propertyName]) + } + } +} + +function testConsecutiveFetch(testCase, model, place, expectedValues, data) +{ + var signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', testCase, "SignalSpy"); + signalSpy.target = model; + signalSpy.signalName ="totalCountChanged"; + + var visDataModel = Qt.createQmlObject('import QtQuick 2.0; ' + + 'VisualDataModel{ delegate: Text{} }', + testCase, "dataModel"); + visDataModel.model = model; + + //check that initial values are as expected + testCase.compare(model.totalCount, -1); + testCase.compare(model.place, null); + testCase.compare(visDataModel.items.count, 0); + + //perform an initial fetch with the default batch size + model.batchSize = data.batchSize + model.place = place; + testCase.tryCompare(signalSpy, "count", 1); + signalSpy.clear(); + + var totalCount = model.totalCount; + testCase.compare(totalCount, 5); + testCase.compare(visDataModel.items.count, Math.min(data.batchSize, totalCount)); + + compareObj(testCase, visDataModel.items.get(0).model, expectedValues[0]); + + //fetch remaining items, in batchSize batches + while (visDataModel.items.count < totalCount) { + var startIndex = visDataModel.items.count + + //'creating' the last item will trigger a fetch + visDataModel.items.create(visDataModel.items.count - 1); + + testCase.tryCompare(visDataModel.items, "count", Math.min(totalCount, startIndex + data.batchSize)); + testCase.compare(signalSpy.count, 0); + testCase.compare(model.totalCount, totalCount); + + for (var i = startIndex; i < Math.min(totalCount, startIndex + data.batchSize); ++i) + compareObj(testCase, visDataModel.items.get(i).model, expectedValues[i]); + } + + visDataModel.destroy(); + signalSpy.destroy(); +} + +function testReset(testCase, model, place) +{ + var dataModel = Qt.createQmlObject('import QtQuick 2.0; ' + + 'VisualDataModel{ delegate: Text{} }', + testCase, "dataModel"); + + dataModel.model = model; + model.place = place; + testCase.wait(1); + testCase.verify(model.totalCount > 0); + testCase.verify(dataModel.items.count > 0); + + model.place = null; + testCase.tryCompare(model, "totalCount", -1); + testCase.compare(dataModel.items.count, 0); + + dataModel.destroy(); +} + +function testFetch(testCase, data) +{ + var model = data.model; + var visDataModel = Qt.createQmlObject('import QtQuick 2.0; ' + + 'VisualDataModel{ delegate: Text{} }', + testCase, "dataModel"); + visDataModel.model = model + + var signalSpy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', + testCase, "SignalSpy"); + signalSpy.target = model; + signalSpy.signalName ="totalCountChanged"; + + model.batchSize = data.batchSize; + model.place = data.place; + testCase.tryCompare(signalSpy, "count", 1); + signalSpy.clear(); + testCase.compare(model.totalCount, data.expectedTotalCount); + testCase.compare(visDataModel.items.count, data.expectedCount); + + visDataModel.destroy(); + signalSpy.destroy(); +} diff --git a/tests/auto/declarative_geoshape/declarative_geoshape.pro b/tests/auto/declarative_geoshape/declarative_geoshape.pro new file mode 100644 index 0000000..aa6647f --- /dev/null +++ b/tests/auto/declarative_geoshape/declarative_geoshape.pro @@ -0,0 +1,11 @@ +# QML tests in this directory must not depend on an OpenGL context. + +TEMPLATE = app +TARGET = tst_declarative_geoshape +CONFIG += qmltestcase +SOURCES += main.cpp + +QT += positioning quick + +OTHER_FILES = *.qml +TESTDATA = $$OTHER_FILES diff --git a/tests/auto/declarative_geoshape/main.cpp b/tests/auto/declarative_geoshape/main.cpp new file mode 100644 index 0000000..ffc9445 --- /dev/null +++ b/tests/auto/declarative_geoshape/main.cpp @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +QUICK_TEST_MAIN(declarative_geoshape) diff --git a/tests/auto/declarative_geoshape/tst_locationsingleton.qml b/tests/auto/declarative_geoshape/tst_locationsingleton.qml new file mode 100644 index 0000000..e38e4ec --- /dev/null +++ b/tests/auto/declarative_geoshape/tst_locationsingleton.qml @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtPositioning 5.2 +import QtLocation 5.5 + +Item { + id: testCase + + property variant coordinate1: QtPositioning.coordinate(1, 1) + property variant coordinate2: QtPositioning.coordinate(2, 2) + property variant coordinate3: QtPositioning.coordinate(80, 80) + + property variant emptyCircle: QtPositioning.circle() + property variant circle1: QtPositioning.circle(coordinate1, 200000) + + SignalSpy { id: circleChangedSpy; target: testCase; signalName: "emptyCircleChanged" } + + TestCase { + name: "Bounding circle" + function test_circle_defaults_and_setters() { + circleChangedSpy.clear(); + compare (emptyCircle.radius, -1) + compare (circle1.radius, 200000) + + emptyCircle.radius = 200 + compare(circleChangedSpy.count, 1); + emptyCircle.radius = 200; + compare(circleChangedSpy.count, 1); + + emptyCircle.center = coordinate1; + compare(circleChangedSpy.count, 2); + emptyCircle.center = coordinate1 + compare(circleChangedSpy.count, 2); + emptyCircle.center = coordinate2 + compare(circleChangedSpy.count, 3); + + emptyCircle.center = coordinate1 + emptyCircle.radius = 200000 + + compare(emptyCircle.contains(coordinate1), true); + compare(emptyCircle.contains(coordinate2), true); + compare(emptyCircle.contains(coordinate3), false); + } + } + + // coordinate unit square + property variant bl: QtPositioning.coordinate(0, 0) + property variant tl: QtPositioning.coordinate(1, 0) + property variant tr: QtPositioning.coordinate(1, 1) + property variant br: QtPositioning.coordinate(0, 1) + property variant ntr: QtPositioning.coordinate(3, 3) + + property variant invalid: QtPositioning.coordinate(100, 190) + property variant inside: QtPositioning.coordinate(0.5, 0.5) + property variant outside: QtPositioning.coordinate(2, 2) + + property variant box: QtPositioning.rectangle(tl, br) + + property variant coordinates: [bl, tl, tr, br] + property variant coordinates2: [bl, tl, tr, br, ntr] + property variant coordinates3: [tr] + property variant coordinates4: [invalid] + property variant coordinates5: [] + + property variant listBox: QtPositioning.rectangle(coordinates) + property variant listBox2: QtPositioning.rectangle(coordinates2) + property variant listBox3: QtPositioning.rectangle(coordinates3) + property variant listBox4: QtPositioning.rectangle(coordinates4) + property variant listBox5: QtPositioning.rectangle(coordinates5) + + property variant widthBox: QtPositioning.rectangle(inside, 1, 1); + + // C++ auto test exists for basics of bounding box, testing here + // only added functionality + TestCase { + name: "Bounding box" + function test_box_defaults_and_setters() { + compare (box.bottomRight.longitude, br.longitude) // sanity + compare (box.contains(bl), true) + compare (box.contains(inside), true) + compare (box.contains(outside), false) + box.topRight = ntr + compare (box.contains(outside), true) + + compare (listBox.isValid, true) + compare (listBox.contains(outside), false) + compare (listBox2.contains(outside), true) + compare (listBox3.isValid, true) + compare (listBox3.isEmpty, true) + compare (listBox4.isValid, false) + compare (listBox5.isValid, false) + + compare (widthBox.contains(inside), true) + compare (widthBox.contains(outside), false) + } + } + + TestCase { + name: "Shape" + + function test_shape_comparison_data() { + return [ + { tag: "invalid shape", shape1: QtPositioning.shape(), shape2: QtPositioning.shape(), result: true }, + { tag: "box equal", shape1: box, shape2: QtPositioning.rectangle(tl, br), result: true }, + { tag: "box not equal", shape1: box, shape2: QtPositioning.rectangle([inside, outside]), result: false }, + { tag: "box invalid shape", rect1: box, shape2: QtPositioning.shape(), result: false }, + { tag: "invalid rectangle", shape1: QtPositioning.rectangle(), shape2: QtPositioning.rectangle(), result: true }, + { tag: "invalid rectangle2", shape1: QtPositioning.rectangle(), shape2: QtPositioning.shape(), result: false }, + { tag: "circle1 equal", shape1: circle1, shape2: QtPositioning.circle(coordinate1, 200000), result: true }, + { tag: "circle1 not equal", shape1: circle1, shape2: QtPositioning.circle(coordinate2, 2000), result: false }, + { tag: "circle1 invalid shape", shape1: circle1, shape2: QtPositioning.shape(), result: false }, + { tag: "invalid circle", shape1: QtPositioning.circle(), shape2: QtPositioning.circle(), result: true }, + { tag: "invalid circle2", shape1: QtPositioning.circle(), shape2: QtPositioning.shape(), result: false } + ] + } + + function test_shape_comparison(data) { + compare(data.shape1 === data.shape2, data.result) + compare(data.shape1 !== data.shape2, !data.result) + compare(data.shape1 == data.shape2, data.result) + compare(data.shape1 != data.shape2, !data.result) + } + } + + TestCase { + name: "Conversions" + + function test_shape_circle_conversions() { + var circle = QtPositioning.shapeToCircle(QtPositioning.shape()) + verify(!circle.isValid) + circle = QtPositioning.shapeToCircle(QtPositioning.circle()) + verify(!circle.isValid) + circle = QtPositioning.shapeToCircle(QtPositioning.circle(tl, 10000)) + verify(circle.isValid) + compare(circle.center, tl) + compare(circle.radius, 10000) + circle = QtPositioning.shapeToCircle(QtPositioning.rectangle()) + verify(!circle.isValid) + circle = QtPositioning.shapeToCircle(QtPositioning.rectangle(tl, br)) + verify(!circle.isValid) + circle = QtPositioning.shapeToCircle(listBox) + verify(!circle.isValid) + } + + function test_shape_rectangle_conversions() { + var rectangle = QtPositioning.shapeToRectangle(QtPositioning.shape()) + verify(!rectangle.isValid) + rectangle = QtPositioning.shapeToRectangle(QtPositioning.circle()) + verify(!rectangle.isValid) + rectangle = QtPositioning.shapeToRectangle(QtPositioning.circle(tl, 10000)) + verify(!rectangle.isValid) + rectangle = QtPositioning.shapeToRectangle(QtPositioning.rectangle()) + verify(!rectangle.isValid) + rectangle = QtPositioning.shapeToRectangle(QtPositioning.rectangle(tl, br)) + verify(rectangle.isValid) + compare(rectangle.topLeft, tl) + compare(rectangle.bottomRight, br) + rectangle = QtPositioning.shapeToRectangle(listBox) + verify(rectangle.isValid) + } + } + + + MapPolyline { + id: mapPolyline + path: [ + { latitude: -27, longitude: 153.0 }, + { latitude: -27, longitude: 154.1 }, + { latitude: -28, longitude: 153.5 }, + { latitude: -29, longitude: 153.5 } + ] + } + + TestCase { + name: "MapPolyline path" + function test_path_operations() { + compare(mapPolyline.path[1].latitude, -27) + compare(mapPolyline.path[1].longitude, 154.1) + compare(mapPolyline.coordinateAt(1), QtPositioning.coordinate(-27, 154.1)) + compare(mapPolyline.path.length, mapPolyline.pathLength()) + + mapPolyline.removeCoordinate(1); + compare(mapPolyline.path[1].latitude, -28) + compare(mapPolyline.path[1].longitude, 153.5) + compare(mapPolyline.coordinateAt(1), QtPositioning.coordinate(-28, 153.5)) + compare(mapPolyline.path.length, mapPolyline.pathLength()) + + mapPolyline.addCoordinate(QtPositioning.coordinate(30, 153.1)) + compare(mapPolyline.path[mapPolyline.path.length-1].latitude, 30) + compare(mapPolyline.path[mapPolyline.path.length-1].longitude, 153.1) + compare(mapPolyline.containsCoordinate(QtPositioning.coordinate(30, 153.1)), true) + compare(mapPolyline.path.length, mapPolyline.pathLength()) + + mapPolyline.removeCoordinate(QtPositioning.coordinate(30, 153.1)) + compare(mapPolyline.path[mapPolyline.path.length-1].latitude, -29) + compare(mapPolyline.path[mapPolyline.path.length-1].longitude, 153.5) + compare(mapPolyline.containsCoordinate(QtPositioning.coordinate(30, 153.1)), false) + compare(mapPolyline.path.length, mapPolyline.pathLength()) + + mapPolyline.insertCoordinate(2, QtPositioning.coordinate(35, 153.1)) + compare(mapPolyline.path[2].latitude, 35) + compare(mapPolyline.path[2].longitude, 153.1) + compare(mapPolyline.containsCoordinate(QtPositioning.coordinate(35, 153.1)), true) + compare(mapPolyline.path.length, mapPolyline.pathLength()) + + mapPolyline.replaceCoordinate(2, QtPositioning.coordinate(45, 150.1)) + compare(mapPolyline.path[2].latitude, 45) + compare(mapPolyline.path[2].longitude, 150.1) + compare(mapPolyline.containsCoordinate(QtPositioning.coordinate(35, 153.1)), false) + compare(mapPolyline.containsCoordinate(QtPositioning.coordinate(45, 150.1)), true) + compare(mapPolyline.path.length, mapPolyline.pathLength()) + + mapPolyline.insertCoordinate(2, QtPositioning.coordinate(35, 153.1)) + compare(mapPolyline.coordinateAt(2).latitude, 35) + compare(mapPolyline.coordinateAt(2).longitude, 153.1) + compare(mapPolyline.containsCoordinate(QtPositioning.coordinate(35, 153.1)), true) + compare(mapPolyline.path.length, mapPolyline.pathLength()) + } + } +} diff --git a/tests/auto/declarative_ui/declarative_ui.pro b/tests/auto/declarative_ui/declarative_ui.pro new file mode 100644 index 0000000..d3a2f08 --- /dev/null +++ b/tests/auto/declarative_ui/declarative_ui.pro @@ -0,0 +1,18 @@ +# QML tests in this directory depend on a Qt platform plugin that supports OpenGL. +# QML tests that do not require an OpenGL context should go in ../declarative_core. + +TEMPLATE = app +TARGET = tst_declarative_ui +!no_ui_tests:CONFIG += qmltestcase +SOURCES += main.cpp + +CONFIG -= app_bundle + +QT += location quick + +OTHER_FILES = *.qml +TESTDATA = $$OTHER_FILES + + +# Import path used by 'make check' since CI doesn't install test imports +IMPORTPATH = $$OUT_PWD/../../../qml diff --git a/tests/auto/declarative_ui/main.cpp b/tests/auto/declarative_ui/main.cpp new file mode 100644 index 0000000..8a12f41 --- /dev/null +++ b/tests/auto/declarative_ui/main.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +static void initializeLibraryPath() +{ + // Set custom path since CI doesn't install test plugins +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif +} + +Q_COREAPP_STARTUP_FUNCTION(initializeLibraryPath) + +QUICK_TEST_MAIN(declarative_ui) diff --git a/tests/auto/declarative_ui/tst_map.qml b/tests/auto/declarative_ui/tst_map.qml new file mode 100644 index 0000000..5f02962 --- /dev/null +++ b/tests/auto/declarative_ui/tst_map.qml @@ -0,0 +1,343 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.6 +import QtPositioning 5.5 + +Item { + width:100 + height:100 + // General-purpose elements for the test: + Plugin { id: testPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true } + Plugin { id: testPlugin2; name: "gmlgeo.test.plugin"; allowExperimental: true } + Plugin { id: herePlugin; name: "here"; + parameters: [ + PluginParameter { + name: "here.app_id" + value: "stub" + }, + PluginParameter { + name: "here.token" + value: "stub" + } + ] + } + + property variant coordinate1: QtPositioning.coordinate(10, 11) + property variant coordinate2: QtPositioning.coordinate(12, 13) + property variant coordinate3: QtPositioning.coordinate(50, 50, 0) + property variant coordinate4: QtPositioning.coordinate(80, 80, 0) + property variant coordinate5: QtPositioning.coordinate(20, 180) + property variant invalidCoordinate: QtPositioning.coordinate() + property variant altitudelessCoordinate: QtPositioning.coordinate(50, 50) + + Map { id: mapZoomOnCompleted; width: 200; height: 200; + zoomLevel: 3; center: coordinate1; plugin: testPlugin; + Component.onCompleted: { + zoomLevel = 7 + } + } + SignalSpy {id: mapZoomSpy; target: mapZoomOnCompleted; signalName: 'zoomLevelChanged'} + + Map { id: mapZoomDefault; width: 200; height: 200; + center: coordinate1; plugin: testPlugin; } + + Map { id: mapZoomUserInit; width: 210; height: 210; + zoomLevel: 4; center: coordinate1; plugin: testPlugin; + Component.onCompleted: { + console.log("mapZoomUserInit completed") + } + } + + Map {id: map; plugin: testPlugin; center: coordinate1; width: 100; height: 100} + SignalSpy {id: mapCenterSpy; target: map; signalName: 'centerChanged'} + + Map {id: coordinateMap; plugin: herePlugin; center: coordinate3; + width: 1000; height: 1000; zoomLevel: 15 } + + + + + TestCase { + when: windowShown + name: "MapProperties" + + function fuzzy_compare(val, ref) { + var tolerance = 0.01; + if ((val > ref - tolerance) && (val < ref + tolerance)) + return true; + console.log('map fuzzy cmp returns false for value, ref: ' + val + ', ' + ref) + return false; + } + + function init() { + mapCenterSpy.clear(); + } + + function test_map_center() { + // coordinate is set at map element declaration + compare(map.center.latitude, 10) + compare(map.center.longitude, 11) + + // change center and its values + mapCenterSpy.clear(); + compare(mapCenterSpy.count, 0) + map.center = coordinate2 + compare(mapCenterSpy.count, 1) + map.center = coordinate2 + compare(mapCenterSpy.count, 1) + + // change center to dateline + mapCenterSpy.clear() + compare(mapCenterSpy.count, 0) + map.center = coordinate5 + compare(mapCenterSpy.count, 1) + compare(map.center, coordinate5) + + map.center = coordinate2 + + verify(isNaN(map.center.altitude)); + compare(map.center.longitude, 13) + compare(map.center.latitude, 12) + } + + function test_map_clamp() + { + //valid + map.center = QtPositioning.coordinate(10.0, 20.5, 30.8) + map.zoomLevel = 2.0 + + compare(map.center.latitude, 10) + compare(map.center.longitude, 20.5) + compare(map.center.altitude, 30.8) + + //negative values + map.center = QtPositioning.coordinate(-50, -20, 100) + map.zoomLevel = 1.0 + + compare(map.center.latitude, -50) + compare(map.center.longitude, -20) + compare(map.center.altitude, 100) + + //clamped center negative + map.center = QtPositioning.coordinate(-89, -45, 0) + map.zoomLevel = 1.0 + + fuzzyCompare(map.center.latitude, -80.8728, 0.001) + compare(map.center.longitude, -45) + compare(map.center.altitude, 0) + + //clamped center positive + map.center = QtPositioning.coordinate(86, 38, 0) + map.zoomLevel = 1.0 + + fuzzyCompare(map.center.latitude, 80.8728, 0.001) + compare(map.center.longitude, 38) + compare(map.center.altitude, 0) + } + + function test_zoom_limits() + { + map.center.latitude = 30 + map.center.longitude = 60 + map.zoomLevel = 4 + + //initial plugin values + compare(map.minimumZoomLevel, 0) + compare(map.maximumZoomLevel, 20) + + //Higher min level than curr zoom, should change curr zoom + map.minimumZoomLevel = 5 + map.maximumZoomLevel = 18 + compare(map.zoomLevel, 5) + compare(map.minimumZoomLevel, 5) + compare(map.maximumZoomLevel, 18) + + //Trying to set higher than max, max should be set. + map.maximumZoomLevel = 21 + compare(map.minimumZoomLevel, 5) + compare(map.maximumZoomLevel, 20) + + //Negative values should be ignored + map.minimumZoomLevel = -1 + map.maximumZoomLevel = -2 + compare(map.minimumZoomLevel, 5) + compare(map.maximumZoomLevel, 20) + + //Max limit lower than curr zoom, should change curr zoom + map.zoomLevel = 18 + map.maximumZoomLevel = 16 + compare(map.zoomLevel, 16) + + //reseting default + map.minimumZoomLevel = 0 + map.maximumZoomLevel = 20 + compare(map.minimumZoomLevel, 0) + compare(map.maximumZoomLevel, 20) + } + + function test_zoom() + { + wait(1000) + compare(mapZoomOnCompleted.zoomLevel, 7) + compare(mapZoomDefault.zoomLevel, 8) + compare(mapZoomUserInit.zoomLevel, 4) + + mapZoomSpy.clear() + mapZoomOnCompleted.zoomLevel = 6 + tryCompare(mapZoomSpy, "count", 1) + } + + function test_pan() + { + map.center.latitude = 30 + map.center.longitude = 60 + map.zoomLevel = 4 + mapCenterSpy.clear(); + + // up left + tryCompare(mapCenterSpy, "count", 0) + map.pan(-20,-20) + tryCompare(mapCenterSpy, "count", 1) + verify(map.center.latitude > 30) + verify(map.center.longitude < 60) + map.center.latitude = 30 + map.center.longitude = 60 + mapCenterSpy.clear() + // up + map.pan(0,-20) + tryCompare(mapCenterSpy, "count", 1) + verify(map.center.latitude > 30) + compare(map.center.longitude, 60) + map.center.latitude = 30 + map.center.longitude = 60 + mapCenterSpy.clear() + // up right + tryCompare(mapCenterSpy, "count", 0) + map.pan(20,-20) + tryCompare(mapCenterSpy, "count", 1) + verify(map.center.latitude > 30) + verify(map.center.longitude > 60) + map.center.latitude = 30 + map.center.longitude = 60 + mapCenterSpy.clear() + // left + map.pan(-20,0) + tryCompare(mapCenterSpy, "count", 1) + verify (fuzzy_compare(map.center.latitude, 30)) + verify(map.center.longitude < 60) + map.center.latitude = 30 + map.center.longitude = 60 + mapCenterSpy.clear() + // center + map.pan(0,0) + tryCompare(mapCenterSpy, "count", 0) + compare(map.center.latitude, 30) + compare(map.center.longitude, 60) + map.center.latitude = 30 + map.center.longitude = 60 + mapCenterSpy.clear() + // right + map.pan(20,0) + tryCompare(mapCenterSpy, "count", 1) + verify (fuzzy_compare(map.center.latitude, 30)) + verify(map.center.longitude > 60) + map.center.latitude = 30 + map.center.longitude = 60 + mapCenterSpy.clear() + // down left + map.pan(-20,20) + tryCompare(mapCenterSpy, "count", 1) + verify (map.center.latitude < 30 ) + verify (map.center.longitude < 60 ) + map.center.latitude = 30 + map.center.longitude = 60 + mapCenterSpy.clear() + // down + map.pan(0,20) + tryCompare(mapCenterSpy, "count", 1) + verify (map.center.latitude < 30 ) + verify (fuzzy_compare(map.center.longitude, 60)) + map.center.latitude = 30 + map.center.longitude = 60 + mapCenterSpy.clear() + // down right + map.pan(20,20) + tryCompare(mapCenterSpy, "count", 1) + verify (map.center.latitude < 30 ) + verify (map.center.longitude > 60 ) + map.center.latitude = 30 + map.center.longitude = 60 + mapCenterSpy.clear() + } + + function test_coordinate_conversion() + { + wait(1000) + mapCenterSpy.clear(); + compare(coordinateMap.center.latitude, 50) + compare(coordinateMap.center.longitude, 50) + // valid to screen position + var point = coordinateMap.fromCoordinate(coordinateMap.center) + verify (point.x > 495 && point.x < 505) + verify (point.y > 495 && point.y < 505) + // valid coordinate without altitude + point = coordinateMap.fromCoordinate(altitudelessCoordinate) + verify (point.x > 495 && point.x < 505) + verify (point.y > 495 && point.y < 505) + // out of map area in view + //var oldZoomLevel = coordinateMap.zoomLevel + //coordinateMap.zoomLevel = 8 + point = coordinateMap.fromCoordinate(coordinate4) + verify(isNaN(point.x)) + verify(isNaN(point.y)) + //coordinateMap.zoomLevel = oldZoomLevel + // invalid coordinates + point = coordinateMap.fromCoordinate(invalidCoordinate) + verify(isNaN(point.x)) + verify(isNaN(point.y)) + point = coordinateMap.fromCoordinate(null) + verify(isNaN(point.x)) + verify(isNaN(point.y)) + // valid point to coordinate + var coord = coordinateMap.toCoordinate(Qt.point(500,500)) + verify(coord.latitude > 49 && coord.latitude < 51) + verify(coord.longitude > 49 && coord.longitude < 51) + // beyond + coord = coordinateMap.toCoordinate(Qt.point(2000, 2000)) + verify(isNaN(coord.latitude)) + verify(isNaN(coord.longitde)) + // invalid + coord = coordinateMap.toCoordinate(Qt.point(-5, -6)) + verify(isNaN(coord.latitude)) + verify(isNaN(coord.longitde)) + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_coordinateanimation.qml b/tests/auto/declarative_ui/tst_map_coordinateanimation.qml new file mode 100644 index 0000000..d9b71ed --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_coordinateanimation.qml @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.6 +import QtPositioning 5.5 + +Item { + width:100 + height:100 + // General-purpose elements for the test: + Plugin { id: testPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true } + + + property var coordinateList: [] + property int coordinateCount: 0 + property int animationDuration: 100 + + Map {id: map + plugin: testPlugin + width: 100 + height: 100 + + Behavior on center { + id: centerBehavior + enabled: false + CoordinateAnimation { + id: coordinateAnimation + duration: animationDuration + } + } + + onCenterChanged: { + if (!coordinateList) { + coordinateList = [] + } + + coordinateList[coordinateCount] = {'latitude': center.latitude, 'longitude': center.longitude} + coordinateCount++ + } + } + + function toMercator(coord) { + var pi = Math.PI + var lon = coord.longitude / 360.0 + 0.5; + + var lat = coord.latitude; + lat = 0.5 - (Math.log(Math.tan((pi / 4.0) + (pi / 2.0) * lat / 180.0)) / pi) / 2.0; + lat = Math.max(0.0, lat); + lat = Math.min(1.0, lat); + + return {'latitude': lat, 'longitude': lon}; + } + + TestCase { + when: windowShown + name: "CoordinateAnimation" + + function test_coordinate_animation() { + + coordinateList = [] + coordinateCount = 0 + + var from = {'latitude': 58.0, 'longitude': 12.0} + var to = {'latitude': 62.0, 'longitude': 24.0} + + + var fromMerc = toMercator(from) + var toMerc = toMercator(to) + + var delta = (toMerc.latitude - fromMerc.latitude) / (toMerc.longitude - fromMerc.longitude) + + // Set from coordinate with animation disabled. + map.center = QtPositioning.coordinate(from.latitude, from.longitude) + + // Expect only one update + compare(coordinateList.length, 1) + + // Set to coordinate with animation enabled + centerBehavior.enabled = true + map.center = QtPositioning.coordinate(to.latitude, to.longitude) + wait(animationDuration) + tryCompare(coordinateAnimation,"running",false) + + //check correct start position + compare(coordinateList[0].latitude, from.latitude) + compare(coordinateList[0].longitude, from.longitude) + + //check correct end position + compare(coordinateList[coordinateList.length - 1].latitude, to.latitude) + compare(coordinateList[coordinateList.length - 1].longitude, to.longitude) + + var i + var lastLatitude + for (i in coordinateList) { + var coordinate = coordinateList[i] + var mercCoordinate = toMercator(coordinate) + + //check that coordinates from the animation is along a straight line between from and to + var estimatedLatitude = fromMerc.latitude + (mercCoordinate.longitude - fromMerc.longitude) * delta + verify(mercCoordinate.latitude - estimatedLatitude < 0.00000000001); + + //check that each step has moved in the right direction + if (lastLatitude) { + verify(coordinate.latitude > lastLatitude) + } + lastLatitude = coordinate.latitude + } + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_error.qml b/tests/auto/declarative_ui/tst_map_error.qml new file mode 100644 index 0000000..c978e0c --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_error.qml @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.6 +import QtPositioning 5.5 + +Item { + id: page + x: 0; y: 0; + width: 200 + height: 100 + property variant coordinate: QtPositioning.coordinate(20, 20) + + Plugin { + id: errorPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true + parameters: [ + PluginParameter { name: "error"; value: "1"}, + PluginParameter { name: "errorString"; value: "This error was expected. No worries !"} + ] + } + + Map { + id: map_error_plugin; + property alias mouseClickedSpy: mouseClickedSpy1 + x: 0; y: 0; width: 100; height: 100; plugin: errorPlugin; + + MouseArea { + id: mouseArea1 + objectName: "mouseArea" + x: 25; y: 25; width: 50; height: 50; + preventStealing: true + } + + SignalSpy {id: mouseClickedSpy1; target: mouseArea1; signalName: "clicked"} + } + + Map { + id: map_no_plugin; + property alias mouseClickedSpy: mouseClickedSpy2 + x: 100; y: 0; width: 100; height: 100; + + MouseArea { + id: mouseArea2 + objectName: "mouseArea" + x: 25; y: 25; width: 50; height: 50; + preventStealing: true + } + + SignalSpy {id: mouseClickedSpy2; target: mouseArea2; signalName: "clicked"} + } + + TestCase { + name: "MapErrorHandling" + when: windowShown + + function init() { + map_error_plugin.zoomLevel = 0 + map_no_plugin.zoomLevel = 0 + map_error_plugin.center = QtPositioning.coordinate(0, 0) + map_no_plugin.center = QtPositioning.coordinate(0, 0) + map_error_plugin.mouseClickedSpy.clear() + map_no_plugin.mouseClickedSpy.clear() + } + + function map_clicked(map) + { + mouseClick(map, 5, 5) + mouseClick(map, 50, 50) + mouseClick(map, 50, 50) + mouseClick(map, 50, 50) + tryCompare(map.mouseClickedSpy, "count", 3) + } + + function test_map_clicked_wiht_no_plugin() + { + map_clicked(map_no_plugin) + } + + function test_map_clicked_with_error_plugin() + { + map_clicked(map_error_plugin) + } + + function test_map_no_supportedMapTypes() + { + compare(map_no_plugin.supportedMapTypes.length , 0) + compare(map_error_plugin.supportedMapTypes.length , 0) + } + + function test_map_set_zoom_level() + { + map_no_plugin.zoomLevel = 9 + compare(map_no_plugin.zoomLevel,9) + map_error_plugin.zoomLevel = 9 + compare(map_error_plugin.zoomLevel,9) + } + + function test_map_set_center() + { + map_no_plugin.center = coordinate + verify(map_no_plugin.center === coordinate) + map_error_plugin.center = coordinate + verify(map_error_plugin.center === coordinate) + } + + function test_map_no_mapItems() + { + compare(map_no_plugin.mapItems.length , 0) + compare(map_error_plugin.mapItems.length , 0) + } + + function test_map_error() + { + compare(map_no_plugin.error , 0) + compare(map_no_plugin.errorString , "") + compare(map_error_plugin.error , 1) + compare(map_error_plugin.errorString ,"This error was expected. No worries !") + } + + function test_map_toCoordinate() + { + map_no_plugin.center = coordinate + compare(map_no_plugin.toCoordinate(50,50).isValid,false) + map_error_plugin.center = coordinate + compare(map_error_plugin.toCoordinate(50,50).isValid,false) + } + + function test_map_fromCoordinate() + { + verify(isNaN(map_error_plugin.fromCoordinate(coordinate).x)) + verify(isNaN(map_error_plugin.fromCoordinate(coordinate).y)) + verify(isNaN(map_no_plugin.fromCoordinate(coordinate).x)) + verify(isNaN(map_no_plugin.fromCoordinate(coordinate).y)) + } + + function test_map_gesture_enabled() + { + verify(map_error_plugin.gesture.enabled) + verify(map_no_plugin.gesture.enabled) + } + + function test_map_pan() + { + map_no_plugin.center = coordinate + map_no_plugin.pan(20,20) + verify(map_no_plugin.center === coordinate) + map_error_plugin.center = coordinate + map_error_plugin.pan(20,20) + verify(map_error_plugin.center === coordinate) + } + + function test_map_prefetchData() + { + map_error_plugin.prefetchData() + map_no_plugin.prefetchData() + } + + function test_map_fitViewportToMapItems() + { + map_error_plugin.fitViewportToMapItems() + map_no_plugin.fitViewportToMapItems() + } + + function test_map_setVisibleRegion() + { + map_no_plugin.visibleRegion = QtPositioning.circle(coordinate,1000) + verify(map_no_plugin.center != coordinate) + verify(map_no_plugin.visibleRegion == QtPositioning.circle(coordinate,1000)) + map_error_plugin.visibleRegion = QtPositioning.circle(coordinate,1000) + verify(map_error_plugin.center != coordinate) + verify(map_no_plugin.visibleRegion == QtPositioning.circle(coordinate,1000)) + } + + function test_map_activeMapType() + { + compare(map_no_plugin.supportedMapTypes.length, 0) + compare(map_no_plugin.activeMapType.style, MapType.NoMap) + compare(map_error_plugin.supportedMapTypes.length, 0) + compare(map_error_plugin.activeMapType.style, MapType.NoMap) + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_flick.qml b/tests/auto/declarative_ui/tst_map_flick.qml new file mode 100644 index 0000000..15f13b5 --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_flick.qml @@ -0,0 +1,347 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtTest 1.0 +import QtLocation 5.6 +import QtPositioning 5.5 + +Item { + // General-purpose elements for the test: + id: page + width: 100 + height: 100 + Plugin { id: testPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true } + + property variant coordinate: QtPositioning.coordinate(10, 11) + + MouseArea { + id: mouseAreaBottom + anchors.fill: parent + visible: false + } + + Map { + id: map + plugin: testPlugin + center: coordinate; + zoomLevel: 9; + anchors.fill: page + x:0; y:0 + + property real flickStartedLatitude + property real flickStartedLongitude + property bool disableOnPanStartedWithNoGesture: false + property bool disableOnFlickStartedWithNoGesture: false + property bool disableOnPanStartedWithDisabled: false + property bool disableOnFlickStartedWithDisabled: false + + gesture.onPanStarted: { + if (disableOnPanStartedWithNoGesture) + map.gesture.acceptedGestures = MapGestureArea.NoGesture + if (disableOnPanStartedWithDisabled) + map.gesture.enabled = false + } + gesture.onFlickStarted: { + flickStartedLatitude = map.center.latitude + flickStartedLatitude = map.center.longitude + if (disableOnFlickStartedWithNoGesture) + map.gesture.acceptedGestures = MapGestureArea.NoGesture + if (disableOnFlickStartedWithDisabled) + map.gesture.enabled = false + } + MouseArea { + id: mouseAreaTop + anchors.fill: parent + visible: false + } + } + + SignalSpy {id: centerSpy; target: map; signalName: 'centerChanged'} + SignalSpy {id: panStartedSpy; target: map.gesture; signalName: 'panStarted'} + SignalSpy {id: panFinishedSpy; target: map.gesture; signalName: 'panFinished'} + SignalSpy {id: gestureEnabledSpy; target: map.gesture; signalName: 'enabledChanged'} + SignalSpy {id: flickDecelerationSpy; target: map.gesture; signalName: 'flickDecelerationChanged'} + SignalSpy {id: flickStartedSpy; target: map.gesture; signalName: 'flickStarted'} + SignalSpy {id: flickFinishedSpy; target: map.gesture; signalName: 'flickFinished'} + SignalSpy {id: mouseAreaTopSpy; target: mouseAreaTop; signalName: 'onPressed'} + SignalSpy {id: mouseAreaBottomSpy; target: mouseAreaBottom; signalName: 'onPressed'} + + TestCase { + when: windowShown + name: "MapFlick" + + function init() + { + map.gesture.acceptedGestures = MapGestureArea.PanGesture | MapGestureArea.FlickGesture; + map.gesture.enabled = true + map.gesture.panEnabled = true + map.gesture.flickDeceleration = 500 + map.zoomLevel = 0 + map.disableOnPanStartedWithNoGesture = false + map.disableOnFlickStartedWithNoGesture = false + map.disableOnPanStartedWithDisabled = false + map.disableOnFlickStartedWithDisabled = false + centerSpy.clear() + gestureEnabledSpy.clear() + flickDecelerationSpy.clear() + panStartedSpy.clear() + panFinishedSpy.clear() + flickStartedSpy.clear() + flickFinishedSpy.clear() + mouseAreaTopSpy.clear() + mouseAreaBottomSpy.clear() + mouseAreaBottom.visible = false + mouseAreaTop.visible = false + compare(map.gesture.pinchActive, false) + compare(map.gesture.panActive, false) + } + + function initTestCase() + { + //check default values + compare(map.gesture.enabled, true) + map.gesture.enabled = false + compare(gestureEnabledSpy.count, 1) + compare(map.gesture.enabled, false) + map.gesture.enabled = false + compare(gestureEnabledSpy.count, 1) + compare(map.gesture.enabled, false) + map.gesture.enabled = true + compare(gestureEnabledSpy.count, 2) + compare(map.gesture.enabled, true) + compare(map.gesture.pinchActive, false) + compare(map.gesture.panActive, false) + verify(map.gesture.acceptedGestures & MapGestureArea.PinchGesture) + map.gesture.acceptedGestures = MapGestureArea.NoGesture + compare(map.gesture.acceptedGestures, MapGestureArea.NoGesture) + map.gesture.acceptedGestures = MapGestureArea.NoGesture + compare(map.gesture.acceptedGestures, MapGestureArea.NoGesture) + map.gesture.acceptedGestures = MapGestureArea.PinchGesture | MapGestureArea.PanGesture + compare(map.gesture.acceptedGestures, MapGestureArea.PinchGesture | MapGestureArea.PanGesture) + map.gesture.acceptedGestures = MapGestureArea.PanGesture + compare(map.gesture.acceptedGestures, MapGestureArea.PanGesture) + compare(map.gesture.flickDeceleration, 2500) + map.gesture.flickDeceleration = 2600 + compare(flickDecelerationSpy.count, 1) + compare(map.gesture.flickDeceleration, 2600) + map.gesture.flickDeceleration = 2600 + compare(flickDecelerationSpy.count, 1) + compare(map.gesture.flickDeceleration, 2600) + map.gesture.flickDeceleration = 400 // too small + compare(flickDecelerationSpy.count, 2) + compare(map.gesture.flickDeceleration, 500) // clipped to min + map.gesture.flickDeceleration = 11000 // too big + compare(flickDecelerationSpy.count, 3) + compare(map.gesture.flickDeceleration, 10000) // clipped to max + } + + function flick_down() + { + map.center.latitude = 10 + map.center.longitude = 11 + mousePress(page, 0, 50) + for (var i = 0; i < 50; i += 5) { + wait(20) + mouseMove(page, 0, (50 + i), 0, Qt.LeftButton); + } + mouseRelease(page, 0, 100) + + // order of signals is: flickStarted, either order: (flickEnded, movementEnded) + verify(map.center.latitude > 10) // latitude increases we are going 'up/north' (moving mouse down) + var moveLatitude = map.center.latitude // store lat and check that flick continues + + tryCompare(flickStartedSpy, "count", 1) + tryCompare(panFinishedSpy, "count", 1) + tryCompare(flickFinishedSpy, "count", 1) + + verify(map.center.latitude > moveLatitude) + compare(map.center.longitude, 11) // should remain the same + } + + function test_flick_down() + { + flick_down() + } + + function test_flick_down_with_filtetring() + { + mouseAreaTop.visible = true + mouseAreaBottom.visible = true + flick_down() + tryCompare(mouseAreaTopSpy, "count", 1) + tryCompare(mouseAreaBottomSpy, "count",0) + } + + function flick_up() + { + map.center.latitude = 70 + map.center.longitude = 11 + mousePress(page, 10, 95) + for (var i = 45; i > 0; i -= 5) { + wait(20) + mouseMove(page, 10, (50 + i), 0, Qt.LeftButton); + } + mouseRelease(page, 10, 50) + verify(map.center.latitude < 70) + var moveLatitude = map.center.latitude // store lat and check that flick continues + tryCompare(flickStartedSpy, "count", 1) + tryCompare(panFinishedSpy, "count", 1) + tryCompare(flickFinishedSpy, "count", 1) + verify(map.center.latitude < moveLatitude) + compare(map.center.longitude, 11) // should remain the same + } + + function test_flick_up() + { + flick_up() + } + + function test_flick_up_with_filtering() + { + mouseAreaTop.visible = true + mouseAreaBottom.visible = true + flick_up() + tryCompare(mouseAreaTopSpy, "count", 1) + tryCompare(mouseAreaBottomSpy, "count",0) + } + + function test_flick_diagonal() + { + map.center.latitude = 50 + map.center.longitude = 50 + mousePress(page, 0, 0) + for (var i = 0; i < 50; i += 5) { + wait(20) + mouseMove(page, i, i, 0, Qt.LeftButton); + } + mouseRelease(page, 50, 50) + verify(map.center.latitude > 50) + verify(map.center.longitude < 50) + var moveLatitude = map.center.latitude + var moveLongitude = map.center.longitude + tryCompare(flickStartedSpy, "count", 1) + tryCompare(panFinishedSpy, "count", 1) + tryCompare(flickFinishedSpy, "count", 1) + verify(map.center.latitude > moveLatitude) + verify(map.center.longitude < moveLongitude) + } + + function disabled_flicking() + { + map.center.latitude = 50 + map.center.longitude = 50 + mousePress(page, 0, 0) + for (var i = 0; i < 50; i += 5) { + wait(20) + mouseMove(page, i, i, 0, Qt.LeftButton); + } + mouseRelease(page, 50, 50) + compare(panStartedSpy.count, 0) + compare(panFinishedSpy.count, 0) + compare(flickStartedSpy.count, 0) + compare(flickFinishedSpy.count, 0) + } + + function test_disabled_flicking_with_nogesture() + { + map.gesture.acceptedGestures = MapGestureArea.NoGesture + } + + function test_disabled_flicking_with_disabled() + { + map.gesture.enabled = false + disabled_flicking() + } + + function disable_onFlickStarted() + { + map.center.latitude = 50 + map.center.longitude = 50 + mousePress(page, 0, 0) + for (var i = 0; i < 50; i += 5) { + wait(20) + mouseMove(page, i, i, 0, Qt.LeftButton); + } + mouseRelease(page, 50, 50) + var latitude = map.center.latitude; + var longitude = map.center.longitude + tryCompare(panStartedSpy, "count", 1) + tryCompare(flickStartedSpy, "count", 1) + verify(map.center.latitude > 50) + tryCompare(panFinishedSpy, "count", 1) + tryCompare(flickFinishedSpy, "count", 1) + // compare that flick was interrupted (less movement than without interrupting) + compare(latitude, map.center.latitude) + compare(longitude, map.center.longitude) + } + + function test_disable_onFlickStarted_with_disabled() + { + map.disableOnFlickStartedWithDisabled = true + disable_onFlickStarted() + } + + function test_disable_onFlickStarted_with_nogesture() + { + map.disableOnFlickStartedWithNoGesture = true + disable_onFlickStarted() + } + + function disable_onPanStarted() + { + map.center.latitude = 50 + map.center.longitude = 50 + mousePress(page, 0, 0) + for (var i = 0; i < 50; i += 5) { + wait(20) + mouseMove(page, i, i, 0, Qt.LeftButton); + } + mouseRelease(page, 50, 50) + compare(map.center.latitude,50) + compare(map.center.longitude,50) + tryCompare(panFinishedSpy, "count", 1) + // compare that flick was interrupted (less movement than without interrupting) + compare(map.center.latitude,50) + compare(map.center.longitude,50) + compare(map.gesture.panActive, false) + } + + function test_disable_onPanStarted_with_disabled() + { + map.disableOnPanStartedWithDisabled = true + disable_onPanStarted() + } + + function test_disable_onPanStarted_with_nogesture() + { + map.disableOnPanStartedWithNoGesture = true + disable_onPanStarted() + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_item.qml b/tests/auto/declarative_ui/tst_map_item.qml new file mode 100644 index 0000000..8ede357 --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_item.qml @@ -0,0 +1,615 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.6 +import QtPositioning 5.5 +import QtLocation.Test 5.6 + + /* + + (0,0) ---------------------------------------------------- (240,0) + | no map | + | (20,20) | + (0,20) | ------------------------------------------ | (240,20) + | | | | + | | map | | + | | | | + | | | | + | | | | + | | (lat 20, lon 20) | | + | | x | | + | | | | + | | | | + | | | | + | | | | + | | | | + | ------------------------------------------ | + | | + (0,240) ---------------------------------------------------- (240,240) + + */ + +Item { + id: page + x: 0; y: 0; + width: 240 + height: 240 + Plugin { id: testPlugin; name : "qmlgeo.test.plugin"; allowExperimental: true } + + property variant someCoordinate1: QtPositioning.coordinate(15, 15) + property variant someCoordinate2: QtPositioning.coordinate(16, 16) + + Route { id: someRoute; + path: [ + { latitude: 22, longitude: 15 }, + { latitude: 21, longitude: 16 }, + { latitude: 23, longitude: 17 } + ] + } + Item { id: someItem } + + MapCircle { + id: extMapCircle + center { + latitude: 35 + longitude: 15 + } + color: 'firebrick' + radius: 600000 + MouseArea { + anchors.fill: parent + SignalSpy { id: extMapCircleClicked; target: parent; signalName: "clicked" } + } + } + + MapQuickItem { + id: extMapQuickItem + MouseArea { + anchors.fill: parent + SignalSpy { id: extMapQuickItemClicked; target: parent; signalName: "clicked" } + } + coordinate { + latitude: 35 + longitude: 33 + } + sourceItem: Rectangle { + color: 'darkblue' + width: 40 + height: 20 + } + } + + Map { + id: map; + x: 20; y: 20; width: 200; height: 200 + zoomLevel: 9 + plugin: testPlugin; + + MapRectangle { + id: preMapRect + MouseArea { + id: preMapRectMa + anchors.fill: parent + drag.target: parent + preventStealing: true + SignalSpy { id: preMapRectClicked; target: parent; signalName: "clicked" } + SignalSpy { id: preMapRectActiveChanged; target: parent.drag; signalName: "activeChanged" } + } + SignalSpy {id: preMapRectTopLeftChanged; target: parent; signalName: "topLeftChanged" } + SignalSpy {id: preMapRectBottomRightChanged; target: parent; signalName: "bottomRightChanged" } + SignalSpy {id: preMapRectColorChanged; target: parent; signalName: "colorChanged"} + } + MapCircle { + id: preMapCircle + MouseArea { + id: preMapCircleMa + anchors.fill: parent + drag.target: parent + preventStealing: true + SignalSpy { id: preMapCircleClicked; target: parent; signalName: "clicked" } + SignalSpy { id: preMapCircleActiveChanged; target: parent.drag; signalName: "activeChanged" } + } + SignalSpy {id: preMapCircleCenterChanged; target: parent; signalName: "centerChanged"} + SignalSpy {id: preMapCircleColorChanged; target: parent; signalName: "colorChanged"} + SignalSpy {id: preMapCircleRadiusChanged; target: parent; signalName: "radiusChanged"} + SignalSpy {id: preMapCircleBorderColorChanged; target: parent.border; signalName: "colorChanged"} + SignalSpy {id: preMapCircleBorderWidthChanged; target: parent.border; signalName: "widthChanged"} + } + MapQuickItem { + id: preMapQuickItem + MouseArea { + id: preMapQuickItemMa + anchors.fill: parent + drag.target: parent + preventStealing: true + SignalSpy { id: preMapQuickItemClicked; target: parent; signalName: "clicked" } + SignalSpy { id: preMapQuickItemActiveChanged; target: parent.drag; signalName: "activeChanged" } + } + sourceItem: Rectangle { + id: preMapQuickItemSource + color: 'darkgreen' + width: 20 + height: 20 + } + SignalSpy { id: preMapQuickItemCoordinateChanged; target: parent; signalName: "coordinateChanged"} + SignalSpy { id: preMapQuickItemAnchorPointChanged; target: parent; signalName: "anchorPointChanged"} + SignalSpy { id: preMapQuickItemZoomLevelChanged; target: parent; signalName: "zoomLevelChanged"} + SignalSpy { id: preMapQuickItemSourceItemChanged; target: parent; signalName: "sourceItemChanged"} + } + MapPolygon { + id: preMapPolygon + color: 'darkgrey' + border.width: 0 + path: [ + { latitude: 25, longitude: 5 }, + { latitude: 20, longitude: 10 }, + { latitude: 15, longitude: 6 } + ] + MouseArea { + anchors.fill: parent + drag.target: parent + SignalSpy { id: preMapPolygonClicked; target: parent; signalName: "clicked" } + } + SignalSpy {id: preMapPolygonPathChanged; target: parent; signalName: "pathChanged"} + SignalSpy {id: preMapPolygonColorChanged; target: parent; signalName: "colorChanged"} + SignalSpy {id: preMapPolygonBorderWidthChanged; target: parent.border; signalName: "widthChanged"} + SignalSpy {id: preMapPolygonBorderColorChanged; target: parent.border; signalName: "colorChanged"} + } + MapPolyline { + id: preMapPolyline + line.color: 'darkred' + path: [ + { latitude: 25, longitude: 15 }, + { latitude: 20, longitude: 19 }, + { latitude: 15, longitude: 16 } + ] + SignalSpy {id: preMapPolylineColorChanged; target: parent.line; signalName: "colorChanged"} + SignalSpy {id: preMapPolylineWidthChanged; target: parent.line; signalName: "widthChanged"} + SignalSpy {id: preMapPolylinePathChanged; target: parent; signalName: "pathChanged"} + } + MapRoute { + id: preMapRoute + line.color: 'yellow' + // don't try this at home - route is not user instantiable + route: Route { + path: [ + { latitude: 25, longitude: 14 }, + { latitude: 20, longitude: 18 }, + { latitude: 15, longitude: 15 } + ] + } + SignalSpy {id: preMapRouteRouteChanged; target: parent; signalName: "routeChanged"} + SignalSpy {id: preMapRouteLineWidthChanged; target: parent.line; signalName: "widthChanged"} + SignalSpy {id: preMapRouteLineColorChanged; target: parent.line; signalName: "colorChanged"} + } + } + TestCase { + name: "MapItems" + when: windowShown + + function initTestCase() + { + // sanity check that the coordinate conversion works, as + // rest of the case relies on it. for robustness cut + // a little slack with fuzzy compare + var mapcenter = map.fromCoordinate(map.center) + verify (fuzzy_compare(mapcenter.x, 100, 2)) + verify (fuzzy_compare(mapcenter.y, 100, 2)) + } + + function init() + { + map.center = QtPositioning.coordinate(20, 20) + preMapCircle.center = QtPositioning.coordinate(10,30) + preMapCircle.border.width = 0 + preMapCircle.color = 'red' + preMapCircle.radius = 10000 + preMapCircleClicked.clear() + preMapCircleCenterChanged.clear() + preMapCircleColorChanged.clear() + preMapCircleRadiusChanged.clear() + preMapCircleBorderColorChanged.clear() + preMapCircleBorderWidthChanged.clear() + + preMapRect.color = 'red' + preMapRect.border.width = 0 + preMapRect.topLeft = QtPositioning.coordinate(20, 20) + preMapRect.bottomRight = QtPositioning.coordinate(10, 30) + preMapRectTopLeftChanged.clear() + preMapRectBottomRightChanged.clear() + preMapRectColorChanged.clear() + preMapRectClicked.clear() + preMapRectActiveChanged.clear() + + preMapQuickItem.sourceItem = preMapQuickItemSource + preMapQuickItem.zoomLevel = 0 + preMapQuickItem.coordinate = QtPositioning.coordinate(35, 3) + preMapQuickItemClicked.clear() + preMapQuickItem.anchorPoint = Qt.point(0,0) + preMapQuickItemCoordinateChanged.clear() + preMapQuickItemAnchorPointChanged.clear() + preMapQuickItemZoomLevelChanged.clear() + preMapQuickItemSourceItemChanged.clear() + + preMapPolygonClicked.clear() + preMapPolylineColorChanged.clear() + preMapPolylineWidthChanged.clear() + preMapPolylinePathChanged.clear() + preMapPolygonPathChanged.clear() + preMapPolygonColorChanged.clear() + preMapPolygonBorderColorChanged.clear() + preMapPolygonBorderWidthChanged.clear() + preMapRouteRouteChanged.clear() + preMapRouteLineColorChanged.clear() + preMapRouteLineWidthChanged.clear() + verify(LocationTestHelper.waitForPolished(map)) + } + + function test_items_on_map() + { + // click rect + map.center = preMapRect.topLeft + verify(LocationTestHelper.waitForPolished(map)) + var point = map.fromCoordinate(preMapRect.topLeft) + mouseClick(map, point.x + 5, point.y + 5) + tryCompare(preMapRectClicked, "count", 1) + mouseClick(map, 1, 1) // no item hit + tryCompare(preMapRectClicked, "count", 1) + compare(preMapCircleClicked.count, 0) + + // click circle, overlaps and is above rect + map.center = preMapCircle.center + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(preMapCircle.center) + mouseClick(map, point.x - 5, point.y - 5) + tryCompare(preMapCircleClicked, "count", 1) + compare(preMapRectClicked.count, 1) + + // click within circle bounding rect but not inside the circle geometry + map.center = preMapCircle.center.atDistanceAndAzimuth(preMapCircle.radius, -45) + mouseClick(map, preMapCircle.x + 4, preMapCircle.y + 4) + tryCompare(preMapRectClicked, "count", 2) + compare(preMapCircleClicked.count, 1) + + // click quick item + compare(preMapQuickItemClicked.count, 0) + map.center = preMapQuickItem.coordinate + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(preMapQuickItem.coordinate) + mouseClick(map, point.x + 5, point.y + 5) + tryCompare(preMapQuickItemClicked, "count", 1) + + // click polygon + compare (preMapPolygonClicked.count, 0) + map.center = preMapPolygon.path[1] + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(preMapPolygon.path[1]) + mouseClick(map, point.x - 5, point.y) + tryCompare(preMapPolygonClicked, "count", 1) + } + + function test_no_items_on_map() + { + // remove items and repeat clicks to verify they are gone + map.clearMapItems() + compare (map.mapItems.length, 0) + map.center = preMapRect.topLeft + var point = map.fromCoordinate(preMapRect.topLeft) + mouseClick(map, point.x + 5, point.y + 5) + compare(preMapRectClicked.count, 0) + verify(LocationTestHelper.waitForPolished(map)) + map.center = preMapCircle.center + point = map.fromCoordinate(preMapCircle.center) + mouseClick(map, point.x - 5, point.y - 5) + compare(preMapRectClicked.count, 0) + compare(preMapCircleClicked.count, 0) + map.center = preMapCircle.center.atDistanceAndAzimuth(preMapCircle.radius, -45) + mouseClick(map, preMapCircle.x + 4, preMapCircle.y + 4) + compare(preMapRectClicked.count, 0) + compare(preMapCircleClicked.count, 0) + compare(preMapQuickItemClicked.count, 0) + map.center = preMapQuickItem.coordinate + point = map.fromCoordinate(preMapQuickItem.coordinate) + mouseClick(map, point.x + 5, point.y + 5) + compare(preMapQuickItemClicked.count, 0) + map.center = preMapPolygon.path[1] + point = map.fromCoordinate(preMapPolygon.path[1]) + mouseClick(map, point.x - 5, point.y) + compare(preMapPolygonClicked.count, 0) + + // re-add items and verify they are back + // note: addition order is significant + map.addMapItem(preMapRect) + map.addMapItem(preMapCircle) + map.addMapItem(preMapQuickItem) + map.addMapItem(preMapPolygon) + map.addMapItem(preMapPolyline) + map.addMapItem(preMapRoute) + compare (map.mapItems.length, 6) + + map.center = preMapRect.topLeft + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(preMapRect.topLeft) + mouseClick(map, point.x + 5, point.y + 5) + tryCompare(preMapRectClicked, "count", 1) + map.center = preMapCircle.center + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(preMapCircle.center) + mouseClick(map, point.x - 5, point.y - 5) + tryCompare(preMapRectClicked, "count", 1) + compare(preMapCircleClicked.count, 1) + map.center = preMapCircle.center.atDistanceAndAzimuth(preMapCircle.radius, -45) + verify(LocationTestHelper.waitForPolished(map)) + mouseClick(map, preMapCircle.x + 4, preMapCircle.y + 4) + tryCompare(preMapRectClicked, "count", 2) + compare(preMapCircleClicked.count, 1) + compare(preMapQuickItemClicked.count, 0) + map.center = preMapQuickItem.coordinate + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(preMapQuickItem.coordinate) + mouseClick(map, point.x + 5, point.y + 5) + tryCompare(preMapQuickItemClicked, "count", 1) + map.center = preMapPolygon.path[1] + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(preMapPolygon.path[1]) + mouseClick(map, point.x - 5, point.y) + tryCompare(preMapPolygonClicked, "count", 1) + + + // item clips to map. not sure if this is sensible test + map.addMapItem(extMapCircle) + map.center = extMapCircle.center + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(extMapCircle.center) + mouseClick(map, point.x, point.y) + tryCompare(extMapCircleClicked, "count", 1) + mouseClick(map, point.x, -5) + tryCompare(extMapCircleClicked, "count", 1) + map.removeMapItem(extMapCircle) + + map.addMapItem(extMapQuickItem) + map.center = extMapQuickItem.coordinate + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(extMapQuickItem.coordinate) + mouseClick(map, point.x + 5, point.y + 5) + tryCompare(extMapQuickItemClicked, "count", 1) + mouseClick(map, map.width + 5, point.y + 5) + tryCompare(extMapQuickItemClicked, "count", 1) + map.removeMapItem(extMapQuickItem) + } + + function test_drag() + { + // basic drags, drag rectangle + compare (preMapRectActiveChanged.count, 0) + map.center = preMapRect.topLeft + verify(LocationTestHelper.waitForPolished(map)) + var i + var point = map.fromCoordinate(preMapRect.topLeft) + var targetCoordinate = map.toCoordinate(51, 51) + mousePress(map, point.x + 5, point.y + 5) + for (i = 0; i < 50; i += 1) { + wait(1) + mouseMove(map, point.x + 5 - i, point.y + 5 - i) + } + mouseRelease(map, point.x + 5 - i, point.y + 5 - i) + compare (preMapRectActiveChanged.count, 2) + verify(preMapRectTopLeftChanged.count > 1) + verify(preMapRectBottomRightChanged.count === preMapRectTopLeftChanged.count) + verify(fuzzy_compare(preMapRect.topLeft.latitude, targetCoordinate.latitude, 0.2)) + verify(fuzzy_compare(preMapRect.topLeft.longitude, targetCoordinate.longitude, 0.2)) + var latH = preMapRect.bottomRight.latitude - preMapRect.topLeft.latitude + var lonW = preMapRect.bottomRight.longitude - preMapRect.topLeft.longitude + verify(fuzzy_compare(preMapRect.bottomRight.latitude, preMapRect.topLeft.latitude + latH, 0.1)) + verify(fuzzy_compare(preMapRect.bottomRight.longitude, preMapRect.topLeft.longitude + lonW, 0.1)) + + // drag circle + compare (preMapCircleActiveChanged.count, 0) + map.center = preMapCircle.center + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(preMapCircle.center) + targetCoordinate = map.toCoordinate(51, 51) + mousePress(map, point.x, point.y) + for (i = 0; i < 50; i += 1) { + wait(1) + mouseMove(map, point.x - i, point.y - i) + } + mouseRelease(map, point.x - i, point.y - i) + verify(LocationTestHelper.waitForPolished(map)) + compare(preMapRectActiveChanged.count, 2) + compare(preMapCircleActiveChanged.count, 2) + verify(preMapCircleCenterChanged.count > 1) + verify(fuzzy_compare(preMapCircle.center.latitude, targetCoordinate.latitude, 0.2)) + verify(fuzzy_compare(preMapCircle.center.longitude, targetCoordinate.longitude, 0.2)) + + // drag quick item + compare (preMapQuickItemActiveChanged.count, 0) + map.center = preMapQuickItem.coordinate + verify(LocationTestHelper.waitForPolished(map)) + point = map.fromCoordinate(preMapQuickItem.coordinate) + targetCoordinate = map.toCoordinate(51, 51) + mousePress(map, point.x + 5, point.y + 5) + for (i = 0; i < 50; i += 1) { + wait(1) + mouseMove(map, point.x - i, point.y - i) + } + mouseRelease(map, point.x - i, point.y - i) + verify(LocationTestHelper.waitForPolished(map)) + compare(preMapQuickItemActiveChanged.count, 2) + verify(preMapQuickItemCoordinateChanged.count > 1) + verify(fuzzy_compare(preMapQuickItem.coordinate.latitude, targetCoordinate.latitude, 0.2)) + verify(fuzzy_compare(preMapQuickItem.coordinate.longitude, targetCoordinate.longitude, 0.2)) + } + + function test_basic_items_properties() + { + // circle + preMapCircle.center = someCoordinate1 + compare (preMapCircleCenterChanged.count, 1) + preMapCircle.center = someCoordinate1 + compare (preMapCircleCenterChanged.count, 1) + preMapCircle.color = 'blue' + compare (preMapCircleColorChanged.count, 1) + preMapCircle.color = 'blue' + compare (preMapCircleColorChanged.count, 1) + preMapCircle.radius = 50 + compare (preMapCircleRadiusChanged.count, 1) + preMapCircle.radius = 50 + compare (preMapCircleRadiusChanged.count, 1) + preMapCircle.border.color = 'blue' + compare(preMapCircleBorderColorChanged.count, 1) + preMapCircle.border.color = 'blue' + compare(preMapCircleBorderColorChanged.count, 1) + preMapCircle.border.width = 5 + compare(preMapCircleBorderWidthChanged.count, 1) + preMapCircle.border.width = 5 + compare(preMapCircleBorderWidthChanged.count, 1) + + // rectangle + preMapRect.topLeft = someCoordinate1 + compare (preMapRectTopLeftChanged.count, 1) + compare (preMapRectBottomRightChanged.count, 0) + preMapRect.bottomRight = someCoordinate2 + compare (preMapRectTopLeftChanged.count, 1) + compare (preMapRectBottomRightChanged.count, 1) + preMapRect.bottomRight = someCoordinate2 + preMapRect.topLeft = someCoordinate1 + compare (preMapRectTopLeftChanged.count, 1) + compare (preMapRectBottomRightChanged.count, 1) + preMapRect.color = 'blue' + compare (preMapRectColorChanged.count, 1) + preMapRect.color = 'blue' + compare (preMapRectColorChanged.count, 1) + + // polyline + preMapPolyline.line.width = 5 + compare (preMapPolylineWidthChanged.count, 1) + preMapPolyline.line.width = 5 + compare (preMapPolylineWidthChanged.count, 1) + preMapPolyline.line.color = 'blue' + compare(preMapPolylineColorChanged.count, 1) + preMapPolyline.line.color = 'blue' + compare(preMapPolylineColorChanged.count, 1) + preMapPolyline.addCoordinate(someCoordinate1) + compare (preMapPolylinePathChanged.count, 1) + preMapPolyline.addCoordinate(someCoordinate1) + compare (preMapPolylinePathChanged.count, 2) + preMapPolyline.removeCoordinate(someCoordinate1) + compare (preMapPolylinePathChanged.count, 3) + preMapPolyline.removeCoordinate(someCoordinate1) + compare (preMapPolylinePathChanged.count, 4) + preMapPolyline.removeCoordinate(someCoordinate1) + compare (preMapPolylinePathChanged.count, 4) + + // polygon + preMapPolygon.border.width = 5 + compare (preMapPolylineWidthChanged.count, 1) + preMapPolygon.border.width = 5 + compare (preMapPolylineWidthChanged.count, 1) + preMapPolygon.border.color = 'blue' + compare(preMapPolylineColorChanged.count, 1) + preMapPolygon.border.color = 'blue' + preMapPolygon.color = 'blue' + compare (preMapPolygonColorChanged.count, 1) + preMapPolygon.color = 'blue' + compare (preMapPolygonColorChanged.count, 1) + preMapPolygon.addCoordinate(someCoordinate1) + compare (preMapPolygonPathChanged.count, 1) + preMapPolygon.addCoordinate(someCoordinate1) + compare (preMapPolygonPathChanged.count, 2) + preMapPolygon.removeCoordinate(someCoordinate1) + compare (preMapPolygonPathChanged.count, 3) + preMapPolygon.removeCoordinate(someCoordinate1) + compare (preMapPolygonPathChanged.count, 4) + preMapPolygon.removeCoordinate(someCoordinate1) + compare (preMapPolygonPathChanged.count, 4) + + // route + preMapRoute.line.width = 5 + compare (preMapRouteLineWidthChanged.count, 1) + preMapRoute.line.width = 5 + compare (preMapRouteLineWidthChanged.count, 1) + preMapRoute.line.color = 'blue' + compare (preMapRouteLineColorChanged.count, 1) + preMapRoute.line.color = 'blue' + compare (preMapRouteLineColorChanged.count, 1) + preMapRoute.route = someRoute + compare (preMapRouteRouteChanged.count, 1) + preMapRoute.route = someRoute + compare (preMapRouteRouteChanged.count, 1) + + // quick + compare (preMapQuickItemCoordinateChanged.count, 0) + preMapQuickItem.coordinate = someCoordinate1 + compare (preMapQuickItemCoordinateChanged.count, 1) + preMapQuickItem.coordinate = someCoordinate1 + compare (preMapQuickItemCoordinateChanged.count, 1) + preMapQuickItem.anchorPoint = Qt.point(39, 3) + compare (preMapQuickItemAnchorPointChanged.count, 1) + preMapQuickItem.anchorPoint = Qt.point(39, 3) + compare (preMapQuickItemAnchorPointChanged.count, 1) + preMapQuickItem.zoomLevel = 6 + compare (preMapQuickItemZoomLevelChanged.count, 1) + preMapQuickItem.zoomLevel = 6 + compare (preMapQuickItemZoomLevelChanged.count, 1) + preMapQuickItem.sourceItem = someItem + compare (preMapQuickItemSourceItemChanged.count, 1) + preMapQuickItem.sourceItem = someItem + compare (preMapQuickItemSourceItemChanged.count, 1) + } + + function fuzzy_compare(val, ref, tol) { + var tolerance = 2 + if (tol !== undefined) + tolerance = tol + if ((val >= ref - tolerance) && (val <= ref + tolerance)) + return true; + console.log('map fuzzy cmp returns false for value, ref, tolerance: ' + val + ', ' + ref + ', ' + tolerance) + return false; + } + + // these 'real_' prefixed functions do sequences as + // it would occur on real app (e.g. doubleclick is in fact + // a sequence of press, release, doubleclick, release). + // (they were recorded as seen on test app). mouseClick() works ok + // because testlib internally converts it to mousePress + mouseRelease events + function real_double_click (target, x, y) { + mousePress(target, x,y) + mouseRelease(target, x, y) + mouseDoubleClick(target, x, y) + mouseRelease(target, x, y) + } + function real_press_and_hold(target, x,y) { + mousePress(target,x,y) + wait(850) // threshold is 800 ms + mouseRelease(target,x, y) + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_item_details.qml b/tests/auto/declarative_ui/tst_map_item_details.qml new file mode 100644 index 0000000..ebaf1ea --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_item_details.qml @@ -0,0 +1,613 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtPositioning 5.5 +import QtLocation 5.6 +import QtLocation.Test 5.6 + +Item { + id: page + x: 0; y: 0; + width: 240 + height: 240 + Plugin { id: testPlugin + name : "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ PluginParameter { name: "finishRequestImmediately"; value: true}] + } + + property variant mapDefaultCenter: QtPositioning.coordinate(20, 20) + + property variant datelineCoordinate: QtPositioning.coordinate(20, 180) + property variant datelineCoordinateLeft: QtPositioning.coordinate(20, 170) + property variant datelineCoordinateRight: QtPositioning.coordinate(20, -170) + + MapPolygon { + id: extMapPolygon + color: 'darkgrey' + path: [ + { latitude: 25, longitude: 5 }, + { latitude: 20, longitude: 10 } + ] + MouseArea { + anchors.fill: parent + drag.target: parent + SignalSpy { id: extMapPolygonClicked; target: parent; signalName: "clicked" } + } + SignalSpy {id: extMapPolygonPathChanged; target: parent; signalName: "pathChanged"} + SignalSpy {id: extMapPolygonColorChanged; target: parent; signalName: "colorChanged"} + SignalSpy {id: extMapPolygonBorderWidthChanged; target: parent.border; signalName: "widthChanged"} + SignalSpy {id: extMapPolygonBorderColorChanged; target: parent.border; signalName: "colorChanged"} + } + + property variant polyCoordinate: QtPositioning.coordinate(15, 6) + + MapPolygon { + id: extMapPolygon0 + color: 'darkgrey' + } + + MapPolyline { + id: extMapPolyline0 + } + + MapPolyline { + id: extMapPolyline + path: [ + { latitude: 25, longitude: 5 }, + { latitude: 20, longitude: 10 } + ] + SignalSpy {id: extMapPolylineColorChanged; target: parent.line; signalName: "colorChanged"} + SignalSpy {id: extMapPolylineWidthChanged; target: parent.line; signalName: "widthChanged"} + SignalSpy {id: extMapPolylinePathChanged; target: parent; signalName: "pathChanged"} + } + + MapRectangle { + id: extMapRectDateline + color: 'darkcyan' + topLeft { + latitude: 20 + longitude: 175 + } + bottomRight { + latitude: 10 + longitude: -175 + } + MouseArea { + anchors.fill: parent + drag.target: parent + preventStealing: true + } + } + + MapCircle { + id: extMapCircleDateline + color: 'darkmagenta' + center { + latitude: 20 + longitude: 180 + } + radius: 400000 + MouseArea { + anchors.fill: parent + drag.target: parent + preventStealing: true + } + } + + MapQuickItem { + id: extMapQuickItemDateline + MouseArea { + anchors.fill: parent + drag.target: parent + preventStealing: true + } + coordinate { + latitude: 20 + longitude: 175 + } + sourceItem: Rectangle { + color: 'darkgreen' + width: 20 + height: 20 + } + } + + MapPolygon { + id: extMapPolygonDateline + color: 'darkmagenta' + path: [ + { latitude: 20, longitude: 175 }, + { latitude: 20, longitude: -175 }, + { latitude: 10, longitude: -175 }, + { latitude: 10, longitude: 175 } + ] + MouseArea { + anchors.fill: parent + drag.target: parent + preventStealing: true + } + } + + MapPolyline { + id: extMapPolylineDateline + line.width : 3 + path: [ + { latitude: 20, longitude: 175 }, + { latitude: 25, longitude: -175 } + ] + MouseArea { + anchors.fill: parent + drag.target: parent + } + } + + MapRoute { + id: extMapRouteDateline + line.color: 'yellow' + route: Route { + path: [ + { latitude: 25, longitude: 175 }, + { latitude: 20, longitude: -175 } + ] + } + } + + MapRectangle { + id: extMapRectEdge + color: 'darkcyan' + topLeft { + latitude: 20 + longitude: -15 + } + bottomRight { + latitude: 10 + longitude: -5 + } + MouseArea { + anchors.fill: parent + drag.target: parent + } + } + + MapCircle { + id: extMapCircleEdge + color: 'darkmagenta' + center { + latitude: 20 + longitude: -15 + } + radius: 400000 + MouseArea { + anchors.fill: parent + drag.target: parent + } + } + + MapQuickItem { + id: extMapQuickItemEdge + MouseArea { + anchors.fill: parent + drag.target: parent + } + coordinate { + latitude: 20 + longitude: -15 + } + sourceItem: Rectangle { + color: 'darkgreen' + width: 20 + height: 20 + } + } + + MapPolygon { + id: extMapPolygonEdge + color: 'darkmagenta' + path: [ + { latitude: 20, longitude: -15 }, + { latitude: 20, longitude: -5 }, + { latitude: 10, longitude: -5 }, + { latitude: 10, longitude: -15 } + ] + MouseArea { + anchors.fill: parent + drag.target: parent + } + } + + MapPolyline { + id: extMapPolylineEdge + line.width : 3 + path: [ + { latitude: 20, longitude: -15 }, + { latitude: 25, longitude: -5 } + ] + MouseArea { + anchors.fill: parent + drag.target: parent + } + } + + MapRoute { + id: extMapRouteEdge + line.color: 'yellow' + route: Route { + path: [ + { latitude: 25, longitude: -15 }, + { latitude: 20, longitude: -5 } + ] + } + } + + Map { + id: map; + x: 20; y: 20; width: 200; height: 200 + center: mapDefaultCenter + plugin: testPlugin; + } + + Text {id: progressText} + + TestCase { + name: "MapItemDetails" + when: windowShown + + /* + + (0,0) ---------------------------------------------------- (240,0) + | no map | + | (20,20) | + (0,20) | ------------------------------------------ | (240,20) + | | | | + | | map | | + | | | | + | | | | + | | | | + | | (lat 20, lon 20) | | + | | x | | + | | | | + | | | | + | | | | + | | | | + | | | | + | ------------------------------------------ | + | | + (0,240) ---------------------------------------------------- (240,240) + + */ + function initTestCase() + { + // sanity check that the coordinate conversion works + var mapcenter = map.fromCoordinate(map.center) + verify (fuzzy_compare(mapcenter.x, 100, 2)) + verify (fuzzy_compare(mapcenter.y, 100, 2)) + } + + function init() + { + map.clearMapItems() + map.zoomLevel = 3 + extMapPolygon.border.width = 1.0 + extMapPolygonClicked.clear() + extMapPolylineColorChanged.clear() + extMapPolylineWidthChanged.clear() + extMapPolylinePathChanged.clear() + extMapPolygonPathChanged.clear() + extMapPolygonColorChanged.clear() + extMapPolygonBorderColorChanged.clear() + extMapPolygonBorderWidthChanged.clear() + } + + function test_polygon() + { + map.center = extMapPolygon.path[1] + var point = map.fromCoordinate(extMapPolygon.path[1]) + map.addMapItem(extMapPolygon) + verify(LocationTestHelper.waitForPolished(map)) + verify(extMapPolygon.path.length == 2) + mouseClick(map, point.x - 5, point.y) + compare(extMapPolygonClicked.count, 0) + map.addMapItem(extMapPolygon0) // mustn't crash or ill-behave + verify(extMapPolygon0.path.length == 0) + extMapPolygon.addCoordinate(polyCoordinate) + verify(extMapPolygon.path.length == 3) + verify(LocationTestHelper.waitForPolished(map)) + mouseClick(map, point.x - 5, point.y) + tryCompare(extMapPolygonClicked, "count", 1) + + extMapPolygon.path[0].latitude = 10 + verify(extMapPolygon.path[0].latitude, 10) + extMapPolygon.path[0].latitude = polyCoordinate.latitude + verify(extMapPolygon.path[0].latitude, 15) + extMapPolygon.path[0].longitude = 2 + verify(extMapPolygon.path[0].longitude, 2) + extMapPolygon.path[0].longitude = polyCoordinate.longitude + verify(extMapPolygon.path[0].longitude, 6) + + extMapPolygon.removeCoordinate(polyCoordinate) + verify(extMapPolygon.path.length == 2) + extMapPolygon.removeCoordinate(extMapPolygon.path[1]) + verify(extMapPolygon.path.length == 1) + extMapPolygon.removeCoordinate(extMapPolygon.path[0]) + verify(extMapPolygon.path.length == 0) + } + + function test_polyline() + { + compare (extMapPolyline.line.width, 1.0) + var point = map.fromCoordinate(extMapPolyline.path[1]) + map.addMapItem(extMapPolyline0) // mustn't crash or ill-behave + verify(extMapPolyline0.path.length == 0) + map.addMapItem(extMapPolyline) + verify(extMapPolyline.path.length == 2) + extMapPolyline.addCoordinate(polyCoordinate) + verify(extMapPolyline.path.length == 3) + extMapPolyline.addCoordinate(extMapPolyline.path[0]) + verify(extMapPolyline.path.length == 4) + + extMapPolyline.path[0].latitude = 10 + verify(extMapPolyline.path[0].latitude, 10) + extMapPolyline.path[0].latitude = polyCoordinate.latitude + verify(extMapPolyline.path[0].latitude, 15) + extMapPolyline.path[0].longitude = 2 + verify(extMapPolyline.path[0].longitude, 2) + extMapPolyline.path[0].longitude = polyCoordinate.longitude + verify(extMapPolyline.path[0].longitude, 6) + + // TODO when line rendering is ready + //mouseClick(map, point.x - 5, point.y) + //compare(extMapPolylineClicked.count, 1) + extMapPolyline.removeCoordinate(extMapPolyline.path[0]) + verify(extMapPolyline.path.length == 3) + extMapPolyline.removeCoordinate(polyCoordinate) + verify(extMapPolyline.path.length == 2) + extMapPolyline.removeCoordinate(extMapPolyline.path[1]) + verify(extMapPolyline.path.length == 1) + extMapPolyline.removeCoordinate(extMapPolyline.path[0]) + verify(extMapPolyline.path.length == 0) + } + + /* + + (0,0) ---------------------------------------------------- (600,0) + | no map | + | (20,20) | + (0,20) | ------------------------------------------ | (600,20) + | | | | + | | map | | + | | | | + | | | | + | | | | + | | (lat 20, lon 180) | | + | | x | | + | | | | + | | | | + | | | | + | | | | + | | | | + | ------------------------------------------ | + | | + (0,240) ---------------------------------------------------- (600,240) + + */ + function test_dateline() { + map.center = datelineCoordinate + map.zoomLevel = 2.2 + var inspectionTime = 0 // change this to inspect the behavior. + + // rectangle + // item spanning across dateline + map.addMapItem(extMapRectDateline) + verify(extMapRectDateline.topLeft.longitude == 175) + verify(extMapRectDateline.bottomRight.longitude == -175) + var point = map.fromCoordinate(extMapRectDateline.topLeft) + verify(point.x < map.width / 2.0) + point = map.fromCoordinate(extMapRectDateline.bottomRight) + verify(point.x > map.width / 2.0) + // move item away from dataline by directly setting its coords + extMapRectDateline.bottomRight.longitude = datelineCoordinateRight.longitude + point = map.fromCoordinate(extMapRectDateline.bottomRight) + verify(point.x > map.width / 2.0) + // move item edge onto dateline + extMapRectDateline.topLeft.longitude = datelineCoordinate.longitude + point = map.fromCoordinate(extMapRectDateline.topLeft) + verify(point.x == map.width / 2.0) + // drag item back onto dateline + verify(LocationTestHelper.waitForPolished(map)) + visualInspectionPoint(inspectionTime) + mousePress(map, point.x + 5, point.y + 5) + var i + for (i=0; i < 20; i += 2) { + wait(1) + mouseMove(map, point.x + 5 - i, point.y + 5 ) + } + mouseRelease(map, point.x + 5 - i, point.y + 5) + verify(LocationTestHelper.waitForPolished(map)) + visualInspectionPoint(inspectionTime) + point = map.fromCoordinate(extMapRectDateline.topLeft) + verify(point.x < map.width / 2.0) + point = map.fromCoordinate(extMapRectDateline.bottomRight) + verify(point.x > map.width / 2.0) + map.removeMapItem(extMapRectDateline) + + // circle + map.addMapItem(extMapCircleDateline) + verify(extMapCircleDateline.center.longitude === 180) + map.center = datelineCoordinate + point = map.fromCoordinate(extMapCircleDateline.center) + verify(point.x == map.width / 2.0) // center of the screen + visualInspectionPoint() + extMapCircleDateline.center.longitude = datelineCoordinateRight.longitude // -170, moving the circle to the right + point = map.fromCoordinate(extMapCircleDateline.center) + verify(LocationTestHelper.waitForPolished(map)) + verify(point.x > map.width / 2.0) + visualInspectionPoint(inspectionTime) + mousePress(map, point.x, point.y) + for (i=0; i < 50; i += 4) { + wait(1) + mouseMove(map, point.x - i, point.y) + } + mouseRelease(map, point.x - i, point.y) + verify(LocationTestHelper.waitForPolished(map)) + visualInspectionPoint(inspectionTime) + point = map.fromCoordinate(extMapCircleDateline.center) + visualInspectionPoint() + verify(point.x < map.width / 2.0) + map.removeMapItem(extMapCircleDateline) + + // quickitem + map.addMapItem(extMapQuickItemDateline) + map.center = datelineCoordinate + verify(extMapQuickItemDateline.coordinate.longitude === 175) + point = map.fromCoordinate(extMapQuickItemDateline.coordinate) + verify(point.x < map.width / 2.0) + visualInspectionPoint() + extMapQuickItemDateline.coordinate.longitude = datelineCoordinateRight.longitude + point = map.fromCoordinate(extMapQuickItemDateline.coordinate) + verify(point.x > map.width / 2.0) + verify(LocationTestHelper.waitForPolished(map)) + visualInspectionPoint(inspectionTime) + mousePress(map, point.x + 5, point.y + 5) + for (i=0; i < 64; i += 5) { + wait(1) + mouseMove(map, point.x + 5 - i, point.y + 5 ) + } + mouseRelease(map, point.x + 5 - i, point.y + 5) + verify(LocationTestHelper.waitForPolished(map)) + visualInspectionPoint(inspectionTime) + point = map.fromCoordinate(extMapQuickItemDateline.coordinate) + visualInspectionPoint() + verify(point.x < map.width / 2.0) + map.removeMapItem(extMapQuickItemDateline) + + // polygon + map.addMapItem(extMapPolygonDateline) + map.center = datelineCoordinate + verify(extMapPolygonDateline.path[0].longitude == 175) + verify(extMapPolygonDateline.path[1].longitude == -175) + verify(extMapPolygonDateline.path[2].longitude == -175) + verify(extMapPolygonDateline.path[3].longitude == 175) + point = map.fromCoordinate(extMapPolygonDateline.path[0]) + verify(point.x < map.width / 2.0) + point = map.fromCoordinate(extMapPolygonDateline.path[1]) + verify(point.x > map.width / 2.0) + point = map.fromCoordinate(extMapPolygonDateline.path[2]) + verify(point.x > map.width / 2.0) + point = map.fromCoordinate(extMapPolygonDateline.path[3]) + verify(point.x < map.width / 2.0) + extMapPolygonDateline.path[1].longitude = datelineCoordinateRight.longitude + point = map.fromCoordinate(extMapPolygonDateline.path[1]) + verify(point.x > map.width / 2.0) + extMapPolygonDateline.path[2].longitude = datelineCoordinateRight.longitude + point = map.fromCoordinate(extMapPolygonDateline.path[2]) + verify(point.x > map.width / 2.0) + var path = extMapPolygonDateline.path; + path[0].longitude = datelineCoordinate.longitude; + extMapPolygonDateline.path = path; + point = map.fromCoordinate(extMapPolygonDateline.path[0]) + verify(point.x == map.width / 2.0) + path = extMapPolygonDateline.path; + path[3].longitude = datelineCoordinate.longitude; + extMapPolygonDateline.path = path; + point = map.fromCoordinate(extMapPolygonDateline.path[3]) + verify(point.x == map.width / 2.0) + verify(LocationTestHelper.waitForPolished(map)) + visualInspectionPoint(inspectionTime) + mousePress(map, point.x + 5, point.y - 5) + for (i=0; i < 16; i += 2) { + wait(1) + mouseMove(map, point.x + 5 - i, point.y - 5 ) + } + mouseRelease(map, point.x + 5 - i, point.y - 5) + verify(LocationTestHelper.waitForPolished(map)) + visualInspectionPoint(inspectionTime) + point = map.fromCoordinate(extMapPolygonDateline.path[0]) + verify(point.x < map.width / 2.0) + point = map.fromCoordinate(extMapPolygonDateline.path[1]) + verify(point.x > map.width / 2.0) + point = map.fromCoordinate(extMapPolygonDateline.path[2]) + verify(point.x > map.width / 2.0) + point = map.fromCoordinate(extMapPolygonDateline.path[3]) + verify(point.x < map.width / 2.0) + map.removeMapItem(extMapPolygonDateline) + + // polyline + map.addMapItem(extMapPolylineDateline) + map.center = datelineCoordinate + verify(extMapPolylineDateline.path[0].longitude == 175) + verify(extMapPolylineDateline.path[1].longitude == -175) + point = map.fromCoordinate(extMapPolylineDateline.path[0]) + verify(point.x < map.width / 2.0) + point = map.fromCoordinate(extMapPolylineDateline.path[1]) + verify(point.x > map.width / 2.0) + extMapPolylineDateline.path[1].longitude = datelineCoordinateRight.longitude + point = map.fromCoordinate(extMapPolylineDateline.path[1]) + verify(point.x > map.width / 2.0) + var path = extMapPolygonDateline.path; + path[0].longitude = datelineCoordinate.longitude; + extMapPolylineDateline.path = path; + point = map.fromCoordinate(extMapPolylineDateline.path[0]) + verify(point.x == map.width / 2.0) + map.removeMapItem(extMapPolylineDateline) + + // map route + // (does not support setting of path coords) + map.addMapItem(extMapRouteDateline) + verify(extMapRouteDateline.route.path[0].longitude == 175) + verify(extMapRouteDateline.route.path[1].longitude == -175) + point = map.fromCoordinate(extMapRouteDateline.route.path[0]) + verify(point.x < map.width / 2.0) + point = map.fromCoordinate(extMapRouteDateline.route.path[1]) + verify(point.x > map.width / 2.0) + map.removeMapItem(extMapRouteDateline) + } + + function fuzzy_compare(val, ref, tol) { + var tolerance = 2 + if (tol !== undefined) + tolerance = tol + if ((val >= ref - tolerance) && (val <= ref + tolerance)) + return true; + console.log('map fuzzy cmp returns false for value, ref, tolerance: ' + val + ', ' + ref + ', ' + tolerance) + return false; + } + // call to visualInspectionPoint testcase (for dev time visual inspection) + function visualInspectionPoint(time) { + var waitTime = 0 // 300 + if (time !== undefined) + waitTime = time + if (waitTime > 0) { + console.log('halting for ' + waitTime + ' milliseconds') + wait (waitTime) + } + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_item_fit_viewport.qml b/tests/auto/declarative_ui/tst_map_item_fit_viewport.qml new file mode 100644 index 0000000..fe4d9e4 --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_item_fit_viewport.qml @@ -0,0 +1,690 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.6 +import QtPositioning 5.5 +import QtLocation.Test 5.5 + + /* + + (0,0) ---------------------------------------------------- (296,0) + | no map | + | (20,20) | + (0,20) | ------------------------------------------ | (296,20) + | | | | + | | map | | + | | | | + | | | | + | | | | + | | (lat 20, lon 20) | | + | | x | | + | | | | + | | | | + | | | | + | | | | + | | | | + | ------------------------------------------ | + | | + (0,296) ---------------------------------------------------- (296,296) + + */ + +Item { + id: page + x: 0; y: 0; + width: 296 + height: 296 + Plugin { id: testPlugin; name : "qmlgeo.test.plugin"; allowExperimental: true } + + property variant mapDefaultCenter: QtPositioning.coordinate(20, 20) + property variant preMapRectangleDefaultTopLeft: QtPositioning.coordinate(20, 20) + property variant preMapRectangleDefaultBottomRight: QtPositioning.coordinate(10, 30) + property variant preMapCircleDefaultCenter: QtPositioning.coordinate(10, 30) + property variant preMapQuickItemDefaultCoordinate: QtPositioning.coordinate(35, 3) + + property variant preMapPolygonDefaultPath: [ + { latitude: 25, longitude: 5 }, + { latitude: 20, longitude: 10 }, + { latitude: 15, longitude: 6 } + ] + + property variant preMapPolylineDefaultPath: [ + { latitude: 25, longitude: 15 }, + { latitude: 20, longitude: 19 }, + { latitude: 15, longitude: 16 } + ] + + property variant preMapRouteDefaultPath: [ + { latitude: 25, longitude: 14 }, + { latitude: 20, longitude: 18 }, + { latitude: 15, longitude: 15 } + ] + + property variant mapCircleTopLeft: QtPositioning.coordinate(0, 0) + property variant mapCircleBottomRight: QtPositioning.coordinate(0, 0) + property variant mapQuickItemTopLeft: QtPositioning.coordinate(0, 0) + property variant mapQuickItemBottomRight: QtPositioning.coordinate(0, 0) + property variant mapPolygonTopLeft: QtPositioning.coordinate(0, 0) + property variant mapPolygonBottomRight: QtPositioning.coordinate(0, 0) + property variant mapPolylineTopLeft: QtPositioning.coordinate(0, 0) + property variant mapPolylineBottomRight: QtPositioning.coordinate(0, 0) + property variant mapRouteTopLeft: QtPositioning.coordinate(0, 0) + property variant mapRouteBottomRight: QtPositioning.coordinate(0, 0) + property variant fitRect: QtPositioning.rectangle(QtPositioning.coordinate(80, 80), + QtPositioning.coordinate(78, 82)) + property variant fitEmptyRect: QtPositioning.rectangle(QtPositioning.coordinate(79, 79),-1, -1) + property variant fitCircle: QtPositioning.circle(QtPositioning.coordinate(-50, -100), 1500) + property variant fitInvalidShape: QtPositioning.shape() + + property variant fitCircleTopLeft: QtPositioning.coordinate(0, 0) + property variant fitCircleBottomRight: QtPositioning.coordinate(0, 0) + + Map { + id: map; + x: 20; y: 20; width: 256; height: 256 + zoomLevel: 3 + center: mapDefaultCenter + plugin: testPlugin; + + MapRectangle { + id: preMapRect + color: 'darkcyan' + border.width: 0 + topLeft: preMapRectangleDefaultTopLeft + bottomRight: preMapRectangleDefaultBottomRight + MouseArea { + id: preMapRectMa + anchors.fill: parent + drag.target: parent + SignalSpy { id: preMapRectClicked; target: parent; signalName: "clicked" } + SignalSpy { id: preMapRectActiveChanged; target: parent.drag; signalName: "activeChanged" } + } + SignalSpy {id: preMapRectTopLeftChanged; target: parent; signalName: "topLeftChanged" } + SignalSpy {id: preMapRectBottomRightChanged; target: parent; signalName: "bottomRightChanged" } + SignalSpy {id: preMapRectColorChanged; target: parent; signalName: "colorChanged"} + } + MapCircle { + id: preMapCircle + color: 'darkmagenta' + border.width: 0 + center: preMapCircleDefaultCenter + radius: 400000 + MouseArea { + anchors.fill: parent + drag.target: parent + SignalSpy { id: preMapCircleClicked; target: parent; signalName: "clicked" } + SignalSpy { id: preMapCircleActiveChanged; target: parent.drag; signalName: "activeChanged" } + } + SignalSpy {id: preMapCircleCenterChanged; target: parent; signalName: "centerChanged"} + SignalSpy {id: preMapCircleColorChanged; target: parent; signalName: "colorChanged"} + SignalSpy {id: preMapCircleRadiusChanged; target: parent; signalName: "radiusChanged"} + SignalSpy {id: preMapCircleBorderColorChanged; target: parent.border; signalName: "colorChanged"} + SignalSpy {id: preMapCircleBorderWidthChanged; target: parent.border; signalName: "widthChanged"} + } + MapQuickItem { + id: preMapQuickItem + MouseArea { + anchors.fill: parent + drag.target: parent + SignalSpy { id: preMapQuickItemClicked; target: parent; signalName: "clicked" } + SignalSpy { id: preMapQuickItemActiveChanged; target: parent.drag; signalName: "activeChanged" } + } + coordinate: preMapQuickItemDefaultCoordinate + sourceItem: Rectangle { + color: 'darkgreen' + width: 20 + height: 20 + } + SignalSpy { id: preMapQuickItemCoordinateChanged; target: parent; signalName: "coordinateChanged"} + SignalSpy { id: preMapQuickItemAnchorPointChanged; target: parent; signalName: "anchorPointChanged"} + SignalSpy { id: preMapQuickItemZoomLevelChanged; target: parent; signalName: "zoomLevelChanged"} + SignalSpy { id: preMapQuickItemSourceItemChanged; target: parent; signalName: "sourceItemChanged"} + } + MapPolygon { + id: preMapPolygon + color: 'darkgrey' + border.width: 0 + path: [ + { latitude: 25, longitude: 5 }, + { latitude: 20, longitude: 10 }, + { latitude: 15, longitude: 6 } + ] + MouseArea { + anchors.fill: parent + drag.target: parent + SignalSpy { id: preMapPolygonClicked; target: parent; signalName: "clicked" } + } + SignalSpy {id: preMapPolygonPathChanged; target: parent; signalName: "pathChanged"} + SignalSpy {id: preMapPolygonColorChanged; target: parent; signalName: "colorChanged"} + SignalSpy {id: preMapPolygonBorderWidthChanged; target: parent.border; signalName: "widthChanged"} + SignalSpy {id: preMapPolygonBorderColorChanged; target: parent.border; signalName: "colorChanged"} + } + MapPolyline { + id: preMapPolyline + line.color: 'darkred' + path: [ + { latitude: 25, longitude: 15 }, + { latitude: 20, longitude: 19 }, + { latitude: 15, longitude: 16 } + ] + SignalSpy {id: preMapPolylineColorChanged; target: parent.line; signalName: "colorChanged"} + SignalSpy {id: preMapPolylineWidthChanged; target: parent.line; signalName: "widthChanged"} + SignalSpy {id: preMapPolylinePathChanged; target: parent; signalName: "pathChanged"} + } + MapRoute { + id: preMapRoute + line.color: 'yellow' + // don't try this at home - route is not user instantiable + route: Route { + path: [ + { latitude: 25, longitude: 14 }, + { latitude: 20, longitude: 18 }, + { latitude: 15, longitude: 15 } + ] + } + SignalSpy {id: preMapRouteRouteChanged; target: parent; signalName: "routeChanged"} + SignalSpy {id: preMapRouteLineWidthChanged; target: parent.line; signalName: "widthChanged"} + SignalSpy {id: preMapRouteLineColorChanged; target: parent.line; signalName: "colorChanged"} + } + } + + TestCase { + name: "MapItemsFitViewport" + when: windowShown + + function initTestCase() + { + // sanity check that the coordinate conversion works + var mapcenter = map.fromCoordinate(map.center) + verify (fuzzy_compare(mapcenter.x, 128, 2)) + verify (fuzzy_compare(mapcenter.y, 128, 2)) + } + + function init() + { + preMapRect.topLeft.latitude = 20 + preMapRect.topLeft.longitude = 20 + preMapRect.bottomRight.latitude = 10 + preMapRect.bottomRight.longitude = 30 + preMapCircle.center.latitude = 10 + preMapCircle.center.longitude = 30 + preMapQuickItem.coordinate.latitude = 35 + preMapQuickItem.coordinate.longitude = 3 + var i + for (i = 0; i < preMapPolygon.path.length; ++i) { + preMapPolygon.path[i].latitude = preMapPolygonDefaultPath[i].latitude + preMapPolygon.path[i].longitude = preMapPolygonDefaultPath[i].longitude + } + for (i = 0; i < preMapPolyline.path.length; ++i) { + preMapPolyline.path[i].latitude = preMapPolylineDefaultPath[i].latitude + preMapPolyline.path[i].longitude = preMapPolylineDefaultPath[i].longitude + } + for (i = 0; i < preMapRoute.route.path.length; ++i) { + preMapRoute.route.path[i].latitude = preMapRouteDefaultPath[i].latitude + preMapRoute.route.path[i].longitude = preMapRouteDefaultPath[i].longitude + } + // remove items + map.clearMapItems() + //clear_data() + compare (map.mapItems.length, 0) + // reset map + map.center.latitude = 20 + map.center.longitude = 20 + map.zoomLevel = 3 + // re-add items and verify they are back (without needing to pan map etc.) + map.addMapItem(preMapRect) + map.addMapItem(preMapCircle) + map.addMapItem(preMapQuickItem) + map.addMapItem(preMapPolygon) + map.addMapItem(preMapPolyline) + map.addMapItem(preMapRoute) + compare (map.mapItems.length, 6) + calculate_bounds() + } + + function test_visible_itmes() + { + // normal case - fit viewport to items which are all already visible + verify_visibility_all_items() + map.fitViewportToMapItems() + visualInspectionPoint() + verify_visibility_all_items() + } + + function test_visible_zoom_in() + { + // zoom in (clipping also occurs) + var z = map.zoomLevel + for (var i = (z + 1); i < map.maximumZoomLevel; ++i ) { + map.zoomLevel = i + visualInspectionPoint() + map.fitViewportToMapItems() + visualInspectionPoint() + verify_visibility_all_items() + } + } + + function test_visible_zoom_out() + { + // zoom out + for (var i = (z - 1); i >= 0; --i ) { + map.zoomLevel = i + visualInspectionPoint() + verify_visibility_all_items() + map.fitViewportToMapItems() + visualInspectionPoint() + verify_visibility_all_items() + } + } + + function test_visible_map_move() { + // move map so all items are out of screen + // then fit viewport + var xDir = 1 + var yDir = 0 + var xDirChange = -1 + var yDirChange = 1 + var dir = 0 + for (dir = 0; dir < 4; dir++) { + verify_visibility_all_items() + var i = 0 + var panX = map.width * xDir * 0.5 + var panY = map.height * yDir * 0.5 + map.pan(panX, panY) + map.pan(panX, panY) + visualInspectionPoint() + // check all items are indeed not within screen bounds + calculate_bounds() + verify(!is_coord_on_screen(preMapRect.topLeft)) + verify(!is_coord_on_screen(preMapRect.bottomRight)) + verify(!is_coord_on_screen(mapCircleTopLeft)) + verify(!is_coord_on_screen(mapCircleBottomRight)) + verify(!is_coord_on_screen(mapPolygonTopLeft)) + verify(!is_coord_on_screen(mapPolygonBottomRight)) + verify(!is_coord_on_screen(mapQuickItemTopLeft)) + verify(!is_coord_on_screen(mapQuickItemBottomRight)) + verify(!is_coord_on_screen(mapPolylineTopLeft)) + verify(!is_coord_on_screen(mapPolylineBottomRight)) + verify(!is_coord_on_screen(mapRouteTopLeft)) + verify(!is_coord_on_screen(mapRouteBottomRight)) + // fit viewport and verify that all items are visible again + map.fitViewportToMapItems() + visualInspectionPoint() + verify_visibility_all_items() + if (dir == 2) + xDirChange *= -1 + if (dir == 1) + yDirChange *= -1 + xDir += xDirChange + yDir += yDirChange + } + } + + function test_fit_to_geoshape() { + visualInspectionPoint() + calculate_fit_circle_bounds() + //None should be visible + verify(!is_coord_on_screen(fitCircleTopLeft)) + verify(!is_coord_on_screen(fitCircleBottomRight)) + verify(!is_coord_on_screen(fitRect.topLeft)) + verify(!is_coord_on_screen(fitRect.bottomRight)) + + map.visibleRegion = fitRect + visualInspectionPoint() + calculate_fit_circle_bounds() + //Rectangle should be visible, not circle + verify(!is_coord_on_screen(fitCircleTopLeft)) + verify(!is_coord_on_screen(fitCircleBottomRight)) + verify(is_coord_on_screen(fitRect.topLeft)) + verify(is_coord_on_screen(fitRect.bottomRight)) + + map.visibleRegion = fitCircle + visualInspectionPoint() + calculate_fit_circle_bounds() + //Circle should be visible, not rectangle + verify(is_coord_on_screen(fitCircleTopLeft)) + verify(is_coord_on_screen(fitCircleBottomRight)) + verify(!is_coord_on_screen(fitRect.topLeft)) + verify(!is_coord_on_screen(fitRect.bottomRight)) + + map.visibleRegion = fitInvalidShape + visualInspectionPoint() + calculate_fit_circle_bounds() + //Invalid shape, map should be in the same position as before + verify(is_coord_on_screen(fitCircleTopLeft)) + verify(is_coord_on_screen(fitCircleBottomRight)) + verify(!is_coord_on_screen(fitRect.topLeft)) + verify(!is_coord_on_screen(fitRect.bottomRight)) + + map.visibleRegion = fitEmptyRect + visualInspectionPoint() + calculate_fit_circle_bounds() + //Empty shape, map should change centerlocation, empty rect visible + verify(!is_coord_on_screen(fitCircleTopLeft)) + verify(!is_coord_on_screen(fitCircleBottomRight)) + verify(is_coord_on_screen(fitEmptyRect.topLeft)) + verify(is_coord_on_screen(fitEmptyRect.bottomRight)) + + // Test if this can be reset + map.visibleRegion = fitRect + verify(is_coord_on_screen(fitRect.topLeft)) + verify(is_coord_on_screen(fitRect.bottomRight)) + // move map + map.center = QtPositioning.coordinate(0,0) + verify(!is_coord_on_screen(fitRect.topLeft)) + verify(!is_coord_on_screen(fitRect.bottomRight)) + // recheck + map.visibleRegion = fitRect + verify(is_coord_on_screen(fitRect.topLeft)) + verify(is_coord_on_screen(fitRect.bottomRight)) + //zoom map + map.zoomLevel++ + verify(!is_coord_on_screen(fitRect.topLeft)) + verify(!is_coord_on_screen(fitRect.bottomRight)) + // recheck + map.visibleRegion = fitRect + verify(is_coord_on_screen(fitRect.topLeft)) + verify(is_coord_on_screen(fitRect.bottomRight)) + } + + // checks that circles belongs to the view port + function circle_in_viewport(center, radius, visible) + { + for (var i = 0; i < 128; ++i) { + var azimuth = 360.0 * i / 128.0; + var coord = center.atDistanceAndAzimuth(radius,azimuth) + if (coord.isValid) + verify(is_coord_on_screen(coord) === visible, visible ? + "circle not visible" : "circle visible") + } + } + + function test_fit_circle_to_viewport(data) + { + verify(!is_coord_on_screen(data.center)) + circle_in_viewport(data.center, data.radius, false) + map.visibleRegion = QtPositioning.circle(data.center, data.radius) + circle_in_viewport(data.center, data.radius, true) + } + + function test_fit_circle_to_viewport_data() + { + return [ + { tag: "circle 1", center: + QtPositioning.coordinate(70,70), radius: 10 }, + { tag: "circle 2", center: + QtPositioning.coordinate(80,30), radius: 2000000 }, + { tag: "circle 3", center: + QtPositioning.coordinate(-82,30), radius: 2000000 }, + { tag: "circle 4", center: + QtPositioning.coordinate(60,179), radius: 20000 }, + { tag: "circle 5", center: + QtPositioning.coordinate(60,-179), radius: 20000 }, + ] + } + + function test_fit_rectangle_to_viewport(data) + { + verify(!is_coord_on_screen(data.topLeft),"rectangle visible") + verify(!is_coord_on_screen(data.bottomRight),"rectangle visible") + map.visibleRegion = QtPositioning.rectangle(data.topLeft,data.bottomRight) + verify(is_coord_on_screen(data.topLeft),"rectangle not visible") + verify(is_coord_on_screen(data.bottomRight),"rectangle not visible") + } + + function test_fit_rectangle_to_viewport_data() + { + return [ + { tag: "rectangle 1", + topLeft: QtPositioning.coordinate(80, 80), + bottomRight: QtPositioning.coordinate(78, 82) }, + { tag: "rectangle 2", + topLeft: QtPositioning.coordinate(30,-130), + bottomRight: QtPositioning.coordinate(0,-100)} + ] + } + + /*function test_ad_visible_items_move() { + // move different individual items out of screen + // then fit viewport + var xDir = 1 + var yDir = 0 + var xDirChange = -1 + var yDirChange = 1 + var dir = 0 + var move = 50 + for (dir = 0; dir < 4; dir++) { + // move rect out of screen + reset() + verify_visibility_all_items() + preMapRect.topLeft.longitude += move * xDir + preMapRect.topLeft.latitude += move * yDir + preMapRect.bottomRight.longitude += move * xDir + preMapRect.bottomRight.latitude += move * yDir + calculate_bounds() + verify(!is_coord_on_screen(preMapRect.topLeft)) + verify(!is_coord_on_screen(preMapRect.bottomRight)) + map.fitViewportToMapItems() + visualInspectionPoint() + verify_visibility_all_items() + + // move circle out of screen + reset() + verify_visibility_all_items() + preMapCircle.center.longitude += move * xDir + preMapCircle.center.latitude += move * yDir + calculate_bounds() + verify(!is_coord_on_screen(mapCircleTopLeft)) + verify(!is_coord_on_screen(mapCircleBottomRight)) + map.fitViewportToMapItems() + visualInspectionPoint() + verify_visibility_all_items() + + // move quick item out of screen + reset() + verify_visibility_all_items() + preMapQuickItem.coordinate.longitude += move * xDir + preMapQuickItem.coordinate.latitude += move * yDir + calculate_bounds() + verify(!is_coord_on_screen(mapQuickItemTopLeft)) + verify(!is_coord_on_screen(mapQuickItemBottomRight)) + map.fitViewportToMapItems() + visualInspectionPoint() + verify_visibility_all_items() + + // move map polygon out of screen + reset() + verify_visibility_all_items() + var i + for (i = 0; i < preMapPolygonDefaultPath.length; ++i) { + preMapPolygon.path[i].longitude += move * xDir + preMapPolygon.path[i].latitude += move * yDir + } + calculate_bounds() + verify(!is_coord_on_screen(mapPolygonTopLeft)) + verify(!is_coord_on_screen(mapPolygonBottomRight)) + map.fitViewportToMapItems() + visualInspectionPoint() + verify_visibility_all_items() + if (dir == 2) + xDirChange *= -1 + if (dir == 1) + yDirChange *= -1 + xDir += xDirChange + yDir += yDirChange + } + }*/ + + function clear_data() { + preMapRectClicked.clear() + preMapCircleClicked.clear() + preMapQuickItemClicked.clear() + preMapPolygonClicked.clear() + preMapCircleCenterChanged.clear() + preMapCircleColorChanged.clear() + preMapCircleRadiusChanged.clear() + preMapCircleBorderColorChanged.clear() + preMapCircleBorderWidthChanged.clear() + preMapRectTopLeftChanged.clear() + preMapRectBottomRightChanged.clear() + preMapRectColorChanged.clear() + preMapPolylineColorChanged.clear() + preMapPolylineWidthChanged.clear() + preMapPolylinePathChanged.clear() + preMapPolygonPathChanged.clear() + preMapPolygonColorChanged.clear() + preMapPolygonBorderColorChanged.clear() + preMapPolygonBorderWidthChanged.clear() + preMapRouteRouteChanged.clear() + preMapRouteLineColorChanged.clear() + preMapRouteLineWidthChanged.clear() + preMapQuickItemCoordinateChanged.clear() + preMapQuickItemAnchorPointChanged.clear() + preMapQuickItemZoomLevelChanged.clear() + preMapQuickItemSourceItemChanged.clear() + } + + function calculate_fit_circle_bounds() { + var circleDiagonal = Math.sqrt(2 * fitCircle.radius * fitCircle.radius) + fitCircleTopLeft = fitCircle.center.atDistanceAndAzimuth(circleDiagonal,-45) + fitCircleBottomRight = fitCircle.center.atDistanceAndAzimuth(circleDiagonal,135) + } + + function calculate_bounds(){ + var circleDiagonal = Math.sqrt(2 * preMapCircle.radius * preMapCircle.radius) + var itemTopLeft = preMapCircle.center.atDistanceAndAzimuth(circleDiagonal,-45) + var itemBottomRight = preMapCircle.center.atDistanceAndAzimuth(circleDiagonal,135) + + mapCircleTopLeft = itemTopLeft; + mapCircleBottomRight = itemBottomRight; + + itemTopLeft = preMapQuickItem.coordinate + var preMapQuickItemScreenPosition = map.fromCoordinate(preMapQuickItem.coordinate) + preMapQuickItemScreenPosition.x += preMapQuickItem.sourceItem.width + preMapQuickItemScreenPosition.y += preMapQuickItem.sourceItem.height + itemBottomRight = map.toCoordinate(preMapQuickItemScreenPosition) + + mapQuickItemTopLeft = itemTopLeft; + mapQuickItemBottomRight = itemBottomRight; + + var bounds = min_max_bounds_from_list(preMapPolygon.path) + mapPolygonTopLeft = bounds.topLeft; + mapPolygonBottomRight = bounds.bottomRight; + + bounds = min_max_bounds_from_list(preMapPolyline.path) + mapPolylineTopLeft = bounds.topLeft; + mapPolylineBottomRight = bounds.bottomRight; + + bounds = min_max_bounds_from_list(preMapRoute.route.path) + mapRouteTopLeft = bounds.topLeft; + mapRouteBottomRight = bounds.bottomRight; + } + + function min_max_bounds_from_list(coorindates){ + var i = 0 + var point = map.fromCoordinate(coorindates[0]) + var minX = point.x + var minY = point.y + var maxX = point.x + var maxY = point.y + + for (i=1; i < coorindates.length; ++i) { + point = map.fromCoordinate(coorindates[i]) + if (point.x < minX) + minX = point.x + if (point.x > maxX) + maxX = point.x + if (point.y < minY) + minY = point.y + if (point.y > maxY) + maxY = point.y + } + point.x = minX + point.y = minY + var itemTopLeft = map.toCoordinate(point) + point.x = maxX + point.y = maxY + var itemBottomRight = map.toCoordinate(point) + + return QtPositioning.rectangle(itemTopLeft, itemBottomRight); + } + + function verify_visibility_all_items(){ + calculate_bounds() + verify(is_coord_on_screen(preMapRect.topLeft)) + verify(is_coord_on_screen(preMapRect.bottomRight)) + verify(is_coord_on_screen(mapCircleTopLeft)) + verify(is_coord_on_screen(mapCircleBottomRight)) + verify(is_coord_on_screen(mapPolygonTopLeft)) + verify(is_coord_on_screen(mapPolygonBottomRight)) + verify(is_coord_on_screen(mapQuickItemTopLeft)) + verify(is_coord_on_screen(mapQuickItemBottomRight)) + verify(is_coord_on_screen(mapPolylineTopLeft)) + verify(is_coord_on_screen(mapPolylineBottomRight)) + verify(is_coord_on_screen(mapRouteTopLeft)) + verify(is_coord_on_screen(mapRouteBottomRight)) + } + + + function is_coord_on_screen(coord) { + return is_point_on_screen(map.fromCoordinate(coord)) + } + + function is_point_on_screen(point) { + if (point.x >= 0 && point.x <= (map.x + map.width) + && point.y >=0 && point.y <= (map.y + map.height) ) + return true; + else + return false; + } + + function fuzzy_compare(val, ref, tol) { + var tolerance = 2 + if (tol !== undefined) + tolerance = tol + if ((val >= ref - tolerance) && (val <= ref + tolerance)) + return true; + console.log('map fuzzy cmp returns false for value, ref, tolerance: ' + val + ', ' + ref + ', ' + tolerance) + return false; + } + + // call to visualInspectionPoint testcase (for dev time visual inspection) + function visualInspectionPoint(time) { + var waitTime = 0 // 300 + if (time !== undefined) + waitTime = time + if (waitTime > 0) { + console.log('halting for ' + waitTime + ' milliseconds') + wait (waitTime) + } + } + } +} + diff --git a/tests/auto/declarative_ui/tst_map_itemview.qml b/tests/auto/declarative_ui/tst_map_itemview.qml new file mode 100644 index 0000000..db788ac --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_itemview.qml @@ -0,0 +1,490 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.6 +import QtPositioning 5.5 +import QtLocation.Test 5.5 + +Item { + id: masterItem + width: 200 + height: 350 + // General-purpose elements for the test: + Plugin { id: testPlugin; name : "qmlgeo.test.plugin"; allowExperimental: true } + + property variant mapDefaultCenter: QtPositioning.coordinate(10, 30) + + Map { + id: map + objectName: 'staticallyDeclaredMap' + center: mapDefaultCenter; + plugin: testPlugin; + width: 100 + height: 100 + zoomLevel: 2 + MapCircle { + id: prepopulatedCircle + objectName: 'prepopulatedCircle' + center: mapDefaultCenter; + radius: 100 + } + } + + Map { + id: map3 + objectName: 'staticallyDeclaredMapWithView' + center: mapDefaultCenter; + plugin: testPlugin; + width: 100 + height: 100 + zoomLevel: 2 + MapItemView { + id: theItemView3 + model: testModel3 + delegate: Component { + MapCircle { + radius: 1500000 + center { + latitude: modeldata.coordinate.latitude + longitude: modeldata.coordinate.longitude + } + } + } + } + } + + MapCircle { + id: externalCircle + objectName: 'externalCircle' + radius: 200 + center: mapDefaultCenter + } + + SignalSpy {id: mapItemSpy; target: map; signalName: 'mapItemsChanged'} + + + MapCircle { + objectName: "externalCircle2" + id: externalCircle2 + radius: 2000000 + center: mapDefaultCenter + } + + MapCircle { + objectName: "externalCircle3" + id: externalCircle3 + radius: 2000000 + center: mapDefaultCenter + } + + MapRectangle { + objectName: "externalRectangle" + id: externalRectangle + } + + MapPolygon { + objectName: "externalPolygon" + id: externalPolygon + } + + MapPolyline { + objectName: 'externalPolyline' + id: externalPolyline + } + + MapQuickItem { + objectName: 'externalQuickItem' + id: externalQuickItem + sourceItem: Rectangle {} + } + + TestModel { + id: testModel + datatype: 'coordinate' + datacount: 7 + delay: 0 + } + + TestModel { + id: testModel2 + datatype: 'coordinate' + datacount: 3 + delay: 0 + } + + TestModel { + id: testModel3 + datatype: 'coordinate' + datacount: 0 + delay: 0 + } + + Plugin { + id: testPlugin_immediate; + name: "qmlgeo.test.plugin" + allowExperimental: true + parameters: [ + // Parms to guide the test plugin + PluginParameter { name: "gc_supported"; value: true}, + PluginParameter { name: "gc_finishRequestImmediately"; value: true}, + PluginParameter { name: "gc_validateWellKnownValues"; value: true} + ] + } + RouteQuery {id: routeQuery; + waypoints: [ + { latitude: 60, longitude: 60 }, + { latitude: 61, longitude: 62 }, + { latitude: 63, longitude: 64 }, + { latitude: 65, longitude: 66 }, + { latitude: 67, longitude: 68 } + ] + } + + RouteModel {id: routeModel; plugin: testPlugin_immediate; query: routeQuery } + SignalSpy {id: mapItemsChangedSpy; target: mapForTestingRouteModel; signalName: "mapItemsChanged"} + + Map { + id: mapForView + + property int mapItemsLength: mapItems.length + + center: mapDefaultCenter + plugin: testPlugin + anchors.fill: parent + zoomLevel: 2 + + MapCircle { + id: internalCircle + radius: 2000000 + center: mapDefaultCenter + } + MapItemView { + id: theItemView + model: testModel + delegate: Component { + id: theItemViewsComponent + MapCircle { + radius: 1500000 + center { + latitude: modeldata.coordinate.latitude + longitude: modeldata.coordinate.longitude + } + } + } + } + } + + Map { + id: mapForTestingListModel + + center: mapDefaultCenter + plugin: testPlugin + anchors.fill: parent + zoomLevel: 2 + + property int mapItemsLength: mapItems.length + property variant itemCoordinates: [ + QtPositioning.coordinate(11, 31), + QtPositioning.coordinate(12, 32), + QtPositioning.coordinate(13, 33) + ] + + MapItemView { + id: listModelItemView + model: ListModel { + id: testingListModel + ListElement { lat: 11; lon: 31 } + ListElement { lat: 12; lon: 32 } + ListElement { lat: 13; lon: 33 } + } + delegate: Component { + MapCircle { + radius: 1500000 + center { + latitude: lat + longitude: lon + } + } + } + } + } + + Map { + id: mapForTestingRouteModel + + property int mapItemsLength: mapItems.length + + plugin: testPlugin + center: mapDefaultCenter + anchors.fill: parent + zoomLevel: 2 + + MapItemView { + id: routeItemView + model: routeModel + delegate: Component { + MapRoute { + route: routeData + } + } + } + } + + TestCase { + name: "MapItem" + when: windowShown + function clear_data() { + mapItemSpy.clear() + } + + function test_basics() { + compare(theItemView.delegate, theItemViewsComponent); + compare(theItemView.model, testModel); + } + + function test_aaa_basic_add_remove() { // aaa to ensure execution first + clear_data() + compare(map.mapItems.length, 1) + compare(map.mapItems[0], prepopulatedCircle) + compare(mapItemSpy.count, 0) + // nonexistent + map.removeMapItem(externalCircle) + compare(mapItemSpy.count, 0) + compare(map.mapItems.length, 1) + compare(map.mapItems[0], prepopulatedCircle) + // real + map.removeMapItem(prepopulatedCircle) + compare(mapItemSpy.count, 1) + compare(map.mapItems.length, 0) + map.addMapItem(externalCircle) + map.addMapItem(prepopulatedCircle) + compare(mapItemSpy.count, 3) + compare(map.mapItems.length, 2) + // same again + map.addMapItem(prepopulatedCircle) + compare(mapItemSpy.count, 3) + compare(map.mapItems.length, 2) + compare(map.mapItems[0], externalCircle) + compare(map.mapItems[1], prepopulatedCircle) + map.removeMapItem(externalCircle) + compare(map.mapItems[0], prepopulatedCircle) + compare(mapItemSpy.count, 4) + compare(map.mapItems.length, 1) + map.clearMapItems() + compare(mapItemSpy.count, 5) + compare(map.mapItems.length, 0) + // empty map, do not crash + map.clearMapItems() + compare(mapItemSpy.count, 5) + compare(map.mapItems.length, 0) + } + + function test_dynamic_map_and_items() { + clear_data(); + /* + // basic create-destroy without items, mustn't crash + var dynamicMap = Qt.createQmlObject('import QtQuick 2.0; import QtLocation 5.3; Map { x:0; y:0; objectName: \'dynomik map\'; width: masterItem.width; height: masterItem.height; plugin: testPlugin} ', masterItem, "dynamicCreationErrors" ); + verify(dynamicMap !== null) + dynamicMap.destroy(1) + //wait(5) + + // add rm add, destroy with item on it + dynamicMap = Qt.createQmlObject('import QtQuick 2.0; import QtLocation 5.3; Map { x:0; y:0; objectName: \'dynomik map\'; width: masterItem.width; height: masterItem.height; plugin: testPlugin} ', masterItem, "dynamicCreationErrors" ); + verify(dynamicMap !== null) + dynamicMap.addMapItem(externalCircle); + compare(dynamicMap.mapItems.length, 1) + dynamicMap.removeMapItem(externalCircle); + compare(dynamicMap.mapItems.length, 0) + dynamicMap.addMapItem(externalCircle); + compare(dynamicMap.mapItems.length, 1) + dynamicMap.destroy(1) + //wait(5) + + // try adding same item to two maps, will not be allowed + var dynamicMap2 = Qt.createQmlObject('import QtQuick 2.0; import QtLocation 5.3; Map { x:0; y:0; objectName: \'dynomik map2\'; width: masterItem.width; height: masterItem.height; plugin: testPlugin} ', masterItem, "dynamicCreationErrors" ); + dynamicMap = Qt.createQmlObject('import QtQuick 2.0; import QtLocation 5.3; Map { x:0; y:0; objectName: \'dynomik map\'; width: masterItem.width; height: masterItem.height; plugin: testPlugin} ', masterItem, "dynamicCreationErrors" ); + verify(dynamicMap !== null) + verify(dynamicMap2 !== null) + compare(dynamicMap.mapItems.length, 0) + dynamicMap.addMapItem(externalCircle3); + compare(dynamicMap.mapItems.length, 1) + dynamicMap2.addMapItem(externalCircle3); + compare(dynamicMap2.mapItems.length, 0) + + // create and destroy a dynamic item that is in the map + var dynamicCircle = Qt.createQmlObject('import QtQuick 2.0; import QtLocation 5.3; MapCircle { objectName: \'dynamic circle 1\'; center { latitude: 5; longitude: 5 } radius: 15 } ', masterItem, "dynamicCreationErrors" ); + verify (dynamicCircle !== null) + compare(map.mapItems.length, 0) + map.addMapItem(dynamicCircle) + compare(mapItemSpy.count, 1) + compare(map.mapItems.length, 1) + dynamicCircle.destroy(1) + tryCompare(mapItemSpy, "count", 2) + compare(map.mapItems.length, 0) + + // leave one map item, will be destroyed at the end of the case + dynamicMap.addMapItem(externalCircle); + compare(dynamicMap.mapItems.length, 2) + + // leave a handful of item from model to the map and let it destroy + compare(map3.mapItems.length, 0) + testModel3.datacount = 4 + testModel3.update() + compare(map3.mapItems.length, 4) + */ + } + + function test_add_and_remove_with_view() { + // Basic adding and removing of static object + tryCompare(mapForView, "mapItemsLength", 8) // 1 declared and 7 from model + mapForView.addMapItem(internalCircle) + compare(mapForView.mapItems.length, 8) + mapForView.removeMapItem(internalCircle) + compare(mapForView.mapItems.length, 7) + mapForView.removeMapItem(internalCircle) + compare(mapForView.mapItems.length, 7) + // Basic adding and removing of dynamic object + var dynamicCircle = Qt.createQmlObject( "import QtQuick 2.0; import QtLocation 5.3; MapCircle {radius: 4000; center: mapDefaultCenter}", map, ""); + mapForView.addMapItem(dynamicCircle) + compare(mapForView.mapItems.length, 8) + mapForView.removeMapItem(dynamicCircle) + compare(mapForView.mapItems.length, 7) + mapForView.removeMapItem(dynamicCircle) + compare(mapForView.mapItems.length, 7) + } + SignalSpy {id: model1Spy; target: testModel; signalName: "modelChanged"} + SignalSpy {id: model2Spy; target: testModel2; signalName: "modelChanged"} + function test_model_change() { + // Ensure that internalCircle is removed + mapForView.removeMapItem(internalCircle) + + // Change the model of an MapItemView on the fly + // and verify that object counts change accordingly. + testModel.datacount = 7 + testModel.update() + + tryCompare(mapForView, "mapItemsLength", 7) + testModel.datacount += 2 + testModel2.datacount += 1 + // delegate spawning is async. wait a bit. + wait(1) + tryCompare(mapForView, "mapItemsLength", 9) + + theItemView.model = testModel + compare(mapForView.mapItems.length, 9) + theItemView.model = testModel2 + tryCompare(mapForView, "mapItemsLength", 4) + } + + function test_listmodel() { + tryCompare(mapForTestingListModel, "mapItemsLength", 3) + + for (var i = 0; i < 3; ++i) { + var itemCoord = mapForTestingListModel.mapItems[i].center + var index = mapForTestingListModel.itemCoordinates.indexOf(itemCoord) + verify(0 <= index && index < 3) + } + + testingListModel.remove(0) + compare(mapForTestingListModel.mapItems.length, 2) + + for (var i = 0; i < 2; ++i) { + itemCoord = mapForTestingListModel.mapItems[i].center + index = mapForTestingListModel.itemCoordinates.indexOf(itemCoord) + verify(1 <= index && index < 3) + } + + testingListModel.append({ lat: 1, lon: 1 }) + tryCompare(mapForTestingListModel, "mapItemsLength", 3) + compare(mapForTestingListModel.mapItems[2].center, QtPositioning.coordinate(1, 1)) + + testingListModel.clear() + compare(mapForTestingListModel.mapItems.length, 0) + } + + function test_routemodel() { + testModel.reset(); + mapItemsChangedSpy.clear() + compare(mapForTestingRouteModel.mapItems.length, 0) // precondition + compare(mapItemsChangedSpy.count, 0) + routeQuery.numberAlternativeRoutes = 4 + routeModel.update(); + tryCompare(mapForTestingRouteModel, "mapItemsLength", 4) + routeQuery.numberAlternativeRoutes = 3 + routeModel.update(); + tryCompare(mapForTestingRouteModel, "mapItemsLength", 3) + routeModel.reset(); + compare(mapForTestingRouteModel.mapItems.length, 0) + routeModel.reset(); // clear empty model + routeQuery.numberAlternativeRoutes = 3 + routeModel.update(); + tryCompare(mapForTestingRouteModel, "mapItemsLength", 3) + mapForTestingRouteModel.addMapItem(externalCircle2) + compare(mapForTestingRouteModel.mapItems.length, 4) + compare(mapForTestingRouteModel.mapItems[3], externalCircle2) + routeModel.reset(); + compare(mapForTestingRouteModel.mapItems.length, 1) + mapForTestingRouteModel.clearMapItems() + compare(mapForTestingRouteModel.mapItems.length, 0) + + // Test the mapItems list + mapForTestingRouteModel.addMapItem(externalCircle2) + compare(mapForTestingRouteModel.mapItems.length, 1) + compare(mapForTestingRouteModel.mapItems[0], externalCircle2) + + mapForTestingRouteModel.addMapItem(externalRectangle) + compare(mapForTestingRouteModel.mapItems.length, 2) + compare(mapForTestingRouteModel.mapItems[1], externalRectangle) + + mapForTestingRouteModel.addMapItem(externalRectangle) + compare(mapForTestingRouteModel.mapItems.length, 2) + compare(mapForTestingRouteModel.mapItems[1], externalRectangle) + + mapForTestingRouteModel.addMapItem(externalPolygon) + compare(mapForTestingRouteModel.mapItems.length, 3) + compare(mapForTestingRouteModel.mapItems[2], externalPolygon) + + mapForTestingRouteModel.addMapItem(externalQuickItem) + compare(mapForTestingRouteModel.mapItems.length, 4) + compare(mapForTestingRouteModel.mapItems[3], externalQuickItem) + + mapForTestingRouteModel.removeMapItem(externalCircle2) + compare(mapForTestingRouteModel.mapItems.length, 3) + compare(mapForTestingRouteModel.mapItems[0], externalRectangle) + + mapForTestingRouteModel.removeMapItem(externalRectangle) + compare(mapForTestingRouteModel.mapItems.length, 2) + compare(mapForTestingRouteModel.mapItems[0], externalPolygon) + + mapForTestingRouteModel.clearMapItems() + compare(mapForTestingRouteModel.mapItems.length, 0) + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_keepgrab.qml b/tests/auto/declarative_ui/tst_map_keepgrab.qml new file mode 100644 index 0000000..fa47eec --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_keepgrab.qml @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtTest 1.0 +import QtLocation 5.6 +import QtPositioning 5.5 + +Item { + // General-purpose elements for the test: + id: page + width: 200 + height: 200 + Plugin { id: testPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true } + + + Flickable { + id: flickable + anchors.fill: parent + contentWidth: flickable.width * 4; contentHeight: flickable.height + + Map { + id: map + x: flickable.width + height: flickable.height + width:flickable.width + plugin: testPlugin + } + } + + SignalSpy { id: mapPanStartedSpy; target: map.gesture; signalName: 'panStarted' } + SignalSpy { id: mapPanFinishedSpy; target: map.gesture; signalName: 'panFinished' } + SignalSpy { id: flickStartedSpy; target: flickable; signalName: 'flickStarted' } + SignalSpy { id: flickEndedSpy; target: flickable; signalName: 'flickEnded' } + SignalSpy { id: preventStealingChangedSpy; target: map.gesture; signalName: 'preventStealingChanged' } + + + TestCase { + when: windowShown + name: "MapKeepGrabAndPreventSteal" + + function initTestCase() + { + compare(map.gesture.preventStealing, false) + } + + function init() + { + map.gesture.acceptedGestures = MapGestureArea.PanGesture | MapGestureArea.FlickGesture; + map.gesture.flickDeceleration = 500 + map.zoomLevel = 1 + map.center = QtPositioning.coordinate(50,50) + map.gesture.preventStealing = false + flickable.contentX = 0 + flickable.contentY = 0 + mapPanStartedSpy.clear() + mapPanFinishedSpy.clear() + flickStartedSpy.clear() + flickEndedSpy.clear() + preventStealingChangedSpy.clear() + } + + function flick() + { + var i = 0 + mousePress(flickable, flickable.width - 1, 0) + for (i = flickable.width; i > 0; i -= 5) { + wait(5) + mouseMove(flickable, i, 0, 0, Qt.LeftButton); + } + mouseRelease(flickable, i, 0) + } + + function pan() + { + var i = 0 + mousePress(map, 0, 0) + for (i = 0; i < flickable.width; i += 5) { + wait(5) + mouseMove(map, i, 0, 0, Qt.LeftButton); + } + mouseRelease(map, i, 0) + } + + function test_flick() + { + var center = QtPositioning.coordinate(map.center.latitude,map.center.longitude) + flick() //flick flickable + tryCompare(flickStartedSpy,"count",1) + pan() //pan map + tryCompare(flickStartedSpy,"count",2) // both directions + tryCompare(flickEndedSpy,"count",1) + tryCompare(mapPanStartedSpy,"count", 0) + tryCompare(mapPanFinishedSpy,"count", 0) + //map should not change + verify(center == map.center) + } + + function test_map_grab() + { + var center = QtPositioning.coordinate(map.center.latitude,map.center.longitude) + pan() //pan map + tryCompare(mapPanStartedSpy,"count",1) + tryCompare(mapPanFinishedSpy, "count", 1) + + compare(flickStartedSpy.count, 0) + compare(flickEndedSpy.count, 0) + //map should change + verify(center != map.center) + } + + function test_map_preventsteal() + { + map.gesture.preventStealing = false + compare(preventStealingChangedSpy.count, 0) + map.gesture.preventStealing = true + compare(preventStealingChangedSpy.count, 1) + + var center = QtPositioning.coordinate(map.center.latitude,map.center.longitude) + flick() //flick flickable + tryCompare(flickStartedSpy,"count",1) + pan() //pan map + tryCompare(flickStartedSpy,"count",1) // both directions + tryCompare(flickEndedSpy,"count",1) + tryCompare(mapPanStartedSpy,"count", 1) + tryCompare(mapPanFinishedSpy,"count", 1) + //map should not change + verify(center != map.center) + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_maptype.qml b/tests/auto/declarative_ui/tst_map_maptype.qml new file mode 100644 index 0000000..fa056c4 --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_maptype.qml @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.6 + +Item{ + id: page + x: 0; y: 0; + width: 100 + height: 100 + + Plugin { id: testPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true } + Map { id: map; anchors.fill: parent } + SignalSpy { id: supportedMapTypesSpy; target: map; signalName: "supportedMapTypesChanged" } + SignalSpy { id: activeMapTypeChangedSpy; target: map; signalName: "activeMapTypeChanged" } + + TestCase { + id: testCase + name: "MapType" + when: windowShown + + function initTestCase() + { + compare(map.supportedMapTypes.length, 0) + compare(map.activeMapType.style, MapType.NoMap) + map.plugin = testPlugin + tryCompare(supportedMapTypesSpy, "count", 1) + compare(map.supportedMapTypes.length,3) + compare(map.supportedMapTypes[0].style, MapType.StreetMap) + compare(map.supportedMapTypes[0].name, "StreetMap") + compare(map.supportedMapTypes[0].description, "StreetMap") + compare(map.supportedMapTypes[1].style, MapType.SatelliteMapDay) + compare(map.supportedMapTypes[1].name, "SatelliteMapDay") + compare(map.supportedMapTypes[1].description, "SatelliteMapDay") + compare(map.supportedMapTypes[2].style, MapType.CycleMap) + compare(map.supportedMapTypes[2].name, "CycleMap") + compare(map.supportedMapTypes[2].description, "CycleMap") + //default + compare(map.activeMapType.style, MapType.StreetMap) + } + + function init() + { + supportedMapTypesSpy.clear() + activeMapTypeChangedSpy.clear() + map.activeMapType = map.supportedMapTypes[0] + } + + function test_setting_types() + { + map.activeMapType = map.supportedMapTypes[0] + tryCompare(activeMapTypeChangedSpy, "count", 0) + + map.activeMapType = map.supportedMapTypes[1] + tryCompare(activeMapTypeChangedSpy, "count", 1) + compare(map.supportedMapTypes[1].name, map.activeMapType.name) + compare(map.supportedMapTypes[1].style, map.activeMapType.style) + + map.activeMapType = map.supportedMapTypes[2] + tryCompare(activeMapTypeChangedSpy, "count", 2) + compare(map.supportedMapTypes[2].name, map.activeMapType.name) + compare(map.supportedMapTypes[2].style, map.activeMapType.style) + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_mouse.qml b/tests/auto/declarative_ui/tst_map_mouse.qml new file mode 100644 index 0000000..99aff03 --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_mouse.qml @@ -0,0 +1,723 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtLocation 5.6 +import QtPositioning 5.5 + + /* + MouseArea setup for this test case. + Map dimensions are 100 * 100 + Item containing map is 120,120 + + (50,50) + (0,0) ---------------------------------------------------- (100,0) + | no mouse area | mouse area overlapper | + | | | + (0,20) ---------------------------------------------------- (100,20) + | mouse area upper | mouse area upper, | + | | mouse area overlapper | + | | | + | | | + | | | + (0,50) ---------------------------------------------------- (100,50) + | mouse area lower | mouse area lower, | + | | mouse area overlapper | + | | | + | | | + | | | + | | | + | | | + | | | + (0,100) ---------------------------------------------------- (100,100) | + | + | + ----------(120, 120) + + */ + +Item { + id: page + x: 0; y: 0; + width: 120 + height: 120 + // General-purpose elements for the test: + Plugin { id: testPlugin; name : "qmlgeo.test.plugin"; allowExperimental: true } + + function setMouseData(ma, me) + { + ma.lastX = me.x + ma.lastY = me.y + ma.lastButton = me.button + ma.lastButtons = me.buttons + ma.lastModifiers = me.modifiers + ma.lastWasHeld = me.wasHeld + ma.lastIsClick = me.isClick + ma.lastAccepted = me.accepted + } + + Map { + id: map; + x: 0; y: 0; width: 100; height: 100 + center { + latitude: 20 + longitude: 20 + } + + plugin: testPlugin; + + MouseArea { + id: mouseUpper + objectName: "mouseUpper" + x: 0; y: 20; width: 100; height: 29 + property int lastX: -1 + property int lastY: -1 + property int lastButton: Qt.NoButton + property int lastButtons: Qt.NoButton + property int lastModifiers: Qt.NoModifier + property bool lastWasHeld: false; + property bool lastIsClick: false + property bool lastAccepted: false; + + preventStealing: true + + // The following signal handlers use arguments[0] instead of mouse due to QTBUG-36560 + onClicked: page.setMouseData(mouseUpper, arguments[0]) + onDoubleClicked: page.setMouseData(mouseUpper, arguments[0]) + onPressed: page.setMouseData(mouseUpper, arguments[0]) + onReleased: page.setMouseData(mouseUpper, arguments[0]) + onPositionChanged: page.setMouseData(mouseUpper, arguments[0]) + onPressAndHold: page.setMouseData(mouseUpper, arguments[0]) + } + MouseArea { + id: mouseLower + objectName: "mouseLower" + x: 0; y: 50; width: 100; height: 50 + property int lastX: -1 + property int lastY: -1 + property int lastButton: Qt.NoButton + property int lastButtons: Qt.NoButton + property int lastModifiers: Qt.NoModifier + property bool lastWasHeld: false; + property bool lastIsClick: false + property bool lastAccepted: false; + + // The following signal handlers use arguments[0] instead of mouse due to QTBUG-36560 + onClicked: page.setMouseData(mouseLower, arguments[0]) + onDoubleClicked: page.setMouseData(mouseLower, arguments[0]) + onPressed: page.setMouseData(mouseLower, arguments[0]) + onReleased: page.setMouseData(mouseLower, arguments[0]) + onPositionChanged: page.setMouseData(mouseLower, arguments[0]) + onPressAndHold: page.setMouseData(mouseLower, arguments[0]) + } + MouseArea { + id: mouseOverlapper + objectName: "mouseOverlapper" + x: 50; y: 0; width: 50; height: 100 + property int lastX: -1 + property int lastY: -1 + property int lastButton: Qt.NoButton + property int lastButtons: Qt.NoButton + property int lastModifiers: Qt.NoModifier + property bool lastWasHeld: false; + property bool lastIsClick: false + property bool lastAccepted: false; + + // The following signal handlers use arguments[0] instead of mouse due to QTBUG-36560 + onClicked: page.setMouseData(mouseOverlapper, arguments[0]) + onDoubleClicked: page.setMouseData(mouseOverlapper, arguments[0]) + onPressed: page.setMouseData(mouseOverlapper, arguments[0]) + onReleased: page.setMouseData(mouseOverlapper, arguments[0]) + onPositionChanged: page.setMouseData(mouseOverlapper, arguments[0]) + onPressAndHold: page.setMouseData(mouseOverlapper, arguments[0]) + } + } + + TestCase { + name: "MouseArea" + when: windowShown + SignalSpy {id: mouseUpperClickedSpy; target: mouseUpper; signalName: "clicked"} + SignalSpy {id: mouseLowerClickedSpy; target: mouseLower; signalName: "clicked"} + SignalSpy {id: mouseOverlapperClickedSpy; target: mouseOverlapper; signalName: "clicked"} + SignalSpy {id: mouseUpperDoubleClickedSpy; target: mouseUpper; signalName: "doubleClicked"} + SignalSpy {id: mouseLowerDoubleClickedSpy; target: mouseLower; signalName: "doubleClicked"} + SignalSpy {id: mouseOverlapperDoubleClickedSpy; target: mouseOverlapper; signalName: "doubleClicked"} + SignalSpy {id: mouseUpperPressedSpy; target: mouseUpper; signalName: "onPressed"} + SignalSpy {id: mouseLowerPressedSpy; target: mouseLower; signalName: "onPressed"} + SignalSpy {id: mouseOverlapperPressedSpy; target: mouseOverlapper; signalName: "onPressed"} + SignalSpy {id: mouseUpperReleasedSpy; target: mouseUpper; signalName: "released"} + SignalSpy {id: mouseLowerReleasedSpy; target: mouseLower; signalName: "released"} + SignalSpy {id: mouseOverlapperReleasedSpy; target: mouseOverlapper; signalName: "released"} + SignalSpy {id: mouseUpperPositionChangedSpy; target: mouseUpper; signalName: "positionChanged"} + SignalSpy {id: mouseLowerPositionChangedSpy; target: mouseLower; signalName: "positionChanged"} + SignalSpy {id: mouseOverlapperPositionChangedSpy; target: mouseOverlapper; signalName: "positionChanged"} + SignalSpy {id: mouseUpperPressAndHoldSpy; target: mouseUpper; signalName: "pressAndHold"} + SignalSpy {id: mouseLowerPressAndHoldSpy; target: mouseLower; signalName: "pressAndHold"} + SignalSpy {id: mouseOverlapperPressAndHoldSpy; target: mouseOverlapper; signalName: "pressAndHold"} + SignalSpy {id: mouseUpperEnteredSpy; target: mouseUpper; signalName: "entered"} + SignalSpy {id: mouseLowerEnteredSpy; target: mouseLower; signalName: "entered"} + SignalSpy {id: mouseOverlapperEnteredSpy; target: mouseOverlapper; signalName: "entered"} + SignalSpy {id: mouseUpperExitedSpy; target: mouseUpper; signalName: "exited"} + SignalSpy {id: mouseLowerExitedSpy; target: mouseLower; signalName: "exited"} + SignalSpy {id: mouseOverlapperExitedSpy; target: mouseOverlapper; signalName: "exited"} + + SignalSpy {id: mouseUpperEnabledChangedSpy; target: mouseUpper; signalName: "enabledChanged"} + SignalSpy {id: mouseUpperAcceptedButtonsChangedSpy; target: mouseUpper; signalName: "acceptedButtonsChanged"} + SignalSpy {id: mouseUpperPressedButtonsChangedSpy; target: mouseUpper; signalName: "pressedChanged"} + SignalSpy {id: mouseUpperHoveredChangedSpy; target: mouseUpper; signalName: "hoveredChanged"} + SignalSpy {id: mouseUpperPressedChangedSpy; target: mouseUpper; signalName: "pressedChanged"} + + SignalSpy {id: mouseOverlapperEnabledChangedSpy; target: mouseOverlapper; signalName: "enabledChanged"} + + function clear_data() { + mouseUpperClickedSpy.clear() + mouseLowerClickedSpy.clear() + mouseOverlapperClickedSpy.clear() + mouseUpperDoubleClickedSpy.clear() + mouseLowerDoubleClickedSpy.clear() + mouseOverlapperDoubleClickedSpy.clear() + mouseUpperPressedSpy.clear() + mouseLowerPressedSpy.clear() + mouseOverlapperPressedSpy.clear() + mouseUpperReleasedSpy.clear() + mouseLowerReleasedSpy.clear() + mouseOverlapperReleasedSpy.clear() + mouseUpperPositionChangedSpy.clear() + mouseLowerPositionChangedSpy.clear() + mouseOverlapperPositionChangedSpy.clear() + mouseUpperPressAndHoldSpy.clear() + mouseLowerPressAndHoldSpy.clear() + mouseOverlapperPressAndHoldSpy.clear() + mouseUpperEnteredSpy.clear() + mouseLowerEnteredSpy.clear() + mouseOverlapperEnteredSpy.clear() + mouseUpperExitedSpy.clear() + mouseLowerExitedSpy.clear() + mouseOverlapperExitedSpy.clear() + + mouseUpperEnabledChangedSpy.clear() + mouseUpperAcceptedButtonsChangedSpy.clear() + mouseUpperPressedButtonsChangedSpy.clear() + mouseUpperHoveredChangedSpy.clear() + mouseUpperPressedChangedSpy.clear() + mouseUpperPositionChangedSpy.clear() + + mouseOverlapperEnabledChangedSpy.clear() + } + // these 'real_' prefixed functions do sequences as + // it would occur on real app (e.g. doubleclick is in fact + // a sequence of press, release, doubleclick, release). + // (they were recorded as seen on test app). mouseClick() works ok + // because testlib internally converts it to mousePress + mouseRelease events + function real_click (target, x, y) { + mousePress(target, x,y) + mouseRelease(target, x, y) + } + function real_double_click (target, x, y) { + mousePress(target, x,y) + mouseRelease(target, x, y) + mousePress(target, x, y) + mouseDoubleClick(target, x, y) + mouseRelease(target, x, y) + } + function real_press_and_hold(target, x,y) { + mousePress(target,x,y) + wait(1000) // threshold is 800 ms + mouseRelease(target,x, y) + } + + function test_enabled() { + clear_data() + // check that disabling overlapping mouse areas let events flow through + mouseUpper.enabled = false + compare(mouseUpperEnabledChangedSpy.count, 1) + compare(mouseUpperClickedSpy.count, 0) + mouseClick(map, 5, 25) + compare(mouseUpperClickedSpy.count, 0) + mouseUpper.enabled = true + mouseClick(map, 5, 25) + tryCompare(mouseUpperClickedSpy, "count", 1) + compare(mouseUpperEnabledChangedSpy.count, 2) + // when overlapping are is disabled, the event should flow through + compare(mouseOverlapperClickedSpy.count, 0) + mouseClick(map, 55, 25) + tryCompare(mouseUpperClickedSpy, "count", 1) + compare(mouseOverlapperClickedSpy.count, 1) + mouseOverlapper.enabled = false + compare(mouseOverlapperEnabledChangedSpy.count, 1) + compare(mouseOverlapper.enabled, false) + mouseClick(map, 55, 25) + tryCompare(mouseOverlapperClickedSpy, "count", 1) + compare(mouseUpperClickedSpy.count, 2) + // re-enable and verify that still works + mouseOverlapper.enabled = true + compare(mouseOverlapperEnabledChangedSpy.count, 2) + compare(mouseOverlapper.enabled, true) + mouseClick(map, 55, 25) + tryCompare(mouseOverlapperClickedSpy, "count", 2) // should consume again + compare(mouseUpperClickedSpy.count, 2) + } + + function test_wheel() { + clear_data() + wait(500); + // on map but without mouse area + var startZoomLevel = 6.20 + map.zoomLevel = startZoomLevel + mouseWheel(map, 5, 5, 15, 5, Qt.LeftButton, Qt.NoModifiers) + //see QDeclarativeGeoMapGestureArea::handleWheelEvent + var endZoomLevel = startZoomLevel + 5 * 0.001 + compare(map.zoomLevel,endZoomLevel) + + map.zoomLevel = startZoomLevel + mouseWheel(map, 5, 5, -15, -5, Qt.LeftButton, Qt.NoModifiers) + //see QDeclarativeGeoMapGestureArea::handleWheelEvent + endZoomLevel = startZoomLevel - 5 * 0.001 + compare(map.zoomLevel,endZoomLevel) + + // on map on top of mouse area + map.zoomLevel = startZoomLevel + mouseWheel(map, 55, 75, -30, -2, Qt.LeftButton, Qt.NoModifiers) + endZoomLevel = startZoomLevel - 2 * 0.001 + compare(map.zoomLevel,endZoomLevel) + + // outside of map + map.zoomLevel = startZoomLevel + mouseWheel(map, -100, -100, 40, 4, Qt.LeftButton, Qt.NoModifiers) + compare(map.zoomLevel,startZoomLevel) + } + + function test_aaa_basic_properties() // _aaa_ to ensure execution first + { + clear_data() + wait(50) + // default values + compare(mouseUpper.containsMouse, false) + compare(mouseUpper.pressed, false) + compare(mouseUpper.enabled, true) + compare(mouseUpper.pressedButtons, 0) + compare(mouseUpper.acceptedButtons, Qt.LeftButton) + // accepted buttons + compare(mouseUpperAcceptedButtonsChangedSpy.count, 0) + mouseUpper.acceptedButtons = Qt.RightButton | Qt.MiddleButton + compare(mouseUpper.acceptedButtons, Qt.RightButton | Qt.MiddleButton) + compare(mouseUpperAcceptedButtonsChangedSpy.count, 1) + mouseClick(map, 5, 25) + compare(mouseUpperClickedSpy.count, 0) // left button not accepted + mouseUpper.acceptedButtons = Qt.LeftButton + compare(mouseUpperAcceptedButtonsChangedSpy.count, 2) + mouseClick(map, 5, 25) + tryCompare(mouseUpperClickedSpy, "count", 1) + } + + function test_basic_position_changed() { + // tests basic position changed/move when button is being pressed + clear_data(); + wait(500); + mousePress(map, 5, 25) + compare(mouseUpperPressedSpy.count, 1) + compare(mouseUpper.lastAccepted, true) + compare(mouseUpper.lastButton, Qt.LeftButton) + compare(mouseUpper.lastButtons, Qt.LeftButton) + compare(mouseUpper.lastModifiers, Qt.NoModifier) + // moves within the mouse area + mouseMove(map, 5, 26, 0, Qt.LeftButton) // '0' is 'delay' + wait(1) // mouseMove event goes one extra eventloop round in the test lib + compare(mouseUpperEnteredSpy.count, 1) + compare(mouseUpperPositionChangedSpy.count, 1) + compare(mouseUpper.mouseX, 5) + compare(mouseUpper.mouseY, 6) // 20 offset, mouseXY is relative to the mouse area + compare(mouseUpper.lastAccepted, true) + compare(mouseUpper.lastButton, Qt.NoButton) + compare(mouseUpper.lastButtons, Qt.LeftButton) // buttons being pressed whilst movin' + compare(mouseUpper.lastModifiers, Qt.NoModifier) + compare(mouseUpper.lastWasHeld, false) // testfunction won't take required 800 ms + compare(mouseUpper.lastX, 5) + compare(mouseUpper.lastY, 6) // remember 20 offset of the mouse area + + mouseMove(map, 6, 27, 0, Qt.LeftButton | Qt.RightButton) + wait(1) + compare(mouseUpperEnteredSpy.count, 1) // no re-entry + compare(mouseUpperPositionChangedSpy.count, 2) + compare(mouseUpper.mouseX, 6) + compare(mouseUpper.mouseY, 7) + compare(mouseUpper.lastAccepted, true) + compare(mouseUpper.lastButton, Qt.NoButton) + compare(mouseUpper.lastButtons, Qt.LeftButton | Qt.RightButton) // buttons being pressed whilst movin' + compare(mouseUpper.lastModifiers, Qt.NoModifier) + compare(mouseUpper.lastWasHeld, false) // testfunction won't take required 800 ms + compare(mouseUpper.lastX, 6) + compare(mouseUpper.lastY, 7) // remember 20 offset of the mouse area + + // moves outside of mouse but within map + mouseMove(map, 2, 2, 0) + wait(1) + compare(mouseUpperExitedSpy.count, 1) + compare(mouseUpperPositionChangedSpy.count, 3) + compare(mouseUpper.mouseX, 2) + compare(mouseUpper.mouseY, -18) + // come back to map + mouseMove(map, 7, 28, 0) + wait(1) + compare(mouseUpperEnteredSpy.count, 2) + compare(mouseUpperExitedSpy.count, 1) + compare(mouseUpperPositionChangedSpy.count, 4) + compare(mouseUpper.mouseX, 7) + compare(mouseUpper.mouseY, 8) + + // move outside of widget area (left). make sure that other mouse areas won't get the events + mouseMove(map, -10, 10, 0) + wait(1) + compare(mouseUpperPositionChangedSpy.count, 5) + compare(mouseUpperExitedSpy.count, 2) + compare(mouseUpper.mouseX, -10) + compare(mouseUpper.mouseY, -10) + + // back in and then on top of the widget + mouseMove(map, 5, 25, 0) + wait(1) + compare(mouseUpperPositionChangedSpy.count, 6) + compare(mouseUpperExitedSpy.count, 2) + compare(mouseUpperEnteredSpy.count, 3) + compare(mouseUpper.mouseX, 5) + compare(mouseUpper.mouseY, 5) + mouseMove(map, 5, -25, 0) + wait(1) + compare(mouseUpperPositionChangedSpy.count, 7) + compare(mouseUpperExitedSpy.count, 3) + compare(mouseUpperEnteredSpy.count, 3) + compare(mouseUpper.mouseX, 5) + compare(mouseUpper.mouseY, -45) + + // back in then float on top of other mouse areas + mouseMove(map, 5, 25, 0) + wait(1) + compare(mouseUpperPositionChangedSpy.count, 8) + compare(mouseUpperExitedSpy.count, 3) + compare(mouseUpperEnteredSpy.count, 4) + compare(mouseUpper.mouseX, 5) + compare(mouseUpper.mouseY, 5) + mouseMove(map, 5, 75, 0) + wait(1) + compare(mouseUpperPositionChangedSpy.count, 9) + compare(mouseUpperExitedSpy.count, 4) + compare(mouseUpperEnteredSpy.count, 4) + compare(mouseUpper.mouseX, 5) + compare(mouseUpper.mouseY, 55) // remember the 20 offset of upper mouse area + mouseMove(map, 75, 75, 0) + wait(1) + compare(mouseUpperPositionChangedSpy.count, 10) + compare(mouseUpperExitedSpy.count, 4) + compare(mouseUpperEnteredSpy.count, 4) + compare(mouseUpper.mouseX, 75) + compare(mouseUpper.mouseY, 55) + // finally back in + mouseMove(map, 5, 25, 0) + wait(1) + compare(mouseUpperPositionChangedSpy.count, 11) + compare(mouseUpperExitedSpy.count, 4) + compare(mouseUpperEnteredSpy.count, 5) + compare(mouseUpper.mouseX, 5) + compare(mouseUpper.mouseY, 5) + + // check that these fellas didn't get any stupid ideas + compare(mouseLowerEnteredSpy.count, 0) + compare(mouseLowerPositionChangedSpy.count, 0) + compare(mouseOverlapperEnteredSpy.count, 0) + compare(mouseOverlapperPositionChangedSpy.count, 0) + // release mouse + mouseRelease(map, 5, 25) + // TODO enable these! + compare(mouseUpperEnteredSpy.count, 5) + compare(mouseUpperExitedSpy.count, 5) // release triggers one more exited() + } + + function test_basic_press_release() { + clear_data() + wait(500); + // send to emptiness + mousePress(map, 5, 5) + compare(mouseUpperPressedSpy.count, 0) + compare(mouseLowerPressedSpy.count, 0) + compare(mouseOverlapperPressedSpy.count, 0) + mouseRelease(map, 5, 5) + compare(mouseUpperReleasedSpy.count, 0) + compare(mouseLowerReleasedSpy.count, 0) + compare(mouseOverlapperReleasedSpy.count, 0) + // send to upper mouse area + mousePress(map, 5, 25) + compare(mouseUpperPressedSpy.count, 1) + compare(mouseLowerPressedSpy.count, 0) + compare(mouseOverlapperPressedSpy.count, 0) + + compare(mouseUpper.lastAccepted, true) + compare(mouseUpper.lastButton, Qt.LeftButton) + compare(mouseUpper.lastModifiers, Qt.NoModifier) + compare(mouseUpper.lastWasHeld, false) + compare(mouseUpper.lastX, 5) + compare(mouseUpper.lastY, 5) // remember 20 offset of the mouse area + + mouseRelease(map, 5, 25) + compare(mouseUpperPressedSpy.count, 1) + compare(mouseUpperReleasedSpy.count, 1) + compare(mouseLowerPressedSpy.count, 0) + compare(mouseLowerReleasedSpy.count, 0) + + mousePress(map, 5, 26) + compare(mouseUpperPressedSpy.count, 2) + compare(mouseLowerPressedSpy.count, 0) + compare(mouseOverlapperPressedSpy.count, 0) + + mouseRelease(map, 5, 26) + compare(mouseUpperPressedSpy.count, 2) + compare(mouseUpperReleasedSpy.count, 2) + compare(mouseLowerPressedSpy.count, 0) + compare(mouseLowerReleasedSpy.count, 0) + compare(mouseUpper.lastAccepted, true) + compare(mouseUpper.lastButton, Qt.LeftButton) + compare(mouseUpper.lastModifiers, Qt.NoModifier) + compare(mouseUpper.lastWasHeld, false) + compare(mouseUpper.lastX, 5) + compare(mouseUpper.lastY, 6) // remember 20 offset of the mouse area + + mousePress(map, 5, 75) + compare(mouseUpperPressedSpy.count, 2) + compare(mouseLowerPressedSpy.count, 1) + compare(mouseOverlapperPressedSpy.count, 0) + compare(mouseLower.lastAccepted, true) + compare(mouseLower.lastButton, Qt.LeftButton) + compare(mouseLower.lastModifiers, Qt.NoModifier) + compare(mouseLower.lastWasHeld, false) + compare(mouseLower.lastX, 5) + compare(mouseLower.lastY, 25) // remember 50 offset of the mouse area + + mouseRelease(map, 5, 75) + compare(mouseUpperPressedSpy.count, 2) + compare(mouseUpperReleasedSpy.count, 2) + compare(mouseLowerPressedSpy.count, 1) + compare(mouseLowerReleasedSpy.count, 1) + + + compare(mouseOverlapperPressedSpy.count, 0) + mousePress(map, 55, 75) + compare(mouseUpperPressedSpy.count, 2) + compare(mouseLowerPressedSpy.count, 1) + compare(mouseOverlapperPressedSpy.count, 1) + compare(mouseOverlapperReleasedSpy.count, 0) + mouseRelease(map, 55, 25) + compare(mouseUpperPressedSpy.count, 2) + compare(mouseUpperReleasedSpy.count, 2) + compare(mouseLowerPressedSpy.count, 1) + compare(mouseLowerReleasedSpy.count, 1) + //this should follow the same logic as Flickable + compare(mouseOverlapperReleasedSpy.count, 0) + } + + function test_basic_click() { + clear_data(); + wait(500); + + mouseClick(map, 5, 5, Qt.RightButton, Qt.AltModifier) + compare(mouseUpperClickedSpy.count, 0) + compare(mouseLowerClickedSpy.count, 0) + compare(mouseOverlapperClickedSpy.count, 0) + mouseUpper.acceptedButtons = Qt.LeftButton | Qt.RightButton + // TC sending click event to upper mouse area 5,25 + mouseClick(map, 5, 25, Qt.RightButton, Qt.AltModifier) + tryCompare(mouseUpperClickedSpy, "count", 1) + // TC done and clicked was received + //compare(mouseUpperClickedSpy.count, 1) + compare(mouseLowerClickedSpy.count, 0) + compare(mouseOverlapperClickedSpy.count, 0) + compare(mouseUpper.lastAccepted, true) + compare(mouseUpper.lastButton, Qt.RightButton) + compare(mouseUpper.lastModifiers, Qt.AltModifier) + compare(mouseUpper.lastWasHeld, false) + compare(mouseUpper.lastX, 5) + compare(mouseUpper.lastY, 5) // remember 20 offset of the mouse area + // check we get valid geocoordinates (would be NaN if something was wrong) + // todo + //verify(mouseUpper.lastMouseEvent.coordinate.longitude > -180 && mouseUpper.lastMouseEvent.coordinate.longitude < 180) + //verify(mouseUpper.lastMouseEvent.coordinate.longitude > -90 && mouseUpper.lastMouseEvent.coordinate.latitude < 90) + + // mouse click with unaccepted buttons should not cause click + mouseUpper.acceptedButtons = Qt.LeftButton + mouseClick(map, 5, 25, Qt.RightButton, Qt.AltModifier) + tryCompare(mouseUpperClickedSpy, "count", 1) + compare(mouseLowerClickedSpy.count, 0) + compare(mouseOverlapperClickedSpy.count, 0) + + mouseClick(map, 5, 25) + tryCompare(mouseUpperClickedSpy, "count", 2) + compare(mouseLowerClickedSpy.count, 0) + compare(mouseOverlapperClickedSpy.count, 0) + compare(mouseUpper.lastModifiers, Qt.NoModifier) + compare(mouseUpper.lastButton, Qt.LeftButton) + mouseClick(map, 5, 55) + tryCompare(mouseUpperClickedSpy, "count", 2) + compare(mouseLowerClickedSpy.count, 1) + compare(mouseOverlapperClickedSpy.count, 0) + mouseClick(map, 5, 55) + tryCompare(mouseUpperClickedSpy,"count", 2) + compare(mouseLowerClickedSpy.count, 2) + compare(mouseOverlapperClickedSpy.count, 0) + // declaration order counts on overlap case; overlapping area + // declared later will get the events + mouseClick(map, 55, 25) + tryCompare(mouseUpperClickedSpy, "count", 2) + compare(mouseLowerClickedSpy.count, 2) + compare(mouseOverlapperClickedSpy.count, 1) + mouseClick(map, 55, 75) + tryCompare(mouseUpperClickedSpy, "count", 2) + compare(mouseLowerClickedSpy.count, 2) + compare(mouseOverlapperClickedSpy.count, 2) + real_click(map, 55, 25) + tryCompare(mouseUpperClickedSpy, "count", 2) + compare(mouseLowerClickedSpy.count, 2) + compare(mouseOverlapperClickedSpy.count, 3) + real_click(map, 55, 75) + tryCompare(mouseUpperClickedSpy, "count", 2) + compare(mouseLowerClickedSpy.count, 2) + compare(mouseOverlapperClickedSpy.count, 4) + } + + function test_basic_double_click() { + clear_data(); + wait(500); + real_double_click(map, 5, 5) + + compare(mouseUpperDoubleClickedSpy.count, 0) + compare(mouseLowerDoubleClickedSpy.count, 0) + compare(mouseOverlapperDoubleClickedSpy.count, 0) + real_double_click(map, 5, 25) + tryCompare(mouseUpper, "lastAccepted", true) + compare(mouseUpper.lastButton, Qt.LeftButton) + compare(mouseUpper.lastModifiers, Qt.NoModifier) + compare(mouseUpper.lastWasHeld, false) + compare(mouseUpper.lastX, 5) + compare(mouseUpper.lastY, 5) // remember 20 offset of the mouse area + + compare(mouseUpperDoubleClickedSpy.count, 1) + compare(mouseLowerDoubleClickedSpy.count, 0) + compare(mouseOverlapperDoubleClickedSpy.count, 0) + real_double_click(map, 5, 25) + tryCompare(mouseUpperDoubleClickedSpy, "count", 2) + compare(mouseLowerDoubleClickedSpy.count, 0) + compare(mouseOverlapperDoubleClickedSpy.count, 0) + real_double_click(map, 5, 55) + tryCompare(mouseUpperDoubleClickedSpy, "count", 2) + compare(mouseLowerDoubleClickedSpy.count, 1) + compare(mouseOverlapperDoubleClickedSpy.count, 0) + real_double_click(map, 5, 55) + tryCompare(mouseUpperDoubleClickedSpy, "count", 2) + compare(mouseLowerDoubleClickedSpy.count, 2) + compare(mouseOverlapperDoubleClickedSpy.count, 0) + // declaration order counts on overlap case; overlapping area declared later will get the events + real_double_click(map, 55, 25) + tryCompare(mouseUpperDoubleClickedSpy, "count", 2) + compare(mouseLowerDoubleClickedSpy.count, 2) + compare(mouseOverlapperDoubleClickedSpy.count, 1) + compare(mouseOverlapperPressedSpy.count, 2) + compare(mouseOverlapperReleasedSpy.count, 2) + real_double_click(map, 55, 75) + tryCompare(mouseUpperDoubleClickedSpy, "count", 2) + compare(mouseLowerDoubleClickedSpy.count, 2) + compare(mouseOverlapperDoubleClickedSpy.count, 2) + compare(mouseOverlapperPressedSpy.count, 4) + compare(mouseOverlapperReleasedSpy.count, 4) + // disable overlapping area and check event is delivered to the ones beneath + mouseOverlapper.enabled = false + real_double_click(map, 55, 25) + tryCompare(mouseUpperDoubleClickedSpy, "count", 3) + compare(mouseLowerDoubleClickedSpy.count, 2) + compare(mouseOverlapperDoubleClickedSpy.count, 2) + real_double_click(map, 55, 75) + tryCompare(mouseUpperDoubleClickedSpy, "count", 3) + compare(mouseLowerDoubleClickedSpy.count, 3) + compare(mouseOverlapperDoubleClickedSpy.count, 2) + mouseOverlapper.enabled = true + real_double_click(map, 55, 25) + tryCompare(mouseUpperDoubleClickedSpy, "count", 3) + compare(mouseLowerDoubleClickedSpy.count, 3) + compare(mouseOverlapperDoubleClickedSpy.count, 3) + real_double_click(map, 55, 75) + tryCompare(mouseUpperDoubleClickedSpy, "count", 3) + compare(mouseLowerDoubleClickedSpy.count, 3) + compare(mouseOverlapperDoubleClickedSpy.count, 4) + } + + function test_zzz_basic_press_and_hold() { // _zzz_ to ensure execution last (takes time) + clear_data(); + wait(1000); + real_press_and_hold(map, 5, 5) + compare(mouseUpperPressAndHoldSpy.count, 0) + compare(mouseLowerPressAndHoldSpy.count, 0) + compare(mouseOverlapperPressAndHoldSpy.count, 0) + + mousePress(map,5,25) + wait(1000) // threshold is 800 ms + compare(mouseUpperPressAndHoldSpy.count, 1) + compare(mouseLowerPressAndHoldSpy.count, 0) + compare(mouseOverlapperPressAndHoldSpy.count, 0) + compare(mouseUpper.lastAccepted, true) + compare(mouseUpper.lastButton, Qt.LeftButton) + compare(mouseUpper.lastModifiers, Qt.NoModifier) + compare(mouseUpper.lastWasHeld, true) // notable part + compare(mouseUpper.lastX, 5) + compare(mouseUpper.lastY, 5) // remember 20 offset of the mouse area + mouseRelease(map,5,25) + real_press_and_hold(map, 5, 55) + tryCompare(mouseUpperPressAndHoldSpy, "count", 1) + compare(mouseLowerPressAndHoldSpy.count, 1) + compare(mouseOverlapperPressAndHoldSpy.count, 0) + real_press_and_hold(map, 55, 75) + tryCompare(mouseUpperPressAndHoldSpy, "count", 1) + compare(mouseLowerPressAndHoldSpy.count, 1) + compare(mouseOverlapperPressAndHoldSpy.count, 1) + compare(mouseOverlapper.lastAccepted, true) + compare(mouseOverlapper.lastButton, Qt.LeftButton) + compare(mouseOverlapper.lastModifiers, Qt.NoModifier) + compare(mouseOverlapper.lastWasHeld, true) + compare(mouseOverlapper.lastX, 5) + compare(mouseOverlapper.lastY, 75) + // make sure that the wasHeld is cleared + mouseClick(map, 55, 75) + tryCompare(mouseOverlapper, "lastAccepted", true) + compare(mouseOverlapper.lastButton, Qt.LeftButton) + compare(mouseOverlapper.lastModifiers, Qt.NoModifier) + compare(mouseOverlapper.lastWasHeld, false) + compare(mouseOverlapper.lastX, 5) + compare(mouseOverlapper.lastY, 75) + real_press_and_hold(map, 55, 25) + tryCompare(mouseUpperPressAndHoldSpy, "count", 1) + compare(mouseLowerPressAndHoldSpy.count, 1) + compare(mouseOverlapperPressAndHoldSpy.count, 2) + } + } +} diff --git a/tests/auto/declarative_ui/tst_map_pinch.qml.QTBUG-47970 b/tests/auto/declarative_ui/tst_map_pinch.qml.QTBUG-47970 new file mode 100644 index 0000000..f80e44a --- /dev/null +++ b/tests/auto/declarative_ui/tst_map_pinch.qml.QTBUG-47970 @@ -0,0 +1,576 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtTest 1.0 +import QtLocation 5.5 +import QtPositioning 5.5 +import QtLocation.Test 5.5 + +Item { + // General-purpose elements for the test: + id: page + width: 100 + height: 100 + Plugin { id: testPlugin; name: "qmlgeo.test.plugin"; allowExperimental: true } + + property variant coordinate: QtPositioning.coordinate(10, 11) + + // From QtLocationTest plugin + PinchGenerator { + id: pinchGenerator + anchors.fill: parent + target: page + enabled: false + } + + MouseArea { + id: mouseAreaBottom + anchors.fill: parent + visible: false + } + + Map { + id: map + plugin: testPlugin + center: coordinate; + zoomLevel: 9; + anchors.fill: page + x:0; y:0 + property variant lastPinchEvent: null + property point startPinchPoint1: Qt.point(0,0) + property point startPinchPoint2: Qt.point(0,0) + property point endPinchPoint1: Qt.point(0,0) + property point endPinchPoint2: Qt.point(0,0) + property bool rejectPinch: false + gesture.onPinchStarted: { + map.lastPinchEvent = pinch; + map.startPinchPoint1= pinch.point1; + map.startPinchPoint2= pinch.point2; + if (rejectPinch) + pinch.accepted = false; + } + gesture.onPinchUpdated: map.lastPinchEvent = pinch; + + gesture.onPinchFinished: { + map.lastPinchEvent = pinch; + map.endPinchPoint1 = pinch.point1; + map.endPinchPoint2 = pinch.point2; + } + MouseArea { + id: mouseAreaTop + anchors.fill: parent + visible: false + } + } + + SignalSpy {id: centerSpy; target: map; signalName: 'centerChanged'} + SignalSpy {id: pinchStartedSpy; target: map.gesture; signalName: 'pinchStarted'} + SignalSpy {id: pinchUpdatedSpy; target: map.gesture; signalName: 'pinchUpdated'} + SignalSpy {id: pinchFinishedSpy; target: map.gesture; signalName: 'pinchFinished'} + SignalSpy {id: pinchMaximumZoomLevelChangeSpy; target: map.gesture; signalName: 'maximumZoomLevelChangeChanged'} + SignalSpy {id: gestureEnabledSpy; target: map.gesture; signalName: 'enabledChanged'} + SignalSpy {id: pinchActiveSpy; target: map.gesture; signalName: 'pinchActiveChanged'} + SignalSpy {id: pinchActiveGesturesSpy; target: map.gesture; signalName: 'activeGesturesChanged'} + SignalSpy {id: mapZoomLevelSpy; target: map; signalName: 'zoomLevelChanged'} + SignalSpy {id: mouseAreaTopSpy; target: mouseAreaTop; signalName: 'onPressed'} + SignalSpy {id: mouseAreaBottomSpy; target: mouseAreaBottom; signalName: 'onPressed'} + + TestCase { + when: windowShown + name: "MapPinch" + + function init() + { + map.gesture.activeGestures = MapGestureArea.ZoomGesture + map.gesture.enabled = true + map.rejectPinch = false + map.center = coordinate + map.minimumZoomLevel = 0 + map.maximumZoomLevel = 20 + mouseRelease(mouseAreaTop,0,0) //Fixme: mouse area state gets broken across the tests + mouseAreaBottom.visible = false + mouseAreaTop.visible = false + pinchGenerator.clear() + centerSpy.clear() + pinchStartedSpy.clear() + pinchUpdatedSpy.clear() + pinchFinishedSpy.clear() + pinchMaximumZoomLevelChangeSpy.clear() + gestureEnabledSpy.clear() + pinchActiveSpy.clear() + pinchActiveGesturesSpy.clear() + mapZoomLevelSpy.clear() + mouseAreaTopSpy.clear() + mouseAreaBottomSpy.clear() + } + + //see QDeclarativeGeoMapGestureArea::updatePinch() + function calculateZoom(startPinchPoint1,startPinchPoint2,endPinchPoint1,endPinchPoint2, + width,height, maximumZoomLevelChange, startZoomLevel) + { + var startDistance = Math.sqrt(Math.pow(startPinchPoint2.x - startPinchPoint1.x,2) + + + Math.pow(startPinchPoint2.y - startPinchPoint1.y,2)) + var endDistance = Math.sqrt(Math.pow(endPinchPoint2.x - endPinchPoint1.x,2) + + + Math.pow(endPinchPoint2.y - endPinchPoint1.y,2)) + return 2 * (endDistance - startDistance) * maximumZoomLevelChange / + (width + height) + startZoomLevel; + } + + function initTestCase() + { + //test default properties + compare(map.gesture.enabled, true) + map.gesture.enabled = false + compare(gestureEnabledSpy.count, 1) + compare(map.gesture.enabled, false) + map.gesture.enabled = false + compare(gestureEnabledSpy.count, 1) + compare(map.gesture.enabled, false) + map.gesture.enabled = true + compare(gestureEnabledSpy.count, 2) + compare(map.gesture.enabled, true) + compare(map.gesture.isPinchActive, false) + verify(map.gesture.activeGestures & MapGestureArea.ZoomGesture) + map.gesture.activeGestures = MapGestureArea.NoGesture + compare(map.gesture.activeGestures, MapGestureArea.NoGesture) + compare(pinchActiveGesturesSpy.count, 1) + map.gesture.activeGestures = MapGestureArea.NoGesture + compare(map.gesture.activeGestures, MapGestureArea.NoGesture) + compare(pinchActiveGesturesSpy.count, 1) + map.gesture.activeGestures = MapGestureArea.ZoomGesture | MapGestureArea.PanGesture + compare(map.gesture.activeGestures, MapGestureArea.ZoomGesture | MapGestureArea.PanGesture) + compare(pinchActiveGesturesSpy.count, 2) + map.gesture.activeGestures = MapGestureArea.PanGesture + compare(map.gesture.activeGestures, MapGestureArea.PanGesture) + compare(pinchActiveGesturesSpy.count, 3) + map.gesture.activeGestures = MapGestureArea.ZoomGesture + compare(map.gesture.activeGestures, MapGestureArea.ZoomGesture) + compare(pinchActiveGesturesSpy.count, 4) + compare(map.gesture.maximumZoomLevelChange, 4) + map.gesture.maximumZoomLevelChange = 8 + compare(pinchMaximumZoomLevelChangeSpy.count, 1) + compare (map.gesture.maximumZoomLevelChange, 8) + map.gesture.maximumZoomLevelChange = 8 + compare(pinchMaximumZoomLevelChangeSpy.count, 1) + compare (map.gesture.maximumZoomLevelChange, 8) + map.gesture.maximumZoomLevelChange = 11 // too big + map.gesture.maximumZoomLevelChange = 0.01 // too small + map.gesture.maximumZoomLevelChange = -1 // too small + compare(pinchMaximumZoomLevelChangeSpy.count, 1) + compare (map.gesture.maximumZoomLevelChange, 8) + map.gesture.maximumZoomLevelChange = 2 + compare(pinchMaximumZoomLevelChangeSpy.count, 2) + compare (map.gesture.maximumZoomLevelChange, 2) + } + + + function zoom_in() + { + var startZoomLevel = 9 + map.zoomLevel = startZoomLevel + mapZoomLevelSpy.clear() + map.gesture.maximumZoomLevelChange = 2 + + compare(map.gesture.isPinchActive, false) + pinchGenerator.pinch( + Qt.point(0,50), // point1From + Qt.point(50,50), // point1To + Qt.point(100,50), // point2From + Qt.point(50,50), // point2To + 40, // interval between touch events (swipe1), default 20ms + 40, // interval between touch events (swipe2), default 20ms + 10, // number of touchevents in point1from -> point1to, default 10 + 10); // number of touchevents in point2from -> point2to, default 10 + tryCompare(pinchStartedSpy, "count", 1); + // check the pinch event data for pinchStarted + compare(map.lastPinchEvent.center.x, 50) + compare(map.lastPinchEvent.center.y, 50) + compare(map.lastPinchEvent.angle, 0) + verify((map.lastPinchEvent.point1.x > pinchGenerator.startDragDistance()) + && (map.lastPinchEvent.point1.x < 25)) + compare(map.lastPinchEvent.point1.y, 50) + verify((map.lastPinchEvent.point2.x > 75) + && (map.lastPinchEvent.point2.x < 100 - pinchGenerator.startDragDistance())) + compare(map.lastPinchEvent.point2.y, 50) + compare(map.lastPinchEvent.accepted, true) + compare(map.lastPinchEvent.pointCount, 2) + tryCompare(pinchActiveSpy, "count", 2) // check that pinch is active + compare(map.gesture.isPinchActive, true) + wait(200) // five points, each 40ms + // check the pinch event data for pinchUpdated + compare(map.lastPinchEvent.center.x, 50) + compare(map.lastPinchEvent.center.y, 50) + compare(map.lastPinchEvent.angle, 0) + verify((map.lastPinchEvent.point1.x) > 25 && (map.lastPinchEvent.point1.x <= 50)) + compare(map.lastPinchEvent.point1.y, 50) + verify((map.lastPinchEvent.point2.x) >= 50 && (map.lastPinchEvent.point2.x < 85)) + compare(map.lastPinchEvent.point2.y, 50) + compare(map.lastPinchEvent.accepted, true) + compare(map.lastPinchEvent.pointCount, 2) + tryCompare(pinchFinishedSpy, "count", 1); + compare(map.gesture.isPinchActive, false) + // check the pinch event data for pinchFinished + compare(map.lastPinchEvent.center.x, 50) + compare(map.lastPinchEvent.center.y, 50) + compare(map.lastPinchEvent.angle, 0) + verify((map.lastPinchEvent.point1.x) > 35 && (map.lastPinchEvent.point1.x <= 50)) + compare(map.lastPinchEvent.point1.y, 50) + verify((map.lastPinchEvent.point2.x) >= 50 && (map.lastPinchEvent.point2.x < 65)) + compare(map.lastPinchEvent.point2.y, 50) + compare(map.lastPinchEvent.accepted, true) + compare(map.lastPinchEvent.pointCount, 0) + + verify(pinchUpdatedSpy.count >= 5); // verify 'sane' number of updates received + compare(pinchActiveSpy.count,3) + compare(map.gesture.isPinchActive, false) + compare(mapZoomLevelSpy.count, pinchUpdatedSpy.count) + var endZoomLevel = calculateZoom( map.startPinchPoint1, map.startPinchPoint2, + map.endPinchPoint1, map.endPinchPoint2, + map.width,map.height, + map.gesture.maximumZoomLevelChange,startZoomLevel) + compare(map.zoomLevel, endZoomLevel) + } + + function test_zoom_in() + { + zoom_in() + } + + function test_zoom_in_with_top_filtering() + { + mouseAreaTop.visible = true + zoom_in() + tryCompare(mouseAreaTopSpy, "count", 1) + } + + function test_zoom_in_with_below_filtering() + { + mouseAreaBottom.visible=true + zoom_in() + tryCompare(mouseAreaBottomSpy, "count",0) + } + + function zoom_out() + { + var startZoomLevel = 7.8 + map.zoomLevel = startZoomLevel + map.gesture.maximumZoomLevelChange = 2 + compare (map.gesture.maximumZoomLevelChange, 2) + mapZoomLevelSpy.clear() + pinchGenerator.pinch(Qt.point(45,50), Qt.point(0,50), + Qt.point(55,50), Qt.point(100,50), + 40, 40, 10, 10); + tryCompare(pinchStartedSpy, "count", 1); + tryCompare(pinchFinishedSpy, "count", 1); + compare(map.gesture.isPinchActive, false) + verify(pinchUpdatedSpy.count >= 5); // verify 'sane' number of updates received + compare(mapZoomLevelSpy.count, pinchUpdatedSpy.count) + var endZoomLevel = calculateZoom( map.startPinchPoint1, map.startPinchPoint2, + map.endPinchPoint1, map.endPinchPoint2, + map.width,map.height, + map.gesture.maximumZoomLevelChange,startZoomLevel) + + compare(map.zoomLevel, endZoomLevel) + } + + function test_zoom_out() + { + zoom_out() + } + + function test_zoom_out_with_top_filtering() + { + mouseAreaTop.visible=true + zoom_out() + tryCompare(mouseAreaTopSpy, "count", 1) + } + + function test_zoom_out_with_below_filtering() + { + mouseAreaBottom.visible=true + zoom_out() + tryCompare(mouseAreaBottomSpy, "count",0) + } + + function test_zoom_in_and_back_out() + { + // direction change during same pinch + var startZoomLevel = 7.8 + map.gesture.maximumZoomLevelChange = 2 + map.zoomLevel = startZoomLevel + pinchGenerator.pinch(Qt.point(0,50), Qt.point(100,50), + Qt.point(100,50),Qt.point(0,50), + 40, 40, 10, 10); + tryCompare(pinchStartedSpy, "count", 1); + tryCompare(pinchFinishedSpy, "count", 1); + verify(pinchUpdatedSpy.count >= 5); // verify 'sane' number of updates received + var endZoomLevel = calculateZoom( map.startPinchPoint1, map.startPinchPoint2, + map.endPinchPoint1, map.endPinchPoint2, + map.width,map.height, + map.gesture.maximumZoomLevelChange,startZoomLevel) + compare(map.zoomLevel, endZoomLevel) // should remain the same + } + + function test_zoom_in_with_different_change_level() + { + var startZoomLevel = 8 + map.zoomLevel = startZoomLevel + map.gesture.maximumZoomLevelChange = 4 + compare (map.gesture.maximumZoomLevelChange, 4) + pinchGenerator.pinch(Qt.point(0,50),Qt.point(50,50), + Qt.point(100,50),Qt.point(50,50), + 40, 40, 10, 10); + tryCompare(pinchFinishedSpy, "count", 1); + var endZoomLevel = calculateZoom( map.startPinchPoint1, map.startPinchPoint2, + map.endPinchPoint1, map.endPinchPoint2, + map.width,map.height, + map.gesture.maximumZoomLevelChange,startZoomLevel) + compare(map.zoomLevel, endZoomLevel) + } + + function test_zoom_out_with_different_change_level() + { + var startZoomLevel = 8 + map.gesture.maximumZoomLevelChange = 1 + map.zoomLevel = startZoomLevel + compare (map.gesture.maximumZoomLevelChange, 1) + pinchGenerator.pinch(Qt.point(50,50), Qt.point(0,50), + Qt.point(50,50), Qt.point(100,50), + 40, 40, 10, 10); + tryCompare(pinchFinishedSpy, "count", 1); + var endZoomLevel = calculateZoom( map.startPinchPoint1, map.startPinchPoint2, + map.endPinchPoint1, map.endPinchPoint2, + map.width,map.height, + map.gesture.maximumZoomLevelChange,startZoomLevel) + compare(map.zoomLevel, endZoomLevel) + } + + function test_zoom_in_below_minimum_zoom_level() + { + map.zoomLevel = 8 + map.gesture.maximumZoomLevelChange = 4 + map.minimumZoomLevel = 7 + pinchGenerator.pinch(Qt.point(0,50),Qt.point(50,50),Qt.point(100,50),Qt.point(50,50)); + wait(250); + tryCompare(pinchFinishedSpy, "count", 1); + compare(map.zoomLevel, 7) + } + + function test_zoom_out_above_maximum_zoom_level() + { + map.gesture.maximumZoomLevelChange = 4 + map.maximumZoomLevel = 8 + pinchGenerator.pinch(Qt.point(50,50), Qt.point(0,50),Qt.point(50,50), Qt.point(100,50)); + tryCompare(pinchFinishedSpy, "count", 1); + compare(map.zoomLevel, 8) + } + + function test_pinch_when_max_and_min_are_same() + { + map.maximumZoomLevel = 8 + map.minimumZoomLevel = 8 + compare(map.maximumZoomLevel, 8) + compare(map.minimumZoomLevel, 8) + pinchGenerator.pinch(Qt.point(0,50),Qt.point(50,50),Qt.point(100,50),Qt.point(50,50)); + tryCompare(pinchFinishedSpy, "count", 1); + compare(map.zoomLevel, 8) + map.minimumZoomLevel = 1 + map.maximumZoomLevel = 20 + } + + function test_pinch_when_max_min_is_not_where_map_zoomLevel_currently_is() + { + map.gesture.maximumZoomLevelChange = 4 + map.minimumZoomLevel = 4 + map.maximumZoomLevel = 6 + // first when above the zoom range + map.zoomLevel = 5 + pinchGenerator.pinch(Qt.point(50,50),Qt.point(0,50),Qt.point(50,50),Qt.point(100,50)); // zoom out + tryCompare(pinchFinishedSpy, "count", 1); + compare(map.zoomLevel, 6) + map.zoomLevel = 5 + pinchGenerator.pinch(Qt.point(0,50),Qt.point(50,50),Qt.point(100,50),Qt.point(50,50)); // zoom in + tryCompare(pinchFinishedSpy, "count", 2); + compare(map.zoomLevel, 4) + pinchGenerator.pinch(Qt.point(0,50),Qt.point(50,50),Qt.point(100,50),Qt.point(50,50)); // zoom in + tryCompare(pinchFinishedSpy, "count", 3); + compare(map.zoomLevel, 4) + map.minimumZoomLevel = 1 + map.maximumZoomLevel = 20 + } + + function test_pinch_while_pinch_area_is_disabled() + { + map.zoomLevel = 7.5 + map.gesture.enabled = false + map.gesture.maximumZoomLevelChange = 2 + pinchGenerator.pinch(Qt.point(50,50), Qt.point(0,50), + Qt.point(50,50), Qt.point(100,50), + 40, 40, 10, 10); + wait(200); + compare(pinchActiveSpy.count, 0) + compare(map.gesture.isPinchActive, false) + compare(pinchStartedSpy.count, 0) + compare(pinchUpdatedSpy.count, 0); + compare(pinchFinishedSpy.count, 0); + compare(map.zoomLevel, 7.5) + pinchGenerator.stop() + } + + function test_pinch_disabling_during_pinching() + { + var startZoomLevel = 7.5 + map.zoomLevel = startZoomLevel + pinchGenerator.pinch(Qt.point(50,50), Qt.point(0,50), + Qt.point(50,50), Qt.point(100,50), + 40, 40, 10, 10); + tryCompare(pinchStartedSpy, "count", 1); + // check that pinch is active. then disable the pinch. pinch area should still process + // as long as it is active + compare(pinchActiveSpy.count,2) + compare(map.gesture.isPinchActive, true) + map.gesture.enabled = false + tryCompare(pinchFinishedSpy, "count", 1) + var pinchupdates = pinchUpdatedSpy.count + verify(pinchupdates > 0) + compare(pinchActiveSpy.count,3) + compare(map.gesture.isPinchActive, false) + var endZoomLevel = calculateZoom( map.startPinchPoint1, map.startPinchPoint2, + map.endPinchPoint1, map.endPinchPoint2, + map.width,map.height, + map.gesture.maximumZoomLevelChange,startZoomLevel) + compare(map.zoomLevel, endZoomLevel) + pinchGenerator.pinch(Qt.point(50,50), Qt.point(0,50), + Qt.point(50,50), Qt.point(100,50), + 40, 40, 10, 10); + compare(map.zoomLevel, endZoomLevel) + } + + function test_check_no_active_gestures() + { + map.zoomLevel = 8.5 + map.gesture.activeGestures = MapGestureArea.NoGesture + + pinchGenerator.pinch(Qt.point(50,50), Qt.point(0,50), + Qt.point(50,50), Qt.point(100,50), + 40, 40, 10, 10); + tryCompare(pinchStartedSpy, "count", 0); + wait(300); + compare(pinchUpdatedSpy.count, 0); + compare(pinchStartedSpy.count, 0); + compare(map.zoomLevel, 8.5) + pinchGenerator.stop() + } + + function test_changing_zoom_level_during_active_pinch_zoom() + { + var startZoomLevel = 8.5 + map.zoomLevel = startZoomLevel + map.gesture.maximumZoomLevelChange = 2 + pinchGenerator.pinch(Qt.point(50,50), Qt.point(0,50), + Qt.point(50,50), Qt.point(100,50), + 40, 40, 10, 10); + tryCompare(pinchStartedSpy, "count", 1); + tryCompare(pinchActiveSpy, "count", 2) + compare(map.gesture.isPinchActive, true) + map.zoomLevel = 3 // will get overridden by pinch + tryCompare(pinchFinishedSpy, "count", 1); + verify(pinchUpdatedSpy.count >= 5); // verify 'sane' number of updates received + var endZoomLevel = calculateZoom( map.startPinchPoint1, map.startPinchPoint2, + map.endPinchPoint1, map.endPinchPoint2, + map.width,map.height, + map.gesture.maximumZoomLevelChange,startZoomLevel) + compare(map.zoomLevel, endZoomLevel) + } + + function test_zoom_below_and_above_plugin_support() + { + map.gesture.maximumZoomLevelChange = 4 + map.zoomLevel = map.minimumZoomLevel + 0.5 + pinchGenerator.pinch(Qt.point(0,50),Qt.point(50,50), + Qt.point(100,50),Qt.point(50,50), + 40, 40, 10, 10); + tryCompare(pinchFinishedSpy, "count", 1); + compare(map.zoomLevel, map.minimumZoomLevel) + map.zoomLevel = map.maximumZoomLevel - 0.5 + pinchGenerator.pinch(Qt.point(50,50), Qt.point(0,50),Qt.point(50,50), Qt.point(100,50)); + tryCompare(pinchFinishedSpy, "count", 2); + compare(map.zoomLevel, map.maximumZoomLevel) + } + + function test_check_pinch_accepted() + { + map.zoomLevel = 10 + map.rejectPinch = true + pinchGenerator.pinch(Qt.point(0,50),Qt.point(50,50), + Qt.point(100,50),Qt.point(50,50), + 40, 40, 10, 10); + wait(300) + compare(pinchUpdatedSpy.count, 0) + compare(pinchFinishedSpy.count, 0) + compare(map.gesture.isPinchActive, false) + compare(map.zoomLevel, 10) + var startZoomLevel = 10 + map.rejectPinch = false + wait(500) + pinchGenerator.pinch(Qt.point(0,50),Qt.point(50,50),Qt.point(100,50),Qt.point(50,50),40, 40, 10, 10); + tryCompare(pinchFinishedSpy, "count", 1) + var endZoomLevel = calculateZoom( map.startPinchPoint1, map.startPinchPoint2, + map.endPinchPoint1, map.endPinchPoint2, + map.width,map.height, + map.gesture.maximumZoomLevelChange,startZoomLevel) + compare(map.zoomLevel, endZoomLevel) + compare(map.lastPinchEvent.accepted, true) + } + + function test_moving_center() + { + pinchGenerator.pinch(Qt.point(0, 50), Qt.point(50,100), Qt.point(50,0), Qt.point(100, 50)) + tryCompare(pinchStartedSpy, "count", 1) + compare(map.lastPinchEvent.center.x, (map.lastPinchEvent.point1.x + map.lastPinchEvent.point2.x) /2) + compare(map.lastPinchEvent.center.x, (map.lastPinchEvent.point1.y + map.lastPinchEvent.point2.y) /2) + tryCompare(pinchFinishedSpy, "count", 1) + compare(map.lastPinchEvent.center.x, (map.lastPinchEvent.point1.x + map.lastPinchEvent.point2.x) /2) + compare(map.lastPinchEvent.center.x, (map.lastPinchEvent.point1.y + map.lastPinchEvent.point2.y) /2) + // sanity check that we are not comparing wrong (points) with wrong (center) and calling it a success + verify((map.lastPinchEvent.center.x > 50) && (map.lastPinchEvent.center.x < 100)) + verify((map.lastPinchEvent.center.y > 50) && (map.lastPinchEvent.center.y < 100)) + } + + function test_angle_between_points() + { + // todo calculate the angle from points for comparison + pinchGenerator.pinch(Qt.point(0,0), Qt.point(0,100), Qt.point(100,100), Qt.point(100,0)) + tryCompare(pinchStartedSpy, "count", 1) + verify(map.lastPinchEvent.angle >= -45 && map.lastPinchEvent.angle < -20) + tryCompare(pinchFinishedSpy, "count", 1) + verify(map.lastPinchEvent.angle >= 20 && map.lastPinchEvent.angle <= 45) + } + } +} diff --git a/tests/auto/doublevectors/doublevectors.pro b/tests/auto/doublevectors/doublevectors.pro new file mode 100644 index 0000000..841a19e --- /dev/null +++ b/tests/auto/doublevectors/doublevectors.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_doublevectors + +SOURCES += tst_doublevectors.cpp + +QT += positioning-private testlib diff --git a/tests/auto/doublevectors/tst_doublevectors.cpp b/tests/auto/doublevectors/tst_doublevectors.cpp new file mode 100644 index 0000000..ae1b2ba --- /dev/null +++ b/tests/auto/doublevectors/tst_doublevectors.cpp @@ -0,0 +1,293 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include +#include + +QT_USE_NAMESPACE + +class tst_doubleVectors : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + // 2D + void constructor2dTest(); + void basicFunctions2dTest(); + void unaryOperator2dTest(); + void binaryOperator2dTest(); + + // 3D + void constructor3dTest(); + void basicFunctions3dTest(); + void unaryOperator3dTest(); + void binaryOperator3dTest(); +}; + +// DoubleVector2D + +void tst_doubleVectors::constructor2dTest() +{ + // empty constructor, since it sets to 0, we should check, in case people rely on it + QDoubleVector2D v1; + QCOMPARE(v1.x(), 0.0); + QCOMPARE(v1.y(), 0.0); + QCOMPARE(v1.isNull(), true); + v1 = QDoubleVector2D(1.1, -2.5); // assignment and constructor + QCOMPARE(v1.x(), 1.1); + QCOMPARE(v1.y(), -2.5); + QDoubleVector2D v2(v1); // copy constructor + QCOMPARE(v2.x(), 1.1); + QCOMPARE(v2.y(), -2.5); + const QDoubleVector3D v3d(2.2, 3.3, 4.4); + QDoubleVector2D v3(v3d); // constructor from 3d vector, just copies x and y + QCOMPARE(v3.x(), 2.2); + QCOMPARE(v3.y(), 3.3); + QCOMPARE(v3.isNull(), false); +} + +void tst_doubleVectors::basicFunctions2dTest() +{ + QDoubleVector2D v1; + v1.setX(3.0); + v1.setY(4.0); + QCOMPARE(v1.x(), 3.0); + QCOMPARE(v1.y(), 4.0); + QCOMPARE(v1.length(), 5.0); + QDoubleVector2D v2 = v1.normalized(); + QCOMPARE(v1.lengthSquared(), 25.0); + v1.normalize(); + QCOMPARE(v1.x(), 3.0/5.0); + QCOMPARE(v1.y(), 4.0/5.0); + QCOMPARE(v2.x(), 3.0/5.0); + QCOMPARE(v2.y(), 4.0/5.0); + + QDoubleVector3D v3d = v1.toVector3D(); + QCOMPARE(v3d.x(), 3.0/5.0); + QCOMPARE(v3d.y(), 4.0/5.0); + QCOMPARE(v3d.z(), 0.0); +} + +void tst_doubleVectors::unaryOperator2dTest() +{ + QDoubleVector2D v1(1.1, 2.2); + QDoubleVector2D v2 = -v1; + QCOMPARE(v2.x(), -1.1); + QCOMPARE(v2.y(), -2.2); + + v1 *= 2.0; + QCOMPARE(v1.x(), 2.2); + QCOMPARE(v1.y(), 4.4); + + v2 /= 2.0; + QCOMPARE(v2.x(), -0.55); + QCOMPARE(v2.y(), -1.1); + + v1 += v2; + QCOMPARE(v1.x(), 1.65); + QCOMPARE(v1.y(), 3.3); + + v1 -= v2; + QCOMPARE(v1.x(), 2.2); + QCOMPARE(v1.y(), 4.4); + + v1 *= v2; + QCOMPARE(v1.x(), -1.21); + QCOMPARE(v1.y(), -4.84); +} + +void tst_doubleVectors::binaryOperator2dTest() +{ + QDoubleVector2D v1(1.1, 2.2); + QDoubleVector2D v2(3.4, 4.4); + QDoubleVector2D v3 = v1 + v2; + QCOMPARE(v3.x(), 4.5); + QCOMPARE(v3.y(), 6.6); + + QDoubleVector2D v4 = v1 - v2; + QCOMPARE(v4.x(), -2.3); + QCOMPARE(v4.y(), -2.2); + + QDoubleVector2D v5 = v2 * 2; + QCOMPARE(v5.x(), 6.8); + QCOMPARE(v5.y(), 8.8); + + QDoubleVector2D v6 = 2 * v2; + QCOMPARE(v6.x(), 6.8); + QCOMPARE(v6.y(), 8.8); + + QDoubleVector2D v7 = v2 / 2; + QCOMPARE(v7.x(), 1.7); + QCOMPARE(v7.y(), 2.2); + + double d = QDoubleVector2D::dotProduct(v1, v2); + QCOMPARE(d, 13.42); + + QCOMPARE(v5 == v6, true); + QCOMPARE(v5 != v6, false); + QCOMPARE(v6 == v7, false); + QCOMPARE(v6 != v7, true); +} + + + +// DoubleVector3D + + +void tst_doubleVectors::constructor3dTest() +{ + // empty constructor, since it sets to 0, we should check, in case people rely on it + QDoubleVector3D v1; + QCOMPARE(v1.x(), 0.0); + QCOMPARE(v1.y(), 0.0); + QCOMPARE(v1.z(), 0.0); + QCOMPARE(v1.isNull(), true); + v1 = QDoubleVector3D(1.1, -2.5, 3.2); // assignment and constructor + QCOMPARE(v1.x(), 1.1); + QCOMPARE(v1.y(), -2.5); + QCOMPARE(v1.z(), 3.2); + QDoubleVector3D v2(v1); // copy constructor + QCOMPARE(v2.x(), 1.1); + QCOMPARE(v2.y(), -2.5); + QCOMPARE(v2.z(), 3.2); + const QDoubleVector2D v2d(2.2, 3.3); + QDoubleVector3D v3(v2d); // constructor from 3d vector, just copies x and y + QCOMPARE(v3.x(), 2.2); + QCOMPARE(v3.y(), 3.3); + QCOMPARE(v3.z(), 0.0); + QCOMPARE(v3.isNull(), false); + const QDoubleVector2D v2d2(2.2, 3.3); + QDoubleVector3D v4(v2d2, -13.6); // constructor from 2d vector + QCOMPARE(v4.x(), 2.2); + QCOMPARE(v4.y(), 3.3); + QCOMPARE(v4.z(), -13.6); +} + +void tst_doubleVectors::basicFunctions3dTest() +{ + QDoubleVector3D v1; + v1.setX(2.0); + v1.setY(3.0); + v1.setZ(6.0); + QCOMPARE(v1.x(), 2.0); + QCOMPARE(v1.y(), 3.0); + QCOMPARE(v1.z(), 6.0); + QCOMPARE(v1.length(), 7.0); + QDoubleVector3D v2 = v1.normalized(); + QCOMPARE(v1.lengthSquared(), 49.0); + v1.normalize(); + QCOMPARE(v1.x(), 2.0/7.0); + QCOMPARE(v1.y(), 3.0/7.0); + QCOMPARE(v1.z(), 6.0/7.0); + QCOMPARE(v2.x(), 2.0/7.0); + QCOMPARE(v2.y(), 3.0/7.0); + QCOMPARE(v2.z(), 6.0/7.0); + + QDoubleVector2D v2d = v1.toVector2D(); + QCOMPARE(v2d.x(), 2.0/7.0); + QCOMPARE(v2d.y(), 3.0/7.0); +} + +void tst_doubleVectors::unaryOperator3dTest() +{ + QDoubleVector3D v1(1.1, 2.2, 3.3); + QDoubleVector3D v2 = -v1; + QCOMPARE(v2.x(), -1.1); + QCOMPARE(v2.y(), -2.2); + QCOMPARE(v2.z(), -3.3); + + v1 *= 2.0; + QCOMPARE(v1.x(), 2.2); + QCOMPARE(v1.y(), 4.4); + QCOMPARE(v1.z(), 6.6); + + v2 /= 2.0; + QCOMPARE(v2.x(), -0.55); + QCOMPARE(v2.y(), -1.1); + QCOMPARE(v2.z(), -1.65); + + v1 += v2; + QCOMPARE(v1.x(), 1.65); + QCOMPARE(v1.y(), 3.3); + QCOMPARE(v1.z(), 4.95); + + v1 -= v2; + QCOMPARE(v1.x(), 2.2); + QCOMPARE(v1.y(), 4.4); + QCOMPARE(v1.z(), 6.6); + + v1 *= v2; + QCOMPARE(v1.x(), -1.21); + QCOMPARE(v1.y(), -4.84); + QCOMPARE(v1.z(), -10.89); +} + +void tst_doubleVectors::binaryOperator3dTest() +{ + QDoubleVector3D v1(1.1, 2.2, 3.3); + QDoubleVector3D v2(3.4, 4.4, 5.5); + QDoubleVector3D v3 = v1 + v2; + QCOMPARE(v3.x(), 4.5); + QCOMPARE(v3.y(), 6.6); + QCOMPARE(v3.z(), 8.8); + + QDoubleVector3D v4 = v1 - v2; + QCOMPARE(v4.x(), -2.3); + QCOMPARE(v4.y(), -2.2); + QCOMPARE(v4.z(), -2.2); + + QDoubleVector3D v5 = v2 * 2; + QCOMPARE(v5.x(), 6.8); + QCOMPARE(v5.y(), 8.8); + QCOMPARE(v5.z(), 11.0); + + QDoubleVector3D v6 = 2 * v2; + QCOMPARE(v6.x(), 6.8); + QCOMPARE(v6.y(), 8.8); + QCOMPARE(v6.z(), 11.0); + + QDoubleVector3D v7 = v2 / 2; + QCOMPARE(v7.x(), 1.7); + QCOMPARE(v7.y(), 2.2); + QCOMPARE(v7.z(), 2.75); + + double d = QDoubleVector3D::dotProduct(v1, v2); + QCOMPARE(d, 31.57); + + QCOMPARE(v5 == v6, true); + QCOMPARE(v5 != v6, false); + QCOMPARE(v6 == v7, false); + QCOMPARE(v6 != v7, true); +} + +QTEST_APPLESS_MAIN(tst_doubleVectors) + +#include "tst_doublevectors.moc" diff --git a/tests/auto/geotestplugin/geotestplugin.json b/tests/auto/geotestplugin/geotestplugin.json new file mode 100644 index 0000000..5272171 --- /dev/null +++ b/tests/auto/geotestplugin/geotestplugin.json @@ -0,0 +1,19 @@ +{ + "Keys": ["qmlgeo.test.plugin"], + "Provider": "qmlgeo.test.plugin", + "Version": 100, + "Experimental": true, + "Features": [ + "OfflineMappingFeature", + "OfflineRoutingFeature", + "AlternativeRoutesFeature", + "ExcludeAreasRoutingFeature", + "RouteUpdatesFeature", + "OfflineGeocodingFeature", + "ReverseGeocodingFeature", + "OfflinePlacesFeature", + "SavePlaceFeature", + "SaveCategoryFeature", + "SearchSuggestionsFeature" + ] +} diff --git a/tests/auto/geotestplugin/geotestplugin.pro b/tests/auto/geotestplugin/geotestplugin.pro new file mode 100644 index 0000000..fb3f1b3 --- /dev/null +++ b/tests/auto/geotestplugin/geotestplugin.pro @@ -0,0 +1,22 @@ +TARGET = qtgeoservices_qmltestplugin +QT += location-private positioning-private testlib + +PLUGIN_TYPE = geoservices +PLUGIN_CLASS_NAME = TestGeoServicePlugin +PLUGIN_EXTENDS = - +load(qt_plugin) + +HEADERS += qgeocodingmanagerengine_test.h \ + qgeoserviceproviderplugin_test.h \ + qgeoroutingmanagerengine_test.h \ + qplacemanagerengine_test.h \ + qgeotiledmappingmanagerengine_test.h \ + qgeotiledmap_test.h \ + qgeotilefetcher_test.h + +SOURCES += qgeoserviceproviderplugin_test.cpp + +OTHER_FILES += \ + geotestplugin.json \ + place_data.json +RESOURCES += testdata.qrc diff --git a/tests/auto/geotestplugin/place_data.json b/tests/auto/geotestplugin/place_data.json new file mode 100644 index 0000000..2006281 --- /dev/null +++ b/tests/auto/geotestplugin/place_data.json @@ -0,0 +1,145 @@ +{ + "categories": [ + { + "name": "Accommodation", + "id": "4b79794f-e146-4adc-9bdf-68c06e7209fd" + }, + { + "name": "Hotel", + "id": "70ab5807-26d2-46be-a860-dc48f17133f0", + "parentId": "4b79794f-e146-4adc-9bdf-68c06e7209fd" + }, + { + "name": "Motel", + "id": "e0478f8a-fe8f-4bf9-8392-c1910e49223f", + "parentId": "4b79794f-e146-4adc-9bdf-68c06e7209fd" + }, + { + "name": "Camping", + "id": "b0434495-9429-4c9f-96e5-75f52f0b8dc8", + "parentId": "4b79794f-e146-4adc-9bdf-68c06e7209fd" + }, + { + "name": "Park", + "id": "c2e1252c-b997-44fc-8165-e53dd00f66a7" + } + ], + + "places": [ + { + "name": "Park View Hotel", + "id": "4dcc74ce-fdeb-443e-827c-367438017cf1", + "categories": [ "70ab5807-26d2-46be-a860-dc48f17133f0" ], + "location": { + "latitude": 0.1001, + "longitude": 0.1001 + }, + "recommendations": [ + "8f72057a-54b2-4e95-a7bb-97b4d2b5721e", + "dacb2181-3f67-4e6a-bd4d-635e99ad5b03" + ], + "reviews": [ + { + "title": "Park View Review 1", + "text": "Park View Review 1 Text", + "dateTime": "13:01 22-09-2004", + "language": "en", + "rating": 3.5, + "reviewId": "0001" + }, + { + "title": "Park View Review 2", + "text": "Park View Review 2 Text", + "dateTime": "04:17 14-09-2005", + "language": "en", + "rating": 1, + "reviewId": "0002" + }, + { + "title": "Park View Review 3", + "text": "Park View Review 3 Text", + "dateTime": "04:12 14-10-2005", + "language": "en", + "rating": 5, + "reviewId": "0003" + }, + { + }, + { + "title": "Park View Review 5", + "text": "Park View Review 5 Text", + "dateTime": "14:53 20-11-2005", + "language": "en", + "rating": 2.3, + "reviewId": "0005" + } + ], + "images": [ + { + "url": "http://somewhere.com/image1.png", + "imageId": "0001", + "mimeType": "image/png" + }, + { + "url": "http://somewhere.com/image2.png", + "imageId": "0002", + "mimeType": "image/png" + }, + { + "url": "http://somewhere.com/image3.png", + "imageId": "0003", + "mimeType": "image/png" + }, + { + }, + { + "url": "http://somewhere.com/image5.png", + "imageId": "0005", + "mimeType": "image/png" + } + ], + "editorials": [ + { + "title": "Editorial 1", + "text": "Editorial 1 Text", + "language": "en" + }, + { + "title": "Editorial 2", + "text": "Editorial 2 Text", + "language": "en" + }, + { + "title": "Editorial 3", + "text": "Editorial 3 Text", + "language": "en" + }, + { + }, + { + "title": "Editorial 5", + "text": "Editorial 5 Text", + "language": "en" + } + ] + }, + { + "name": "Sea View Hotel", + "id": "8f72057a-54b2-4e95-a7bb-97b4d2b5721e", + "categories": [ "70ab5807-26d2-46be-a860-dc48f17133f0" ], + "location": { + "latitude": 0.1002, + "longitude": 0.1002 + } + }, + { + "name": "Country Gardens", + "id": "dacb2181-3f67-4e6a-bd4d-635e99ad5b03", + "categories": [ "c2e1252c-b997-44fc-8165-e53dd00f66a7" ], + "location": { + "latitude": 0.1001, + "longitude": 0.1002 + } + } + ] +} diff --git a/tests/auto/geotestplugin/qgeocodingmanagerengine_test.h b/tests/auto/geotestplugin/qgeocodingmanagerengine_test.h new file mode 100644 index 0000000..1d9f079 --- /dev/null +++ b/tests/auto/geotestplugin/qgeocodingmanagerengine_test.h @@ -0,0 +1,274 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODINGMANAGERENGINE_TEST_H +#define QGEOCODINGMANAGERENGINE_TEST_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_USE_NAMESPACE + + +class GeocodeReplyTest :public QGeoCodeReply +{ + Q_OBJECT +public: + GeocodeReplyTest(QObject *parent = 0) : QGeoCodeReply (parent) {} + + void callAddLocation ( const QGeoLocation & location ) {addLocation(location);} + void callSetError ( Error error, const QString & errorString ) {setError(error, errorString);} + void callSetFinished ( bool finished ) {setFinished(finished);} + void callSetLimit ( int limit ) {setLimit(limit);} + void callSetOffset ( int offset ) {setOffset(offset);} + void callSetLocations ( const QList & locations ) {setLocations(locations);} + void callSetViewport ( const QGeoShape &viewport ) {setViewport(viewport);} + void abort() { + emit aborted(); + } +Q_SIGNALS: + void aborted(); +}; + +class QGeoCodingManagerEngineTest: public QGeoCodingManagerEngine + +{ +Q_OBJECT +public: + QGeoCodingManagerEngineTest(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) : + QGeoCodingManagerEngine(parameters), + validateWellKnownValues_(false), + finishRequestImmediately_(true), + supported_(true), + geocodeReply_(0), + timerId_(0), + errorCode_(QGeoCodeReply::NoError) + { + Q_UNUSED(error) + Q_UNUSED(errorString) + if (parameters.contains("supported")) + supported_ = qvariant_cast(parameters.value("supported")); + if (parameters.contains("finishRequestImmediately")) + finishRequestImmediately_ = qvariant_cast(parameters.value("finishRequestImmediately")); + if (parameters.contains("validateWellKnownValues")) + validateWellKnownValues_ = qvariant_cast(parameters.value("validateWellKnownValues")); + + setLocale(QLocale (QLocale::German, QLocale::Germany)); + } + + QGeoCodeReply* geocode(const QString &searchString, + int limit = -1, + int offset = 0, + const QGeoShape &bounds = QGeoShape()) + { + geocodeReply_ = new GeocodeReplyTest(); + connect(geocodeReply_, SIGNAL(aborted()), this, SLOT(requestAborted())); + geocodeReply_->callSetViewport(bounds); + + if (searchString.length() == 1) { + errorString_ = searchString; + errorCode_ = (QGeoCodeReply::Error)searchString.toInt(); + } else { + errorString_ = ""; + errorCode_ = QGeoCodeReply::NoError; + } + + if (errorCode_ == QGeoCodeReply::NoError) + setLocations(geocodeReply_, searchString, limit, offset); + + if (finishRequestImmediately_) { + // check if we should finish with error + if (errorCode_) { + geocodeReply_->callSetError(errorCode_, errorString_); + } else { + geocodeReply_->callSetFinished(true); + } + } else { + // we only allow serialized requests in QML - previous must have been aborted + Q_ASSERT(timerId_ == 0); + timerId_ = startTimer(200); + } + return static_cast(geocodeReply_); + } + + QGeoCodeReply* geocode(const QGeoAddress & address, const QGeoShape &bounds) + { + geocodeReply_ = new GeocodeReplyTest(); + connect(geocodeReply_, SIGNAL(aborted()), this, SLOT(requestAborted())); + geocodeReply_->callSetViewport(bounds); + + if (address.street().startsWith("error")) { + errorString_ = address.street(); + errorCode_ = (QGeoCodeReply::Error)address.county().toInt(); + } else { + errorString_ = ""; + errorCode_ = QGeoCodeReply::NoError; + } + // 1. Check if we are to validate values + if (validateWellKnownValues_) { + if (address.street() != "wellknown street") { + geocodeReply_->callSetError(QGeoCodeReply::EngineNotSetError, address.street()); + } else { + geocodeReply_->callSetError(QGeoCodeReply::NoError,address.street()); + } + } + + // 2. Set the locations into the reply + setLocations(geocodeReply_, address); + + // 3. Finish the request + if (finishRequestImmediately_) { + // check if we should finish with error + if (errorCode_) { + geocodeReply_->callSetError(errorCode_, errorString_); + } else { + geocodeReply_->callSetFinished(true); + } + } else { + // we only allow serialized requests in QML - previous must have been aborted + Q_ASSERT(timerId_ == 0); + timerId_ = startTimer(200); + } + return static_cast(geocodeReply_); + } + +public Q_SLOTS: + void requestAborted() + { + if (timerId_) { + killTimer(timerId_); + timerId_ = 0; + } + errorString_ = ""; + errorCode_ = QGeoCodeReply::NoError; + } + +public: + void setLocations(GeocodeReplyTest* reply, const QString searchString, int limit, int offset) + { + if (limit < 0) + limit = 0; + for (int i = 0; i < limit; ++i) { + QGeoLocation location; + QGeoAddress address; + address.setStreet(searchString); + address.setCounty(QString::number(offset)); + location.setAddress(address); + reply->callAddLocation(location); + } + } + + void setLocations(GeocodeReplyTest* reply, const QGeoAddress& address) + { + int count = address.county().toInt(); + + for (int i = 0; i < count; ++i) { + QGeoLocation location; + location.setAddress(address); + reply->callAddLocation(location); + } + } + + void setLocations(GeocodeReplyTest* reply, const QGeoCoordinate & coordinate) + { + for (int i = 0; i < coordinate.longitude(); ++i) { + QGeoLocation location; + location.setCoordinate(coordinate); + reply->callAddLocation(location); + } + } + + QGeoCodeReply* reverseGeocode(const QGeoCoordinate &coordinate, const QGeoShape &bounds) + { + geocodeReply_ = new GeocodeReplyTest(); + connect(geocodeReply_, SIGNAL(aborted()), this, SLOT(requestAborted())); + + setLocations(geocodeReply_, coordinate); + geocodeReply_->callSetViewport(bounds); + + if (coordinate.latitude() > 70) { + errorString_ = "error"; + errorCode_ = (QGeoCodeReply::Error) (qRound(coordinate.latitude() - 70)); + } else { + errorString_ = ""; + errorCode_ = QGeoCodeReply::NoError; + } + if (finishRequestImmediately_) { + if (errorCode_) { + geocodeReply_->callSetError(errorCode_, errorString_); + } else { + geocodeReply_->callSetError(QGeoCodeReply::NoError,coordinate.toString()); + geocodeReply_->callSetFinished(true); + } + } else { + // we only allow serialized requests in QML - previous must have been aborted or finished + Q_ASSERT(timerId_ == 0); + timerId_ = startTimer(200); + } + return static_cast(geocodeReply_); + } + +protected: + void timerEvent(QTimerEvent *event) + { + Q_UNUSED(event); + Q_ASSERT(timerId_ == event->timerId()); + Q_ASSERT(geocodeReply_); + killTimer(timerId_); + timerId_ = 0; + if (errorCode_) { + geocodeReply_->callSetError(errorCode_, errorString_); + emit error(geocodeReply_, errorCode_, errorString_); + } else { + geocodeReply_->callSetError(QGeoCodeReply::NoError, "no error"); + geocodeReply_->callSetFinished(true); + } + emit finished(geocodeReply_); + } + +private: + bool validateWellKnownValues_; + bool finishRequestImmediately_; + bool supported_; + GeocodeReplyTest* geocodeReply_; + int timerId_; + QGeoCodeReply::Error errorCode_; + QString errorString_; +}; + +#endif diff --git a/tests/auto/geotestplugin/qgeomappingmanagerengine_test.h b/tests/auto/geotestplugin/qgeomappingmanagerengine_test.h new file mode 100644 index 0000000..07d832c --- /dev/null +++ b/tests/auto/geotestplugin/qgeomappingmanagerengine_test.h @@ -0,0 +1,175 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMAPPINGMANAGERENGINE_TEST_H +#define QGEOMAPPINGMANAGERENGINE_TEST_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "qgeomaptype.h" +#include "qgeotilespec.h" +#include "qgeocameracapabilities_p.h" + +#include +#include +#include + +QT_USE_NAMESPACE + + +class TiledMapReplyTest :public QGeoTiledMapReply +{ + Q_OBJECT +public: + TiledMapReplyTest(const QGeoTileSpec &spec, QObject *parent=0): QGeoTiledMapReply (spec, parent) {} + void callSetError ( Error error, const QString & errorString ) {setError(error, errorString);} + void callSetFinished ( bool finished ) { setFinished(finished);} + void callSetCached(bool cached) { setFinished(cached);} + void callSetMapImageData(const QByteArray &data) { setMapImageData(data); } + void callSetMapImageFormat(const QString &format) { setMapImageFormat(format); } + void abort() { emit aborted(); } + +Q_SIGNALS: + void aborted(); +}; + +class QGeoMappingManagerEngineTest: public QGeoMappingManagerEngine + +{ +Q_OBJECT +public: + QGeoMappingManagerEngineTest(const QMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) : + QGeoMappingManagerEngine(parameters), + finishRequestImmediately_(true), + mappingReply_(0), + timerId_(0), + errorCode_(QGeoTiledMapReply::NoError) + { + Q_UNUSED(error) + Q_UNUSED(errorString) + if (parameters.contains("finishRequestImmediately")) + finishRequestImmediately_ = qvariant_cast(parameters.value("finishRequestImmediately")); + setLocale(QLocale (QLocale::German, QLocale::Germany)); + QGeoCameraCapabilities capabilities; + capabilities.setMinimumZoomLevel(0.0); + capabilities.setMaximumZoomLevel(20.0); + capabilities.setSupportsBearing(true); + setCameraCapabilities(capabilities); + } + + void init() + { + setTileSize(256); + QList types; + types << QGeoMapType(QGeoMapType::StreetMap,tr("Street Map"),tr("Test Street Map"), false, 1); + setSupportedMapTypes(types); + QGeoMappingManagerEngine::init(); + } + + QGeoTiledMapReply* getTileImage(const QGeoTileSpec &spec) + { + mappingReply_ = new TiledMapReplyTest(spec, this); + + QImage im(256, 256, QImage::Format_RGB888); + im.fill(QColor("lightgray")); + QRectF rect; + QString text("X: " + QString::number(spec.x()) + "\nY: " + QString::number(spec.y()) + "\nZ: " + QString::number(spec.zoom())); + rect.setWidth(250); + rect.setHeight(250); + rect.setLeft(3); + rect.setTop(3); + QPainter painter; + QPen pen(QColor("firebrick")); + painter.begin(&im); + painter.setPen(pen); + painter.setFont( QFont("Times", 35, 10, false)); + painter.drawText(rect, text); + // different border color for vertically and horizontally adjacent frames + if ((spec.x() + spec.y()) % 2 == 0) + pen.setColor(QColor("yellow")); + pen.setWidth(5); + painter.setPen(pen); + painter.drawRect(0,0,255,255); + painter.end(); + QPixmap pm = QPixmap::fromImage(im); + QByteArray bytes; + QBuffer buffer(&bytes); + buffer.open(QIODevice::WriteOnly); + pm.save(&buffer, "PNG"); + + mappingReply_->callSetMapImageData(bytes); + mappingReply_->callSetMapImageFormat("png"); + mappingReply_->callSetFinished(true); + + return static_cast(mappingReply_); + } + +public Q_SLOTS: + void requestAborted() + { + if (timerId_) { + killTimer(timerId_); + timerId_ = 0; + } + errorString_ = ""; + errorCode_ = QGeoTiledMapReply::NoError; + } + +protected: + void timerEvent(QTimerEvent *event) + { + Q_ASSERT(timerId_ == event->timerId()); + Q_ASSERT(mappingReply_); + killTimer(timerId_); + timerId_ = 0; + if (errorCode_) { + mappingReply_->callSetError(errorCode_, errorString_); + emit tileError(mappingReply_->tileSpec(), errorString_); + } else { + mappingReply_->callSetError(QGeoTiledMapReply::NoError, "no error"); + mappingReply_->callSetFinished(true); + } + // emit finished(mappingReply_); todo tileFinished + } + +private: + bool finishRequestImmediately_; + TiledMapReplyTest* mappingReply_; + int timerId_; + QGeoTiledMapReply::Error errorCode_; + QString errorString_; +}; + +#endif diff --git a/tests/auto/geotestplugin/qgeoroutingmanagerengine_test.h b/tests/auto/geotestplugin/qgeoroutingmanagerengine_test.h new file mode 100644 index 0000000..8ae5804 --- /dev/null +++ b/tests/auto/geotestplugin/qgeoroutingmanagerengine_test.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTINGMANAGERENGINE_TEST_H +#define QGEOROUTINGMANAGERENGINE_TEST_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_USE_NAMESPACE + + +class RouteReplyTest :public QGeoRouteReply +{ + Q_OBJECT + +public: + RouteReplyTest(QObject *parent=0) :QGeoRouteReply (QGeoRouteRequest(), parent) + {} + void callSetError ( Error error, const QString & errorString ) {setError(error, errorString);} + void callSetFinished ( bool finished ) {setFinished(finished);} + void callSetRoutes(const QList &routes) {setRoutes(routes);} + + void abort() { + emit aborted(); + } +Q_SIGNALS: + void aborted(); +}; + +class QGeoRoutingManagerEngineTest: public QGeoRoutingManagerEngine +{ + Q_OBJECT + RouteReplyTest* routeReply_; + bool finishRequestImmediately_; + int timerId_; + QGeoRouteReply::Error errorCode_; + QString errorString_; + +public: + QGeoRoutingManagerEngineTest(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) : + QGeoRoutingManagerEngine(parameters), + routeReply_(0), + finishRequestImmediately_(true), + timerId_(0), + errorCode_(QGeoRouteReply::NoError) + { + Q_UNUSED(error) + Q_UNUSED(errorString) + + if (parameters.contains("gc_finishRequestImmediately")) { + finishRequestImmediately_ = qvariant_cast(parameters.value("gc_finishRequestImmediately")); + } + + setLocale(QLocale (QLocale::German, QLocale::Germany)); + setSupportedFeatureTypes ( + QGeoRouteRequest::NoFeature | QGeoRouteRequest::TollFeature | + QGeoRouteRequest::HighwayFeature | QGeoRouteRequest::PublicTransitFeature | + QGeoRouteRequest::FerryFeature | QGeoRouteRequest::TunnelFeature | + QGeoRouteRequest::DirtRoadFeature | QGeoRouteRequest::ParksFeature | + QGeoRouteRequest::MotorPoolLaneFeature ); + setSupportedFeatureWeights ( + QGeoRouteRequest::NeutralFeatureWeight | QGeoRouteRequest::PreferFeatureWeight | + QGeoRouteRequest::RequireFeatureWeight | QGeoRouteRequest::AvoidFeatureWeight | + QGeoRouteRequest::DisallowFeatureWeight ); + setSupportedManeuverDetails ( + QGeoRouteRequest::NoManeuvers | QGeoRouteRequest::BasicManeuvers); + setSupportedRouteOptimizations ( + QGeoRouteRequest::ShortestRoute | QGeoRouteRequest::FastestRoute | + QGeoRouteRequest::MostEconomicRoute | QGeoRouteRequest::MostScenicRoute); + setSupportedSegmentDetails ( + QGeoRouteRequest::NoSegmentData | QGeoRouteRequest::BasicSegmentData ); + setSupportedTravelModes ( + QGeoRouteRequest::CarTravel | QGeoRouteRequest::PedestrianTravel | + QGeoRouteRequest::BicycleTravel | QGeoRouteRequest::PublicTransitTravel | + QGeoRouteRequest::TruckTravel ); + } + + virtual QGeoRouteReply* calculateRoute(const QGeoRouteRequest& request) + { + routeReply_ = new RouteReplyTest(); + connect(routeReply_, SIGNAL(aborted()), this, SLOT(requestAborted())); + + if (request.numberAlternativeRoutes() > 70) { + errorCode_ = (QGeoRouteReply::Error)(request.numberAlternativeRoutes() - 70); + errorString_ = "error"; + } else { + errorCode_ = QGeoRouteReply::NoError; + errorString_ = ""; + } + setRoutes(request, routeReply_); + if (finishRequestImmediately_) { + if (errorCode_) { + routeReply_->callSetError(errorCode_, errorString_); + } else { + routeReply_->callSetError(QGeoRouteReply::NoError, "no error"); + routeReply_->callSetFinished(true); + } + } else { + // we only allow serialized requests in QML - previous must have been aborted or finished + Q_ASSERT(timerId_ == 0); + timerId_ = startTimer(200); + } + return static_cast(routeReply_); + } + + void setRoutes(const QGeoRouteRequest& request, RouteReplyTest* reply) + { + QList routes; + for (int i = 0; i < request.numberAlternativeRoutes(); ++i) { + QGeoRoute route; + route.setPath(request.waypoints()); + routes.append(route); + } + reply->callSetRoutes(routes); + } + +public Q_SLOTS: + void requestAborted() + { + if (timerId_) { + killTimer(timerId_); + timerId_ = 0; + } + errorCode_ = QGeoRouteReply::NoError; + errorString_ = ""; + } + +protected: + void timerEvent(QTimerEvent *event) + { + Q_UNUSED(event); + Q_ASSERT(timerId_ == event->timerId()); + Q_ASSERT(routeReply_); + killTimer(timerId_); + timerId_ = 0; + if (errorCode_) { + routeReply_->callSetError(errorCode_, errorString_); + emit error(routeReply_, errorCode_, errorString_); + } else { + routeReply_->callSetError(QGeoRouteReply::NoError, "no error"); + routeReply_->callSetFinished(true); + emit finished(routeReply_); + } + } +}; + +#endif diff --git a/tests/auto/geotestplugin/qgeoserviceproviderplugin_test.cpp b/tests/auto/geotestplugin/qgeoserviceproviderplugin_test.cpp new file mode 100644 index 0000000..d3b0461 --- /dev/null +++ b/tests/auto/geotestplugin/qgeoserviceproviderplugin_test.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoserviceproviderplugin_test.h" +#include "qgeocodingmanagerengine_test.h" +#include "qgeoroutingmanagerengine_test.h" +#include "qgeotiledmappingmanagerengine_test.h" +#include "qplacemanagerengine_test.h" + +#include + +namespace +{ + template + EngineType * createEngine(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) + { + const QString failError = parameters.value(QStringLiteral("error")).toString(); + const QString failErrorString = parameters.value(QStringLiteral("errorString")).toString(); + + if (!failError.isEmpty()) { + *error = QGeoServiceProvider::Error(failError.toInt()); + *errorString = failErrorString; + return 0; + } else { + return new EngineType(parameters, error, errorString); + } + } +} + +QGeoServiceProviderFactoryTest::QGeoServiceProviderFactoryTest() +{ +} + +QGeoServiceProviderFactoryTest::~QGeoServiceProviderFactoryTest() +{ +} + +QGeoRoutingManagerEngine* QGeoServiceProviderFactoryTest::createRoutingManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) const +{ + return createEngine(parameters, error, errorString); +} + + +QGeoCodingManagerEngine* QGeoServiceProviderFactoryTest::createGeocodingManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, + QString *errorString) const +{ + return createEngine(parameters, error, errorString); +} + + +QGeoMappingManagerEngine* QGeoServiceProviderFactoryTest::createMappingManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) const +{ + return createEngine(parameters, error, errorString); +} + +QPlaceManagerEngine* QGeoServiceProviderFactoryTest::createPlaceManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) const +{ + Q_UNUSED(error); + Q_UNUSED(errorString); + return new QPlaceManagerEngineTest(parameters); +} diff --git a/tests/auto/geotestplugin/qgeoserviceproviderplugin_test.h b/tests/auto/geotestplugin/qgeoserviceproviderplugin_test.h new file mode 100644 index 0000000..c606fdb --- /dev/null +++ b/tests/auto/geotestplugin/qgeoserviceproviderplugin_test.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDER_TEST_H +#define QGEOSERVICEPROVIDER_TEST_H + +#include +#include + +QT_USE_NAMESPACE + +class QGeoServiceProviderFactoryTest: public QObject, public QGeoServiceProviderFactory +{ + Q_OBJECT + Q_INTERFACES(QGeoServiceProviderFactory) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0" + FILE "geotestplugin.json") + +public: + QGeoServiceProviderFactoryTest(); + ~QGeoServiceProviderFactoryTest(); + + QGeoMappingManagerEngine* createMappingManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) const; + QGeoRoutingManagerEngine* createRoutingManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString ) const; + QGeoCodingManagerEngine* createGeocodingManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) const; + QPlaceManagerEngine* createPlaceManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) const; +}; + +#endif + + diff --git a/tests/auto/geotestplugin/qgeotiledmap_test.h b/tests/auto/geotestplugin/qgeotiledmap_test.h new file mode 100644 index 0000000..27ff716 --- /dev/null +++ b/tests/auto/geotestplugin/qgeotiledmap_test.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEDMAP_TEST_H +#define QGEOTILEDMAP_TEST_H + +#include +#include + +QT_USE_NAMESPACE +class QGeoTiledMappingManagerEngineTest; +class QGeoTiledMapTest: public QGeoTiledMap +{ + Q_OBJECT +public: + QGeoTiledMapTest(QGeoTiledMappingManagerEngine *engine, QObject *parent = 0): + QGeoTiledMap(engine, parent), + m_engine(engine){} +public: + using QGeoTiledMap::setCameraData; + QGeoTiledMappingManagerEngine *m_engine; +}; + +#endif diff --git a/tests/auto/geotestplugin/qgeotiledmappingmanagerengine_test.h b/tests/auto/geotestplugin/qgeotiledmappingmanagerengine_test.h new file mode 100644 index 0000000..2765c26 --- /dev/null +++ b/tests/auto/geotestplugin/qgeotiledmappingmanagerengine_test.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEDMAPPINGMANAGERENGINE_TEST_H +#define QGEOTILEDMAPPINGMANAGERENGINE_TEST_H + +#include +#include +#include +#include +#include +#include + +#include "qgeotiledmap_test.h" +#include "qgeotilefetcher_test.h" + +QT_USE_NAMESPACE + +class QGeoTiledMappingManagerEngineTest: public QGeoTiledMappingManagerEngine +{ +Q_OBJECT +public: + QGeoTiledMappingManagerEngineTest(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) : + QGeoTiledMappingManagerEngine() + { + Q_UNUSED(error) + Q_UNUSED(errorString) + + setLocale(QLocale (QLocale::German, QLocale::Germany)); + QGeoCameraCapabilities capabilities; + capabilities.setMinimumZoomLevel(0.0); + capabilities.setMaximumZoomLevel(20.0); + capabilities.setSupportsBearing(true); + setTileSize(QSize(256, 256)); + + QList mapTypes; + mapTypes << QGeoMapType(QGeoMapType::StreetMap, tr("StreetMap"), tr("StreetMap"), false, false, 1); + mapTypes << QGeoMapType(QGeoMapType::SatelliteMapDay, tr("SatelliteMapDay"), tr("SatelliteMapDay"), false, false, 2); + mapTypes << QGeoMapType(QGeoMapType::CycleMap, tr("CycleMap"), tr("CycleMap"), false, false, 3); + setSupportedMapTypes(mapTypes); + + QGeoTileFetcherTest *fetcher = new QGeoTileFetcherTest(this); + if (parameters.contains(QStringLiteral("finishRequestImmediately"))) + fetcher->setFinishRequestImmediately(parameters.value(QStringLiteral("finishRequestImmediately")).toBool()); + if (parameters.contains(QStringLiteral("tileSize"))) { + int tileSize = parameters.value(QStringLiteral("tileSize")).toInt(); + setTileSize(QSize(tileSize, tileSize)); + } + if (parameters.contains(QStringLiteral("maxZoomLevel"))) { + double maxZoomLevel = parameters.value(QStringLiteral("maxZoomLevel")).toDouble(); + capabilities.setMaximumZoomLevel(maxZoomLevel); + } + + setCameraCapabilities(capabilities); + fetcher->setTileSize(tileSize()); + setTileFetcher(fetcher); + } + + QGeoMap *createMap() + { + return new QGeoTiledMapTest(this); + } + +}; + +#endif diff --git a/tests/auto/geotestplugin/qgeotilefetcher_test.h b/tests/auto/geotestplugin/qgeotilefetcher_test.h new file mode 100644 index 0000000..a25b011 --- /dev/null +++ b/tests/auto/geotestplugin/qgeotilefetcher_test.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOTILEFETCHER_TEST_H +#define QGEOTILEFETCHER_TEST_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class TiledMapReplyTest :public QGeoTiledMapReply +{ + Q_OBJECT +public: + TiledMapReplyTest(const QGeoTileSpec &spec, QObject *parent=0): QGeoTiledMapReply (spec, parent) {} + void callSetError ( Error error, const QString & errorString ) {setError(error, errorString);} + void callSetFinished ( bool finished ) { setFinished(finished);} + void callSetCached(bool cached) { setFinished(cached);} + void callSetMapImageData(const QByteArray &data) { setMapImageData(data); } + void callSetMapImageFormat(const QString &format) { setMapImageFormat(format); } + void abort() { emit aborted(); } + +Q_SIGNALS: + void aborted(); +}; + +class QGeoTileFetcherTest: public QGeoTileFetcher +{ + Q_OBJECT +public: + QGeoTileFetcherTest(QObject *parent = 0) + : QGeoTileFetcher(parent), finishRequestImmediately_(false), errorCode_(QGeoTiledMapReply::NoError) + { + } + + bool init() + { + return true; + } + + QGeoTiledMapReply* getTileImage(const QGeoTileSpec &spec) + { + TiledMapReplyTest* mappingReply = new TiledMapReplyTest(spec, this); + + QImage im(256, 256, QImage::Format_RGB888); + im.fill(QColor("lightgray")); + QRectF rect; + QString text("X: " + QString::number(spec.x()) + "\nY: " + QString::number(spec.y()) + "\nZ: " + QString::number(spec.zoom())); + rect.setWidth(250); + rect.setHeight(250); + rect.setLeft(3); + rect.setTop(3); + QPainter painter; + QPen pen(QColor("firebrick")); + painter.begin(&im); + painter.setPen(pen); + painter.setFont( QFont("Times", 35, 10, false)); + painter.drawText(rect, text); + // different border color for vertically and horizontally adjacent frames + if ((spec.x() + spec.y()) % 2 == 0) + pen.setColor(QColor("yellow")); + pen.setWidth(5); + painter.setPen(pen); + painter.drawRect(0,0,255,255); + painter.end(); + QPixmap pm = QPixmap::fromImage(im); + QByteArray bytes; + QBuffer buffer(&bytes); + buffer.open(QIODevice::WriteOnly); + pm.save(&buffer, "PNG"); + + mappingReply->callSetMapImageData(bytes); + mappingReply->callSetMapImageFormat("png"); + + if (finishRequestImmediately_) { + updateRequest(mappingReply); + return mappingReply; + } else { + if (m_queue.isEmpty()) + timer_.start(500, this); + m_queue.append(mappingReply); + } + + return mappingReply; + } + + void setFinishRequestImmediately(bool enabled) + { + finishRequestImmediately_ = enabled; + } + + void setTileSize(QSize tileSize) + { + tileSize_ = tileSize; + } + +public Q_SLOTS: + void requestAborted() + { + timer_.stop(); + errorString_.clear(); + errorCode_ = QGeoTiledMapReply::NoError; + } +Q_SIGNALS: + void tileFetched(const QGeoTileSpec&); + +protected: + void updateRequest(TiledMapReplyTest* mappingReply) + { + if (errorCode_) { + mappingReply->callSetError(errorCode_, errorString_); + emit tileError(mappingReply->tileSpec(), errorString_); + } else { + mappingReply->callSetError(QGeoTiledMapReply::NoError, "no error"); + mappingReply->callSetFinished(true); + emit tileFetched(mappingReply->tileSpec()); + } + } + + void timerEvent(QTimerEvent *event) + { + if (event->timerId() != timer_.timerId()) { + QGeoTileFetcher::timerEvent(event); + return; + } + updateRequest(m_queue.takeFirst()); + if (m_queue.isEmpty()) { + timer_.stop(); + } + } + +private: + bool finishRequestImmediately_; + QBasicTimer timer_; + QGeoTiledMapReply::Error errorCode_; + QString errorString_; + QSize tileSize_; + QList m_queue; +}; + +#endif diff --git a/tests/auto/geotestplugin/qplacemanagerengine_test.h b/tests/auto/geotestplugin/qplacemanagerengine_test.h new file mode 100644 index 0000000..f8af1ed --- /dev/null +++ b/tests/auto/geotestplugin/qplacemanagerengine_test.h @@ -0,0 +1,693 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLACEMANAGERENGINE_TEST_H +#define QPLACEMANAGERENGINE_TEST_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +inline uint qHash(const QPlaceCategory &category) +{ + return qHash(QUuid(category.categoryId().toLatin1())); +} + +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +class PlaceReply : public QPlaceReply +{ + Q_OBJECT + + friend class QPlaceManagerEngineTest; + +public: + PlaceReply(QObject *parent = 0) + : QPlaceReply(parent) + { } + + Q_INVOKABLE void emitFinished() + { + emit finished(); + } +}; + +class ContentReply : public QPlaceContentReply +{ + Q_OBJECT + + friend class QPlaceManagerEngineTest; + +public: + ContentReply(QObject *parent = 0) + : QPlaceContentReply(parent) + {} + + Q_INVOKABLE void emitError() + { + emit error(error(), errorString()); + } + + Q_INVOKABLE void emitFinished() + { + emit finished(); + } +}; + +class DetailsReply : public QPlaceDetailsReply +{ + Q_OBJECT + + friend class QPlaceManagerEngineTest; + +public: + DetailsReply(QObject *parent = 0) + : QPlaceDetailsReply(parent) + { } + + Q_INVOKABLE void emitError() + { + emit error(error(), errorString()); + } + + Q_INVOKABLE void emitFinished() + { + emit finished(); + } +}; + +class IdReply : public QPlaceIdReply +{ + Q_OBJECT + + friend class QPlaceManagerEngineTest; + +public: + IdReply(QPlaceIdReply::OperationType type, QObject *parent = 0) + : QPlaceIdReply(type, parent) + { } + + Q_INVOKABLE void emitError() + { + emit error(error(), errorString()); + } + + Q_INVOKABLE void emitFinished() + { + emit finished(); + } +}; + +class PlaceSearchReply : public QPlaceSearchReply +{ + Q_OBJECT + +public: + PlaceSearchReply(const QList &results, QObject *parent = 0) + : QPlaceSearchReply(parent) + { + setResults(results); + } + + Q_INVOKABLE void emitError() + { + emit error(error(), errorString()); + } + + Q_INVOKABLE void emitFinished() + { + emit finished(); + } +}; + +class SuggestionReply : public QPlaceSearchSuggestionReply +{ + Q_OBJECT + +public: + SuggestionReply(const QStringList &suggestions, QObject *parent = 0) + : QPlaceSearchSuggestionReply(parent) + { + setSuggestions(suggestions); + } + + Q_INVOKABLE void emitError() + { + emit error(error(), errorString()); + } + + Q_INVOKABLE void emitFinished() + { + emit finished(); + } +}; + +class QPlaceManagerEngineTest : public QPlaceManagerEngine +{ + Q_OBJECT +public: + QPlaceManagerEngineTest(const QVariantMap ¶meters) + : QPlaceManagerEngine(parameters) + { + m_locales << QLocale(); + if (parameters.value(QStringLiteral("initializePlaceData"), false).toBool()) { + QFile placeData(QFINDTESTDATA("place_data.json")); + QVERIFY(placeData.exists()); + if (placeData.open(QIODevice::ReadOnly)) { + QJsonDocument document = QJsonDocument::fromJson(placeData.readAll()); + + if (document.isObject()) { + QJsonObject o = document.object(); + + if (o.contains(QStringLiteral("categories"))) { + QJsonArray categories = o.value(QStringLiteral("categories")).toArray(); + + for (int i = 0; i < categories.count(); ++i) { + QJsonObject c = categories.at(i).toObject(); + + QPlaceCategory category; + + category.setName(c.value(QStringLiteral("name")).toString()); + category.setCategoryId(c.value(QStringLiteral("id")).toString()); + + m_categories.insert(category.categoryId(), category); + + const QString parentId = c.value(QStringLiteral("parentId")).toString(); + m_childCategories[parentId].append(category.categoryId()); + } + } + + if (o.contains(QStringLiteral("places"))) { + QJsonArray places = o.value(QStringLiteral("places")).toArray(); + + for (int i = 0; i < places.count(); ++i) { + QJsonObject p = places.at(i).toObject(); + + QPlace place; + + place.setName(p.value(QStringLiteral("name")).toString()); + place.setPlaceId(p.value(QStringLiteral("id")).toString()); + + QList categories; + QJsonArray ca = p.value(QStringLiteral("categories")).toArray(); + for (int j = 0; j < ca.count(); ++j) { + QPlaceCategory c = m_categories.value(ca.at(j).toString()); + if (!c.isEmpty()) + categories.append(c); + } + place.setCategories(categories); + + QGeoCoordinate coordinate; + QJsonObject lo = p.value(QStringLiteral("location")).toObject(); + coordinate.setLatitude(lo.value(QStringLiteral("latitude")).toDouble()); + coordinate.setLongitude(lo.value(QStringLiteral("longitude")).toDouble()); + + QGeoLocation location; + location.setCoordinate(coordinate); + + place.setLocation(location); + + m_places.insert(place.placeId(), place); + + QStringList recommendations; + QJsonArray ra = p.value(QStringLiteral("recommendations")).toArray(); + for (int j = 0; j < ra.count(); ++j) + recommendations.append(ra.at(j).toString()); + m_placeRecommendations.insert(place.placeId(), recommendations); + + QJsonArray revArray = p.value(QStringLiteral("reviews")).toArray(); + QList reviews; + for (int j = 0; j < revArray.count(); ++j) { + QJsonObject ro = revArray.at(j).toObject(); + QPlaceReview review; + if (ro.contains(QStringLiteral("title"))) + review.setTitle(ro.value(QStringLiteral("title")).toString()); + if (ro.contains(QStringLiteral("text"))) + review.setText(ro.value(QStringLiteral("text")).toString()); + + if (ro.contains(QStringLiteral("language"))) + review.setLanguage(ro.value("language").toString()); + + if (ro.contains(QStringLiteral("rating"))) + review.setRating(ro.value("rating").toDouble()); + + if (ro.contains(QStringLiteral("dateTime"))) + review.setDateTime(QDateTime::fromString( + ro.value(QStringLiteral("dateTime")).toString(), + QStringLiteral("hh:mm dd-MM-yyyy"))); + if (ro.contains(QStringLiteral("reviewId"))) + review.setReviewId(ro.value("reviewId").toString()); + + reviews << review; + } + m_placeReviews.insert(place.placeId(), reviews); + + QJsonArray imgArray = p.value(QStringLiteral("images")).toArray(); + QList images; + for (int j = 0; j < imgArray.count(); ++j) { + QJsonObject imgo = imgArray.at(j).toObject(); + QPlaceImage image; + if (imgo.contains(QStringLiteral("url"))) + image.setUrl(imgo.value(QStringLiteral("url")).toString()); + + if (imgo.contains("imageId")) + image.setImageId(imgo.value(QStringLiteral("imageId")).toString()); + + if (imgo.contains("mimeType")) + image.setMimeType(imgo.value(QStringLiteral("mimeType")).toString()); + + images << image; + } + + m_placeImages.insert(place.placeId(), images); + + QJsonArray edArray = p.value(QStringLiteral("editorials")).toArray(); + QList editorials; + for (int j = 0; j < edArray.count(); ++j) { + QJsonObject edo = edArray.at(j).toObject(); + QPlaceEditorial editorial; + if (edo.contains(QStringLiteral("title"))) + editorial.setTitle(edo.value(QStringLiteral("title")).toString()); + + if (edo.contains(QStringLiteral("text"))) + editorial.setText(edo.value(QStringLiteral("text")).toString()); + + if (edo.contains(QStringLiteral("language"))) + editorial.setLanguage(edo.value(QStringLiteral("language")).toString()); + + editorials << editorial; + } + + m_placeEditorials.insert(place.placeId(), editorials); + } + } + } + } + } + } + + QPlaceDetailsReply *getPlaceDetails(const QString &placeId) Q_DECL_OVERRIDE + { + DetailsReply *reply = new DetailsReply(this); + + if (placeId.isEmpty() || !m_places.contains(placeId)) { + reply->setError(QPlaceReply::PlaceDoesNotExistError, tr("Place does not exist")); + QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection); + } else { + reply->setPlace(m_places.value(placeId)); + } + + QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection); + + return reply; + } + + QPlaceContentReply *getPlaceContent(const QPlaceContentRequest &query) Q_DECL_OVERRIDE + { + ContentReply *reply = new ContentReply(this); + if (query.placeId().isEmpty() || !m_places.contains(query.placeId())) { + reply->setError(QPlaceReply::PlaceDoesNotExistError, tr("Place does not exist")); + QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection); + + } else { + QPlaceContent::Collection collection; + int totalCount = 0; + switch (query.contentType()) { + case QPlaceContent::ReviewType: + totalCount = m_placeReviews.value(query.placeId()).count(); + break; + case QPlaceContent::ImageType: + totalCount = m_placeImages.value(query.placeId()).count(); + break; + case QPlaceContent::EditorialType: + totalCount = m_placeEditorials.value(query.placeId()).count(); + default: + //do nothing + break; + } + + QVariantMap context = query.contentContext().toMap(); + + int offset = context.value(QStringLiteral("offset"), 0).toInt(); + int max = (query.limit() == -1) ? totalCount + : qMin(offset + query.limit(), totalCount); + for (int i = offset; i < max; ++i) { + switch (query.contentType()) { + case QPlaceContent::ReviewType: + collection.insert(i, m_placeReviews.value(query.placeId()).at(i)); + break; + case QPlaceContent::ImageType: + collection.insert(i, m_placeImages.value(query.placeId()).at(i)); + break; + case QPlaceContent::EditorialType: + collection.insert(i, m_placeEditorials.value(query.placeId()).at(i)); + default: + //do nothing + break; + } + } + + reply->setContent(collection); + reply->setTotalCount(totalCount); + + if (max != totalCount) { + context.clear(); + context.insert(QStringLiteral("offset"), offset + query.limit()); + QPlaceContentRequest request = query; + request.setContentContext(context); + reply->setNextPageRequest(request); + } + if (offset > 0) { + context.clear(); + context.insert(QStringLiteral("offset"), qMin(0, offset - query.limit())); + QPlaceContentRequest request = query; + request.setContentContext(context); + reply->setPreviousPageRequest(request); + } + } + + QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection); + return reply; + } + + QPlaceSearchReply *search(const QPlaceSearchRequest &query) Q_DECL_OVERRIDE + { + QList results; + + if (!query.searchTerm().isEmpty()) { + foreach (const QPlace &place, m_places) { + if (!place.name().contains(query.searchTerm(), Qt::CaseInsensitive)) + continue; + + QPlaceResult r; + r.setPlace(place); + r.setTitle(place.name()); + + results.append(r); + } + } else if (!query.categories().isEmpty()) { + QSet categories = query.categories().toSet(); + foreach (const QPlace &place, m_places) { + if (place.categories().toSet().intersect(categories).isEmpty()) + continue; + + QPlaceResult r; + r.setPlace(place); + r.setTitle(place.name()); + + results.append(r); + } + } else if (!query.recommendationId().isEmpty()) { + QStringList recommendations = m_placeRecommendations.value(query.recommendationId()); + foreach (const QString &id, recommendations) { + QPlaceResult r; + r.setPlace(m_places.value(id)); + r.setTitle(r.place().name()); + + results.append(r); + } + } + + PlaceSearchReply *reply = new PlaceSearchReply(results, this); + + QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection); + + return reply; + } + + QPlaceSearchSuggestionReply *searchSuggestions(const QPlaceSearchRequest &query) Q_DECL_OVERRIDE + { + QStringList suggestions; + if (query.searchTerm() == QLatin1String("test")) { + suggestions << QStringLiteral("test1"); + suggestions << QStringLiteral("test2"); + suggestions << QStringLiteral("test3"); + } + + SuggestionReply *reply = new SuggestionReply(suggestions, this); + + QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection); + + return reply; + } + + QPlaceIdReply *savePlace(const QPlace &place) Q_DECL_OVERRIDE + { + IdReply *reply = new IdReply(QPlaceIdReply::SavePlace, this); + + if (!place.placeId().isEmpty() && !m_places.contains(place.placeId())) { + reply->setError(QPlaceReply::PlaceDoesNotExistError, tr("Place does not exist")); + QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection); + } else if (!place.placeId().isEmpty()) { + m_places.insert(place.placeId(), place); + reply->setId(place.placeId()); + } else { + QPlace p = place; + p.setPlaceId(QUuid::createUuid().toString()); + m_places.insert(p.placeId(), p); + + reply->setId(p.placeId()); + } + + QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection); + + return reply; + } + + QPlaceIdReply *removePlace(const QString &placeId) Q_DECL_OVERRIDE + { + IdReply *reply = new IdReply(QPlaceIdReply::RemovePlace, this); + reply->setId(placeId); + + if (!m_places.contains(placeId)) { + reply->setError(QPlaceReply::PlaceDoesNotExistError, tr("Place does not exist")); + QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection); + } else { + m_places.remove(placeId); + } + + QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection); + + return reply; + } + + QPlaceIdReply *saveCategory(const QPlaceCategory &category, const QString &parentId) Q_DECL_OVERRIDE + { + IdReply *reply = new IdReply(QPlaceIdReply::SaveCategory, this); + + if ((!category.categoryId().isEmpty() && !m_categories.contains(category.categoryId())) || + (!parentId.isEmpty() && !m_categories.contains(parentId))) { + reply->setError(QPlaceReply::CategoryDoesNotExistError, tr("Category does not exist")); + QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection); + } else if (!category.categoryId().isEmpty()) { + m_categories.insert(category.categoryId(), category); + QStringList children = m_childCategories.value(parentId); + + QMutableHashIterator i(m_childCategories); + while (i.hasNext()) { + i.next(); + i.value().removeAll(category.categoryId()); + } + + if (!children.contains(category.categoryId())) { + children.append(category.categoryId()); + m_childCategories.insert(parentId, children); + } + reply->setId(category.categoryId()); + } else { + QPlaceCategory c = category; + c.setCategoryId(QUuid::createUuid().toString()); + m_categories.insert(c.categoryId(), c); + QStringList children = m_childCategories.value(parentId); + if (!children.contains(c.categoryId())) { + children.append(c.categoryId()); + m_childCategories.insert(parentId, children); + } + + reply->setId(c.categoryId()); + } + + QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection); + + return reply; + } + + QPlaceIdReply *removeCategory(const QString &categoryId) Q_DECL_OVERRIDE + { + IdReply *reply = new IdReply(QPlaceIdReply::RemoveCategory, this); + reply->setId(categoryId); + + if (!m_categories.contains(categoryId)) { + reply->setError(QPlaceReply::CategoryDoesNotExistError, tr("Category does not exist")); + QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection); + } else { + m_categories.remove(categoryId); + + QMutableHashIterator i(m_childCategories); + while (i.hasNext()) { + i.next(); + i.value().removeAll(categoryId); + } + } + + QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection); + + return reply; + } + + QPlaceReply *initializeCategories() Q_DECL_OVERRIDE + { + QPlaceReply *reply = new PlaceReply(this); + + QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection); + + return reply; + } + + QString parentCategoryId(const QString &categoryId) const Q_DECL_OVERRIDE + { + QHashIterator i(m_childCategories); + while (i.hasNext()) { + i.next(); + if (i.value().contains(categoryId)) + return i.key(); + } + + return QString(); + } + + virtual QStringList childCategoryIds(const QString &categoryId) const Q_DECL_OVERRIDE + { + return m_childCategories.value(categoryId); + } + + virtual QPlaceCategory category(const QString &categoryId) const Q_DECL_OVERRIDE + { + return m_categories.value(categoryId); + } + + QList childCategories(const QString &parentId) const Q_DECL_OVERRIDE + { + QList categories; + + foreach (const QString &id, m_childCategories.value(parentId)) + categories.append(m_categories.value(id)); + + return categories; + } + + QList locales() const Q_DECL_OVERRIDE + { + return m_locales; + } + + void setLocales(const QList &locales) Q_DECL_OVERRIDE + { + m_locales = locales; + } + + QUrl constructIconUrl(const QPlaceIcon &icon, const QSize &size) const Q_DECL_OVERRIDE + { + QList > candidates; + + QMap sizeDictionary; + sizeDictionary.insert(QStringLiteral("s"), 20); + sizeDictionary.insert(QStringLiteral("m"), 30); + sizeDictionary.insert(QStringLiteral("l"), 50); + + QStringList sizeKeys; + sizeKeys << QStringLiteral("s") << QStringLiteral("m") << QStringLiteral("l"); + + foreach (const QString &sizeKey, sizeKeys) + { + if (icon.parameters().contains(sizeKey)) + candidates.append(QPair(sizeDictionary.value(sizeKey), + icon.parameters().value(sizeKey).toUrl())); + } + + if (candidates.isEmpty()) + return QUrl(); + else if (candidates.count() == 1) { + return candidates.first().second; + } else { + //we assume icons are squarish so we can use height to + //determine which particular icon to return + int requestedHeight = size.height(); + + for (int i = 0; i < candidates.count() - 1; ++i) { + int thresholdHeight = (candidates.at(i).first + candidates.at(i+1).first) / 2; + if (requestedHeight < thresholdHeight) + return candidates.at(i).second; + } + return candidates.last().second; + } + } + + QPlace compatiblePlace(const QPlace &original) const Q_DECL_OVERRIDE + { + QPlace place; + place.setName(original.name()); + return place; + } + +private: + QList m_locales; + QHash m_places; + QHash m_categories; + QHash m_childCategories; + QHash m_placeRecommendations; + QHash > m_placeReviews; + QHash > m_placeImages; + QHash > m_placeEditorials; +}; + +#endif diff --git a/tests/auto/geotestplugin/testdata.qrc b/tests/auto/geotestplugin/testdata.qrc new file mode 100644 index 0000000..23eed23 --- /dev/null +++ b/tests/auto/geotestplugin/testdata.qrc @@ -0,0 +1,5 @@ + + + place_data.json + + diff --git a/tests/auto/maptype/maptype.pro b/tests/auto/maptype/maptype.pro new file mode 100644 index 0000000..1622dc5 --- /dev/null +++ b/tests/auto/maptype/maptype.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_maptype + +SOURCES += tst_maptype.cpp + +QT += location-private testlib diff --git a/tests/auto/maptype/tst_maptype.cpp b/tests/auto/maptype/tst_maptype.cpp new file mode 100644 index 0000000..9b7956f --- /dev/null +++ b/tests/auto/maptype/tst_maptype.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QGeoMapType) + +class tst_MapType : public QObject +{ + Q_OBJECT + +public: + tst_MapType(); + +private Q_SLOTS: + void constructorTest(); + void comparison(); + void comparison_data(); +}; + +tst_MapType::tst_MapType() {} + +void tst_MapType::constructorTest() +{ + QGeoMapType *testObjPtr = new QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street map"), + QStringLiteral("map description"), true, true, 1); + QVERIFY(testObjPtr); + QCOMPARE(testObjPtr->style(), QGeoMapType::StreetMap); + QCOMPARE(testObjPtr->name(), QStringLiteral("street map")); + QCOMPARE(testObjPtr->description(), QStringLiteral("map description")); + QVERIFY(testObjPtr->mobile()); + QVERIFY(testObjPtr->night()); + QCOMPARE(testObjPtr->mapId(), 1); + delete testObjPtr; + + testObjPtr = new QGeoMapType(); + QCOMPARE(testObjPtr->style(), QGeoMapType::NoMap); + QVERIFY2(testObjPtr->name().isEmpty(), "Wrong default value"); + QVERIFY2(testObjPtr->description().isEmpty(), "Wrong default value"); + QVERIFY2(!testObjPtr->mobile(), "Wrong default value"); + QVERIFY2(!testObjPtr->night(), "Wrong default value"); + QCOMPARE(testObjPtr->mapId(), 0); + delete testObjPtr; +} + +void tst_MapType::comparison_data() +{ + QTest::addColumn("type1"); + QTest::addColumn("type2"); + QTest::addColumn("expected"); + + QTest::newRow("null") << QGeoMapType() << QGeoMapType() << true; + + QTest::newRow("equal") << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 42) + << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 42) + << true; + + QTest::newRow("style") << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 42) + << QGeoMapType(QGeoMapType::TerrainMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 42) + << false; + + QTest::newRow("name") << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 42) + << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("different name"), + QStringLiteral("street desc"), false, false, 42) + << false; + + QTest::newRow("description") << QGeoMapType(QGeoMapType::StreetMap, + QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 42) + << QGeoMapType(QGeoMapType::StreetMap, + QStringLiteral("street name"), + QStringLiteral("different desc"), false, false, 42) + << false; + + QTest::newRow("mobile") << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 42) + << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), true, false, 42) + << false; + + QTest::newRow("night") << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 42) + << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, true, 42) + << false; + + QTest::newRow("id") << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 42) + << QGeoMapType(QGeoMapType::StreetMap, QStringLiteral("street name"), + QStringLiteral("street desc"), false, false, 99) + << false; +} + +void tst_MapType::comparison() +{ + QFETCH(QGeoMapType, type1); + QFETCH(QGeoMapType, type2); + QFETCH(bool, expected); + + QCOMPARE(type1 == type2, expected); + QCOMPARE(type1 != type2, !expected); +} + +QTEST_APPLESS_MAIN(tst_MapType) + +#include "tst_maptype.moc" diff --git a/tests/auto/nokia_services/nokia_services.pro b/tests/auto/nokia_services/nokia_services.pro new file mode 100644 index 0000000..4c56e9f --- /dev/null +++ b/tests/auto/nokia_services/nokia_services.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += routing places_semiauto diff --git a/tests/auto/nokia_services/places_semiauto/places_semiauto.pro b/tests/auto/nokia_services/places_semiauto/places_semiauto.pro new file mode 100644 index 0000000..7953509 --- /dev/null +++ b/tests/auto/nokia_services/places_semiauto/places_semiauto.pro @@ -0,0 +1,10 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplaces + +HEADERS += ../../placemanager_utils/placemanager_utils.h + +SOURCES += tst_places.cpp \ + ../../placemanager_utils/placemanager_utils.cpp + +QT += location testlib diff --git a/tests/auto/nokia_services/places_semiauto/tst_places.cpp b/tests/auto/nokia_services/places_semiauto/tst_places.cpp new file mode 100644 index 0000000..8e5289d --- /dev/null +++ b/tests/auto/nokia_services/places_semiauto/tst_places.cpp @@ -0,0 +1,745 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../placemanager_utils/placemanager_utils.h" + +QT_USE_NAMESPACE + +class tst_QPlaceManagerNokia : public PlaceManagerUtils +{ + Q_OBJECT +public: + enum ExpectedResults { + AnyResults, //zero or more results expected + SomeResults, //at least one result expected + NoResults // zero results expected + }; + + tst_QPlaceManagerNokia(); + +private Q_SLOTS: + void initTestCase(); + void search(); + void search_data(); + void searchResultFields(); + void recommendations(); + void recommendations_data(); + void details(); + void categories(); + void suggestions(); + void suggestions_data(); + void suggestionsMisc(); + void locale(); + void content(); + void content_data(); + void unsupportedFunctions(); + +private: + void commonAreas(QList *dataTags, QList *areas, + QList *errors, + QList *results); + + static const QLatin1String ValidKnownPlaceId; + static const QLatin1String ProxyEnv; + static const QLatin1String AppIdEnv; + static const QLatin1String TokenEnv; + + QGeoServiceProvider *provider; +}; + +Q_DECLARE_METATYPE(tst_QPlaceManagerNokia::ExpectedResults) + +// ValidKnownPlaceId is the id of a place with a full complement of place content. Editorials, +// reviews, images, recommendations. If it disappears these tests will fail. +// Currently it is set to an Eiffel Tower tourist office. +const QLatin1String tst_QPlaceManagerNokia::ValidKnownPlaceId("250u09tu-4561b8da952f4fd79c4e1998c3fcf032"); + +const QLatin1String tst_QPlaceManagerNokia::ProxyEnv("NOKIA_PLUGIN_PROXY"); +const QLatin1String tst_QPlaceManagerNokia::AppIdEnv("NOKIA_APPID"); +const QLatin1String tst_QPlaceManagerNokia::TokenEnv("NOKIA_TOKEN"); + +tst_QPlaceManagerNokia::tst_QPlaceManagerNokia() +{ +} + +void tst_QPlaceManagerNokia::initTestCase() +{ + QVariantMap params; + QStringList providers = QGeoServiceProvider::availableServiceProviders(); + QVERIFY(providers.contains("here")); +#ifndef QT_NO_PROCESS + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + + if (!(env.contains(AppIdEnv) && env.contains(TokenEnv))) + QSKIP("NOKIA_APPID and NOKIA_TOKEN environment variables not set");\ + + params.insert(QStringLiteral("here.app_id"), env.value(AppIdEnv)); + params.insert(QStringLiteral("here.token"), env.value(TokenEnv)); + + if (env.contains(ProxyEnv)) + params.insert(QStringLiteral("here.proxy"), env.value(ProxyEnv)); +#else + QSKIP("Cannot parse process environment, NOKIA_APPID and NOKIA_TOKEN not set"); +#endif + provider = new QGeoServiceProvider("here", params); + placeManager = provider->placeManager(); + QVERIFY(placeManager); +} + +void tst_QPlaceManagerNokia::search() +{ + QFETCH(QGeoShape, area); + QFETCH(QString, searchTerm); + QFETCH(QList, categories); + QFETCH(QPlaceReply::Error, error); + QFETCH(ExpectedResults, expectedResults); + + QPlaceSearchRequest searchRequest; + searchRequest.setSearchArea(area); + searchRequest.setSearchTerm(searchTerm); + if (categories.count() == 1) + searchRequest.setCategory(categories.first()); + else + searchRequest.setCategories(categories); + + QList results; + QVERIFY(doSearch(searchRequest, &results, error)); + + if (expectedResults == NoResults) + QVERIFY(results.count() == 0); + else if (expectedResults == SomeResults) + QVERIFY(results.count() > 0); +} + +void tst_QPlaceManagerNokia::search_data() +{ + QTest::addColumn("area"); + QTest::addColumn("searchTerm"); + QTest::addColumn >("categories"); + QTest::addColumn("hint"); + QTest::addColumn ("error"); + QTest::addColumn("expectedResults"); + + for (int i = 0; i < 3; i++) { + QByteArray suffix; + QGeoShape area; + if (i==0) { + area = QGeoCircle(QGeoCoordinate(-27.5, 153)); + suffix = " (Circle - center only)"; + } else if (i==1) { + area = QGeoCircle(QGeoCoordinate(-27.5, 153), 5000); + suffix = " (Circle - with radius specified)"; + } else { + area = QGeoRectangle(QGeoCoordinate(-26.5, 152), QGeoCoordinate(-28.5, 154)); + suffix = " (Rectangle)"; + } + + QByteArray dataTag = QByteArray("coordinate only") + suffix; + QTest::newRow(dataTag) << area + << QString() + << QList() + << QPlaceSearchRequest::UnspecifiedHint + << QPlaceReply::NoError + << SomeResults; + + dataTag = QByteArray("seach term") + suffix; + QTest::newRow(dataTag) << area + << "sushi" + << QList() + << QPlaceSearchRequest::UnspecifiedHint + << QPlaceReply::NoError + << SomeResults; + + QPlaceCategory eatDrinkCat; + eatDrinkCat.setCategoryId(QStringLiteral("eat-drink")); + dataTag = QByteArray("single valid category") + suffix; + QTest::newRow(dataTag) << area + << QString() + << (QList() + << eatDrinkCat) + << QPlaceSearchRequest::UnspecifiedHint + << QPlaceReply::NoError + << SomeResults; + + QPlaceCategory accommodationCat; + accommodationCat.setCategoryId(QStringLiteral("accommodation")); + dataTag = QByteArray("multiple valid categories") + suffix; + QTest::newRow(dataTag) << area + << QString() + << (QList() + << eatDrinkCat + << accommodationCat) + << QPlaceSearchRequest::UnspecifiedHint + << QPlaceReply::NoError + << SomeResults; + + QPlaceCategory nonExistentCat; + nonExistentCat.setCategoryId(QStringLiteral("klingon cuisine")); + dataTag = QByteArray("non-existent category") + suffix; + QTest::newRow(dataTag) << area + << QString() + << (QList() + << nonExistentCat) + << QPlaceSearchRequest::UnspecifiedHint + << QPlaceReply::NoError + << NoResults; + + dataTag = QByteArray("search term and category") + suffix; + QTest::newRow(dataTag) << area + << "sushi" + << (QList() + << eatDrinkCat) + << QPlaceSearchRequest::UnspecifiedHint + << QPlaceReply::BadArgumentError + << NoResults; + } + + + //invalid areas and boundary testing + QList dataTags; + QList areas; + QList errors; + QList results; + commonAreas(&dataTags, &areas, &errors, &results); + + for (int i = 0; i < dataTags.count(); ++i) { + QTest::newRow(dataTags.at(i)) << areas.at(i) + << "sushi" + << QList() + << QPlaceSearchRequest::UnspecifiedHint + << errors.at(i) + << results.at(i); + } + + //relevancy hints will be ignored by the backend, but should give a valid result + QTest::newRow("check using a distance relevancy hint") + << static_cast(QGeoCircle(QGeoCoordinate(-27.5, 153))) + << QStringLiteral("sushi") + << QList() + << QPlaceSearchRequest::DistanceHint + << QPlaceReply::NoError + << AnyResults; + + QTest::newRow("check using lexical place name hint") + << static_cast(QGeoCircle(QGeoCoordinate(-27.5, 153))) + << QStringLiteral("sushi") + << QList() + << QPlaceSearchRequest::LexicalPlaceNameHint + << QPlaceReply::NoError + << AnyResults; +} + +void tst_QPlaceManagerNokia::searchResultFields() +{ + //check that using a distance relevancy hint will give a valid result + //even though it will be ignored by the backend. + QPlaceSearchRequest searchRequest; + searchRequest.setSearchArea(QGeoCircle(QGeoCoordinate(-27.5, 153))); + searchRequest.setSearchTerm(QStringLiteral("sushi")); + + QPlaceSearchReply *reply = placeManager->search(searchRequest); + QSignalSpy spy(reply, SIGNAL(finished())); + QTRY_VERIFY_WITH_TIMEOUT(spy.count() == 1, Timeout); + QVERIFY(reply->results().count() > 0); + + //check that search results have basic data filled in + QPlaceResult result= reply->results().at(0); + QVERIFY(!result.title().isEmpty()); + QVERIFY(!result.icon().url().isEmpty()); + QVERIFY(!qIsNaN(result.distance())); + QVERIFY(!result.place().name().isEmpty()); + QVERIFY(result.place().location().coordinate().isValid()); + QVERIFY(!result.place().location().address().text().isEmpty()); + QVERIFY(!result.place().location().address().isTextGenerated()); + QVERIFY(result.place().categories().count() == 1);//only primary category retrieved on + //search + + //sponsored and ratings fields are optional and thus have not been explicitly tested. +} + +void tst_QPlaceManagerNokia::recommendations() +{ + QFETCH(QString, recommendationId); + QFETCH(QString, searchTerm); + QFETCH(QGeoShape, searchArea); + QFETCH(QList, categories); + QFETCH(QPlaceReply::Error, error); + + QPlaceSearchRequest searchRequest; + searchRequest.setRecommendationId(recommendationId); + searchRequest.setSearchTerm(searchTerm); + searchRequest.setSearchArea(searchArea); + searchRequest.setCategories(categories); + + QList results; + QVERIFY(doSearch(searchRequest, &results, error)); + + if (error == QPlaceReply::NoError) + QVERIFY(results.count() > 0); +} + +void tst_QPlaceManagerNokia::recommendations_data() +{ + QTest::addColumn("recommendationId"); + QTest::addColumn("searchTerm"); + QTest::addColumn("searchArea"); + QTest::addColumn >("categories"); + QTest::addColumn("error"); + + QPlaceCategory eatDrinkCat; + eatDrinkCat.setCategoryId(QStringLiteral("eat-drink")); + + QTest::newRow("search recommendations with valid id") + << QString(ValidKnownPlaceId) + << QString() + << QGeoShape() + << QList() + << QPlaceReply::NoError; + + QTest::newRow("search for recommendations with invalid id") + << QStringLiteral("does_not_exist_id") + << QString() + << QGeoShape() + << QList() + << QPlaceReply::PlaceDoesNotExistError; + + QTest::newRow("search for recommendations with id and search term") + << QString(ValidKnownPlaceId) + << QStringLiteral("sushi") + << QGeoShape() + << QList() + << QPlaceReply::BadArgumentError; + + QTest::newRow("search for recommendations with an id and category") + << QString(ValidKnownPlaceId) + << QString() + << QGeoShape() + << (QList() << eatDrinkCat) + << QPlaceReply::BadArgumentError; + + QTest::newRow("search for recommendations with id, search term and category") + << QString(ValidKnownPlaceId) + << QStringLiteral("sushi") + << QGeoShape() + << (QList() << eatDrinkCat) + << QPlaceReply::BadArgumentError; + + QTest::newRow("search for recommendations with an id and search area") + << QString(ValidKnownPlaceId) + << QString() + << static_cast(QGeoCircle(QGeoCoordinate(-27.5, 153))) + << QList() + << QPlaceReply::BadArgumentError; +} + +void tst_QPlaceManagerNokia::details() +{ + QSKIP("Fetching details from HERE place server always fails - QTBUG-44837"); + //fetch the details of a valid place + QPlace place; + QVERIFY(doFetchDetails(ValidKnownPlaceId, &place)); + QVERIFY(!place.name().isEmpty()); + QVERIFY(!place.icon().url().isEmpty()); + QStringList contactTypes = place.contactTypes(); + QVERIFY(!contactTypes.isEmpty()); + foreach (const QString &contactType, contactTypes) { + QList details = place.contactDetails(contactType); + QVERIFY(details.count() > 0); + foreach (const QPlaceContactDetail &detail, details) { + QVERIFY(!detail.label().isEmpty()); + QVERIFY(!detail.value().isEmpty()); + } + } + + QVERIFY(place.location().coordinate().isValid()); + QVERIFY(!place.location().address().isEmpty()); + QVERIFY(!place.location().address().text().isEmpty()); + QVERIFY(!place.location().address().isTextGenerated()); + + QVERIFY(place.ratings().average() >= 1 && place.ratings().average() <= 5); + QVERIFY(place.ratings().maximum() == 5); + QVERIFY(place.ratings().count() > 0); + + QVERIFY(place.categories().count() > 0); + foreach (const QPlaceCategory &category, place.categories()) { + QVERIFY(!category.name().isEmpty()); + QVERIFY(!category.categoryId().isEmpty()); + QVERIFY(!category.icon().url().isEmpty()); + } + + QVERIFY(!place.extendedAttributeTypes().isEmpty()); + QVERIFY(place.visibility() == QLocation::PublicVisibility); + QVERIFY(place.detailsFetched()); + + //attributions are optional and thus have not been explicitly tested. + + //fetch the details of a non-existent place + QVERIFY(doFetchDetails(QStringLiteral("does_not_exist"), &place, + QPlaceReply::PlaceDoesNotExistError)); +} + +void tst_QPlaceManagerNokia::categories() +{ + QVERIFY(doInitializeCategories()); + + QList categories = placeManager->childCategories(); + QVERIFY(categories.count() > 0); + foreach (const QPlaceCategory &category, categories) { + //check that we have valid fields + QVERIFY(!category.categoryId().isEmpty()); + QVERIFY(!category.name().isEmpty()); + QVERIFY(!category.icon().url().isEmpty()); + + //check we can retrieve the very same category by id + QCOMPARE(placeManager->category(category.categoryId()), category); + + //the here plugin supports a two-level level category tree + QVERIFY(placeManager->parentCategoryId(category.categoryId()).isEmpty()); + const QList childCats = + placeManager->childCategories(category.categoryId()); + if (!childCats.isEmpty()) { + foreach (const QPlaceCategory &child, childCats) { + // only two levels of categories hence 2.nd level has no further children + QVERIFY(placeManager->childCategories(child.categoryId()).isEmpty()); + QVERIFY(placeManager->parentCategoryId(child.categoryId()) == category.categoryId()); + } + } + } +} + +void tst_QPlaceManagerNokia::suggestions() +{ + QFETCH(QGeoShape, area); + QFETCH(QString, searchTerm); + QFETCH(QPlaceReply::Error, error); + QFETCH(ExpectedResults, expectedResults); + + QPlaceSearchRequest searchRequest; + searchRequest.setSearchArea(area); + searchRequest.setSearchTerm(searchTerm); + + QStringList results; + QVERIFY(doSearchSuggestions(searchRequest, &results, error)); + + if (expectedResults == NoResults) + QVERIFY(results.count() == 0); + else if (expectedResults == SomeResults) + QVERIFY(results.count() > 0); +} + + +void tst_QPlaceManagerNokia::suggestions_data() +{ + QTest::addColumn("area"); + QTest::addColumn("searchTerm"); + QTest::addColumn ("error"); + QTest::addColumn("expectedResults"); + + for (int i = 0; i < 3; i++) { + QByteArray suffix; + QGeoShape area; + if (i == 0) { + area = QGeoCircle(QGeoCoordinate(-27.5, 153)); + suffix = " (Circle - center only)"; + } else if (i == 1) { + area = QGeoCircle(QGeoCoordinate(-27.5, 153), 5000); + suffix = " (Circle - with radius specified)"; + } else { + area = QGeoRectangle(QGeoCoordinate(-26.5, 152), QGeoCoordinate(-28.5, 154)); + suffix = " (Rectangle)"; + } + + QByteArray dataTag = QByteArray("valid usage") + suffix; + QTest::newRow(dataTag) << area + << "sus" + << QPlaceReply::NoError + << SomeResults; + } + + //invalid areas and boundary testing + QList dataTags; + QList areas; + QList errors; + QList results; + commonAreas(&dataTags, &areas, &errors, &results); + + for (int i = 0; i < dataTags.count(); ++i) { + QTest::newRow(dataTags.at(i)) << areas.at(i) + << "sus" + << errors.at(i) + << results.at(i); + } + + QTest::newRow("no text") << static_cast(QGeoCircle(QGeoCoordinate(-27.5, 153))) + << QString() + << QPlaceReply::NoError + << NoResults; +} + +void tst_QPlaceManagerNokia::suggestionsMisc() +{ + //check providing a distance relevancy hint (should be ignored) + QPlaceSearchRequest searchRequest; + QStringList results; + searchRequest.setSearchArea(QGeoCircle(QGeoCoordinate(-27.5, 153))); + searchRequest.setSearchTerm(QStringLiteral("sus")); + searchRequest.setRelevanceHint(QPlaceSearchRequest::DistanceHint); + QVERIFY(doSearchSuggestions(searchRequest, &results, QPlaceReply::NoError)); + QVERIFY(results.count() > 0); + searchRequest.clear(); + + //check porviding a lexical place name relevancy hint (should be ignored) + searchRequest.setSearchArea(QGeoCircle(QGeoCoordinate(-27.5, 153))); + searchRequest.setSearchTerm(QStringLiteral("sus")); + searchRequest.setRelevanceHint(QPlaceSearchRequest::LexicalPlaceNameHint); + QVERIFY(doSearchSuggestions(searchRequest, &results, QPlaceReply::NoError)); + QVERIFY(results.count() > 0); + searchRequest.clear(); + + //check providing a category + QPlaceCategory eatDrinkCat; + eatDrinkCat.setCategoryId(QStringLiteral("eat-drink")); + searchRequest.setSearchArea(QGeoCircle(QGeoCoordinate(-27.5, 153))); + searchRequest.setSearchTerm(QStringLiteral("sus")); + searchRequest.setCategory(eatDrinkCat); + QVERIFY(doSearchSuggestions(searchRequest, &results, QPlaceReply::BadArgumentError)); + QCOMPARE(results.count(), 0); + searchRequest.clear(); + + //check providing a recommendation id + searchRequest.setSearchArea(QGeoCircle(QGeoCoordinate(-27.5, 153))); + searchRequest.setSearchTerm(QStringLiteral("sus")); + searchRequest.setRecommendationId("id"); + QVERIFY(doSearchSuggestions(searchRequest, &results, QPlaceReply::BadArgumentError)); +} + +void tst_QPlaceManagerNokia::locale() +{ + //check that the defualt locale is set + QCOMPARE(placeManager->locales().count(), 1); + QCOMPARE(placeManager->locales().at(0), QLocale()); + + //check that we can set different locales for the categories + placeManager->setLocale(QLocale("en")); + QVERIFY(doInitializeCategories()); + QList enCategories = placeManager->childCategories(); + QVERIFY(enCategories.count() > 0); + + placeManager->setLocale(QLocale("fi")); + QVERIFY(doInitializeCategories()); + QList fiCategories = placeManager->childCategories(); + + foreach (const QPlaceCategory enCat, enCategories) { + foreach (const QPlaceCategory fiCat, fiCategories) { + if (enCat.categoryId() == fiCat.categoryId()) { + QVERIFY(fiCat.name() != enCat.name()); + QVERIFY(fiCat == placeManager->category(fiCat.categoryId())); + } + } + } + + // we are skipping the check below because we are requesting + // details for a place without a search before. This implies + // URL templating must be possible which the HERE place + // server refuses. + + QSKIP("remainder of test skipped due to QTBUG-44837"); + + //check that setting a locale will affect place detail fetches. + QPlace place; + placeManager->setLocale(QLocale("en")); + QVERIFY(doFetchDetails(ValidKnownPlaceId, + &place)); + QString englishName = place.name(); + placeManager->setLocale(QLocale("fr")); + QVERIFY(doFetchDetails(ValidKnownPlaceId, + &place)); + QVERIFY(englishName != place.name()); +} + +void tst_QPlaceManagerNokia::content() +{ + QFETCH(QPlaceContent::Type, type); + + //check fetching of content + QPlaceContentRequest request; + request.setContentType(type); + request.setPlaceId(ValidKnownPlaceId); + QPlaceContent::Collection results; + QVERIFY(doFetchContent(request, &results)); + + QVERIFY(results.count() > 0); + + QMapIterator iter(results); + while (iter.hasNext()) { + iter.next(); + switch (type) { + case (QPlaceContent::ImageType): { + QPlaceImage image = iter.value(); + QVERIFY(!image.url().isEmpty()); + break; + } case (QPlaceContent::ReviewType) : { + QPlaceReview review = iter.value(); + QVERIFY(!review.dateTime().isValid()); + QVERIFY(!review.text().isEmpty()); + QVERIFY(review.rating() >= 1 && review.rating() <= 5); + + //title and language fields are optional and thus have not been + //explicitly tested + break; + } case (QPlaceContent::EditorialType): { + QPlaceEditorial editorial = iter.value(); + QVERIFY(!editorial.text().isEmpty()); + + //The language field is optional and thus has not been + //explicitly tested. + break; + } default: + QFAIL("Unknown content type"); + } + } + + //check total count + QPlaceContentReply *contentReply = placeManager->getPlaceContent(request); + QSignalSpy contentSpy(contentReply, SIGNAL(finished())); + QTRY_VERIFY_WITH_TIMEOUT(contentSpy.count() ==1, Timeout); + QVERIFY(contentReply->totalCount() > 0); + + if (contentReply->totalCount() >= 2) { + //try testing with a limit + request.setLimit(1); + QPlaceContent::Collection newResults; + QVERIFY(doFetchContent(request, &newResults)); + QCOMPARE(newResults.count(), 1); + QCOMPARE(newResults.values().first(), results.value(0)); + } +} + +void tst_QPlaceManagerNokia::content_data() +{ + QTest::addColumn("type"); + + QTest::newRow("images") << QPlaceContent::ImageType; + QTest::newRow("reviews") << QPlaceContent::ReviewType; + QTest::newRow("editorials") << QPlaceContent::EditorialType; +} + +void tst_QPlaceManagerNokia::unsupportedFunctions() +{ + QPlace place; + place.setName(QStringLiteral("Brisbane")); + + QVERIFY(doSavePlace(place, QPlaceReply::UnsupportedError)); + QVERIFY(doRemovePlace(place, QPlaceReply::UnsupportedError)); + + QPlaceCategory category; + category.setName(QStringLiteral("Accommodation")); + QVERIFY(doSaveCategory(category, QPlaceReply::UnsupportedError)); + QVERIFY(doRemoveCategory(category, QPlaceReply::UnsupportedError)); +} + +void tst_QPlaceManagerNokia::commonAreas(QList *dataTags, + QList *areas, + QList *errors, + QList *results) +{ + Q_ASSERT(dataTags); + Q_ASSERT(areas); + dataTags->append("Unknown shape for search area"); + areas->append(QGeoShape()); + errors->append(QPlaceReply::BadArgumentError); + results->append(NoResults); + + dataTags->append("NaN coordinate"); + areas->append(QGeoCircle(QGeoCoordinate())); + errors->append(QPlaceReply::BadArgumentError); + results->append(NoResults); + + dataTags->append("Valid latitude (upper boundary)"); + areas->append(QGeoCircle(QGeoCoordinate(90.0, 45))); + errors->append(QPlaceReply::NoError); + results->append(AnyResults); + + dataTags->append("Invalid latitude (upper boundary)"); + areas->append(QGeoCircle(QGeoCoordinate(90.1, 45))); + errors->append(QPlaceReply::BadArgumentError); + results->append(NoResults); + + dataTags->append("Valid latitude (lower boundary)"); + areas->append(QGeoCircle(QGeoCoordinate(-90.0, 45))); + errors->append(QPlaceReply::NoError); + results->append(AnyResults); + + dataTags->append("Invalid latitude (lower boundary)"); + areas->append(QGeoCircle(QGeoCoordinate(-90.1, 45))); + errors->append(QPlaceReply::BadArgumentError); + results->append(NoResults); + + dataTags->append("Valid longitude (lower boundary)"); + areas->append(QGeoCircle(QGeoCoordinate(-45, -180.0))); + errors->append(QPlaceReply::NoError); + results->append(AnyResults); + + dataTags->append("Invalid longitude (lower boundary)"); + areas->append(QGeoCircle(QGeoCoordinate(-45, -180.1))); + errors->append(QPlaceReply::BadArgumentError); + results->append(NoResults); + + dataTags->append("Valid longitude (upper boundary)"); + areas->append(QGeoCircle(QGeoCoordinate(-45, 180.0))); + errors->append(QPlaceReply::NoError); + results->append(AnyResults); + + dataTags->append("Invalid longitude (upper boundary)"); + areas->append(QGeoCircle(QGeoCoordinate(-45, 180.1))); + errors->append(QPlaceReply::BadArgumentError); + results->append(NoResults); + + dataTags->append("Invalid rectangular area"); + areas->append(QGeoRectangle(QGeoCoordinate(20,20), + QGeoCoordinate(30,10))); + errors->append(QPlaceReply::BadArgumentError); + results->append(NoResults); +} + +QTEST_GUILESS_MAIN(tst_QPlaceManagerNokia) + +#include "tst_places.moc" diff --git a/tests/auto/nokia_services/routing/error-no-route.xml b/tests/auto/nokia_services/routing/error-no-route.xml new file mode 100644 index 0000000..1e1560a --- /dev/null +++ b/tests/auto/nokia_services/routing/error-no-route.xml @@ -0,0 +1 @@ +

    NOROUTE: Request failed
    \ No newline at end of file diff --git a/tests/auto/nokia_services/routing/invalid-response-half-way-through.xml b/tests/auto/nokia_services/routing/invalid-response-half-way-through.xml new file mode 100644 index 0000000..545954a --- /dev/null +++ b/tests/auto/nokia_services/routing/invalid-response-half-way-through.xml @@ -0,0 +1,150 @@ + + + + + 2012-04-26T14:49:24.451Z + 2012-04-26T14:47:00.025+0000 + 5094886 + 2012-04-26T14:47:02.481+0000 + 12015 + 2012-04-26T14:47:02.481+0000 + 857 + 2011Q3 + routeserver,9.2-2012.02.20-hotfix6.2.13.1 + 22 + routing-route-service,6.2.13.1 + + + REMvaQUAAAB4tdyZCURKQJROJJhqxipAAAAAYAlESkAAAADAasYqQAAAAAAAAPB_AAAAAAAA8H9pqM_8V_SHZp4MKQHNgLOULCerAAEAAICiDCkBAQAAADAnqwABAAAAAADA_wEAAAAAAMD_HY0-82dwsAoDCQ + + -53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + + fastestNow + car + enabled + + 52.5315361,13.3875332 52.5315094,13.3872204 52.5314484,13.3868303 52.5308685,13.3871498 52.5304298,13.3873901 52.5303993,13.3872299 52.5303612,13.3871202 52.5293198,13.3846502 52.5289383,13.3851404 52.5288315,13.3852901 52.5287399,13.3853998 52.5285416,13.3856297 52.5283089,13.38591 52.5281982,13.3860397 52.5280991,13.3861103 52.5274811,13.3867598 52.5271416,13.3869896 52.5270386,13.3870201 52.5262985,13.3871603 52.5262985,13.38727 52.5262489,13.3874302 52.5261917,13.3877001 52.5260506,13.3882999 52.5259895,13.38873 52.5257187,13.3898802 52.5249786,13.3928604 52.5246773,13.3941345 + + + 52.5315361 + 13.3846502 + + + 52.5246773 + 13.3941345 + + + + + -53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + 1271.0 + 273.9 + + + 52.5315361 + 13.3875332 + + Head toward Eichendorffstraße on Invalidenstraße. Go for 150 feet. + 5.2 + 48.0 + -53499799 + forward + + + + 52.5314484 + 13.3868303 + + Turn left onto Eichendorffstraße. Go for 400 feet. + 47.1 + 119.0 + -780236888 + left + + + + 52.5304298 + 13.3873901 + + Turn right onto Schlegelstraße. Go for 0.1 miles. + 55.9 + 223.0 + -53499914 + right + + + + 52.5293198 + 13.3846502 + + Turn left onto Chausseestraße. Go for 0.2 miles. + 68.0 + 286.0 + -749446557 + left + + + + 52.5271416 + 13.3869896 + + Continue on Friedrichstraße, Oranienburger Tor. Go for 300 feet. + 20.1 + 93.0 + -572708773 + forward + + + + 52.5262985 + 13.3871603 + + Turn left onto Oranienburger Straße. Go for 0.3 miles. + 77.6 + 502.0 + +812293299 + left diff --git a/tests/auto/nokia_services/routing/invalid-response-no-route-tag.xml b/tests/auto/nokia_services/routing/invalid-response-no-route-tag.xml new file mode 100644 index 0000000..322f1a1 --- /dev/null +++ b/tests/auto/nokia_services/routing/invalid-response-no-route-tag.xml @@ -0,0 +1,18 @@ + + + + + 2012-04-26T14:49:24.451Z + 2012-04-26T14:47:00.025+0000 + 5094886 + 2012-04-26T14:47:02.481+0000 + 12015 + 2012-04-26T14:47:02.481+0000 + 857 + 2011Q3 + routeserver,9.2-2012.02.20-hotfix6.2.13.1 + 22 + routing-route-service,6.2.13.1 + + + \ No newline at end of file diff --git a/tests/auto/nokia_services/routing/invalid-response-trash.xml b/tests/auto/nokia_services/routing/invalid-response-trash.xml new file mode 100644 index 0000000000000000000000000000000000000000..ec6a3ae4c656164f56de5a2481e3625305f71a7f GIT binary patch literal 785 zcmZvay-Nc@5XI;2@@+7F7sg_(l&E-jBE|?B(O?ddh_*ICFM1jxTtcw0N}-l9l?Xxz z+E`flCs6N7MWtEfE66+7p3V+0;MXwGMz(NX5dSQd*`G5u*-q9yW0qy1qx+-* zXrQj{I)~X}78~k@k-*Q?nX!;CmlBI{+ArC;oLyjMaw&&PIn`jyBO)O_gO~UO6 zF)4La>Xy`qRQ_(fZbtTPWO; + + + + + + + 2012-04-26T14:49:24.451Z + + 2012-04-26T14:47:00.025+0000 + 5094886 + 2012-04-26T14:47:02.481+0000 + 12015 + 2012-04-26T14:47:02.481+0000 + 857 + 2011Q3 + routeserver,9.2-2012.02.20-hotfix6.2.13.1 + 22 + routing-route-service,6.2.13.1 + + + + + + + + + REMvaQUAAAB4tdyZCURKQJROJJhqxipAAAAAYAlESkAAAADAasYqQAAAAAAAAPB_AAAAAAAA8H9pqM_8V_SHZp4MKQHNgLOULCerAAEAAICiDCkBAQAAADAnqwABAAAAAADA_wEAAAAAAMD_HY0-82dwsAoDCQ + + + -53499799 + + 52.5315361 + 13.3875332 + 32 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + + fastestNow + car + enabled + + 52.5315361,13.3875332 52.5315094,13.3872204 52.5314484,13.3868303 52.5308685,13.3871498 52.5304298,13.3873901 52.5303993,13.3872299 52.5303612,13.3871202 52.5293198,13.3846502 52.5289383,13.3851404 52.5288315,13.3852901 52.5287399,13.3853998 52.5285416,13.3856297 52.5283089,13.38591 52.5281982,13.3860397 52.5280991,13.3861103 52.5274811,13.3867598 52.5271416,13.3869896 52.5270386,13.3870201 52.5262985,13.3871603 52.5262985,13.38727 52.5262489,13.3874302 52.5261917,13.3877001 52.5260506,13.3882999 52.5259895,13.38873 52.5257187,13.3898802 52.5249786,13.3928604 52.5246773,13.3941345 + + + 52.5315361 + 13.3846502 + + + 52.5246773 + 13.3941345 + + + + + + + + + + -53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + 1271.0 + 273.9 + + 232 + + 52.5315361 + 13.3875332 + + Head toward Eichendorffstraße on Invalidenstraße. Go for 150 feet. + 5.2 + 48.0 + -53499799 + forward + + + + 52.5314484 + 13.3868303 + + Turn left onto Eichendorffstraße. Go for 400 feet. + 47.1 + 119.0 + -780236888 + left + + + + 52.5304298 + 13.3873901 + + Turn right onto Schlegelstraße. Go for 0.1 miles. + 55.9 + 223.0 + -53499914 + right + + + + 52.5293198 + 13.3846502 + + Turn left onto Chausseestraße. Go for 0.2 miles. + 68.0 + 286.0 + -749446557 + left + + + + 52.5271416 + 13.3869896 + + Continue on Friedrichstraße, Oranienburger Tor. Go for 300 feet. + 20.1 + 93.0 + -572708773 + forward + + + + 52.5262985 + 13.3871603 + + Turn left onto Oranienburger Straße. Go for 0.3 miles. + 77.6 + 502.0 + +812293299 + left + + + + 52.5246773 + 13.3941345 + + Your destination on Oranienburger Straße is on the right. The trip takes 0.8 miles and 5 mins. + 0.0 + 0.0 + forward + + + -53499799 + 52.5315361,13.3875332 52.5315094,13.3872204 52.5314484,13.3868303 + 48.0 + 13.89 + + 9.17 + 5.2 + 9.72 + 4.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Invalidenstraße +
    + + + -780236888 + 52.5314484,13.3868303 52.5308685,13.3871498 + 68.0 + + 5.0 + 13.6 + 6.94 + 9.8 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Eichendorffstraße +
    + + + -780236887 + 52.5308685,13.3871498 52.5304298,13.3873901 + 51.0 + + 5.28 + 9.7 + 6.94 + 7.3 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Eichendorffstraße +
    + + + -53499914 + 52.5304298,13.3873901 52.5303993,13.3872299 52.5303612,13.3871202 52.5293198,13.3846502 + 223.0 + + 5.28 + 42.3 + 6.94 + 32.1 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Schlegelstraße +
    + + + -749446557 + 52.5293198,13.3846502 52.5289383,13.3851404 + 53.0 + 13.89 + + 10.83 + 4.9 + 9.72 + 5.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763170 + 52.5289383,13.3851404 52.5288315,13.3852901 + 15.0 + 13.89 + + 10.83 + 1.4 + 9.72 + 1.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763169 + 52.5288315,13.3852901 52.5287399,13.3853998 + 12.0 + 13.89 + + 10.83 + 1.1 + 9.72 + 1.2 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763164 + 52.5287399,13.3853998 52.5285416,13.3856297 + 26.0 + 13.89 + + 10.83 + 2.4 + 9.72 + 2.7 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763163 + 52.5285416,13.3856297 52.5283089,13.38591 + 32.0 + 13.89 + + 10.83 + 3.0 + 9.72 + 3.3 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -749446551 + 52.5283089,13.38591 52.5281982,13.3860397 + 15.0 + 13.89 + + 10.83 + 1.4 + 9.72 + 1.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -572708771 + 52.5281982,13.3860397 52.5280991,13.3861103 52.5274811,13.3867598 + 93.0 + 13.89 + + 10.83 + 8.6 + 9.72 + 9.6 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -53500039 + 52.5274811,13.3867598 52.5271416,13.3869896 + 40.0 + 13.89 + + 10.83 + 3.7 + 9.72 + 4.1 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -572708773 + 52.5271416,13.3869896 52.5270386,13.3870201 + 11.0 + 13.89 + + 10.28 + 1.1 + 9.72 + 1.1 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Friedrichstraße, Oranienburger Tor +
    + + + -572708772 + 52.5270386,13.3870201 52.5262985,13.3871603 + 82.0 + 13.89 + + 10.28 + 8.0 + 9.72 + 8.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Friedrichstraße +
    + + + +812293299 + 52.5262985,13.3871603 52.5262985,13.38727 + 7.0 + 13.89 + + 3.61 + 1.9 + 9.72 + 0.7 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -733054082 + 52.5262985,13.38727 52.5262489,13.3874302 + 12.0 + 13.89 + + 11.39 + 1.1 + 9.72 + 1.2 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -733054081 + 52.5262489,13.3874302 52.5261917,13.3877001 + 19.0 + 13.89 + + 11.39 + 1.7 + 9.72 + 2.0 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -572680644 + 52.5261917,13.3877001 52.5260506,13.3882999 + 43.0 + 13.89 + + 10.0 + 4.3 + 9.72 + 4.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -572680642 + 52.5260506,13.3882999 52.5259895,13.38873 + 29.0 + 13.89 + + 11.39 + 2.5 + 9.72 + 3.0 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -572680640 + 52.5259895,13.38873 52.5257187,13.3898802 + 83.0 + 13.89 + + 11.11 + 7.5 + 9.72 + 8.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -53501098 + 52.5257187,13.3898802 52.5249786,13.3928604 + 217.0 + 13.89 + + 8.33 + 26.0 + 9.72 + 22.3 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -53501113 + 52.5249786,13.3928604 52.5246773,13.3941345 + 92.0 + 13.89 + + 8.61 + 10.7 + 9.72 + 9.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + +
    + + 1271.0 + 1271.0 + 243.0 + + +
    +
    +
    \ No newline at end of file diff --git a/tests/auto/nokia_services/routing/multiple-routes-in-response.xml b/tests/auto/nokia_services/routing/multiple-routes-in-response.xml new file mode 100644 index 0000000..12a4d21 --- /dev/null +++ b/tests/auto/nokia_services/routing/multiple-routes-in-response.xml @@ -0,0 +1 @@ +2012-04-26T15:01:07.170Z2012-04-26T14:59:00.073+000050949822012-04-26T14:59:02.647+0000122232012-04-26T14:59:02.647+00007032011Q3routeserver,9.2-2012.02.20-hotfix6.2.13.120routing-route-service,6.2.13.1REMvFQUAAAB4tdyZCURKQJROJJhqxipAAAAAYAlESkAAAADAasYqQAAAAAAAAPB_AAAAAAAA8H-XVzADV_SHZp4MKQHNgLOULCerAAEAAICiDCkBAQAAADAnqwABAAAAAADA_wEAAAAAAMD_HY0-83dwYQCu+5349979952.531536113.387533252.53154313.387532stopOver-5350111352.524677313.394134552.52464613.394128stopOversceniccar52.5315361,13.3875332 52.5315819,13.3880997 52.5316582,13.3889303 52.5317612,13.3896999 52.5316315,13.3899002 52.5302505,13.3916502 52.5300598,13.3919001 52.5286217,13.3937197 52.5285416,13.3938799 52.5282288,13.3924398 52.5276604,13.3927898 52.5274887,13.39293 52.5264091,13.3935404 52.5262985,13.3935604 52.5254402,13.3931103 52.5249786,13.3928604 52.5246773,13.394134552.531761213.387533252.524677313.3941345+5349979952.531536113.387533252.53154313.387532stopOver-5350111352.524677313.394134552.52464613.394128stopOver1177.0237.752.531536113.3875332Head toward Borsigstraße on Invalidenstraße. Go for 500 feet.15.2148.0+53499799forward52.531761213.3896999Turn right onto Gartenstraße. Go for 0.3 miles.90.8454.0-811854188right52.528541613.3938799Turn right onto Torstraße. Go for 350 feet.28.4103.0-53499981right52.528228813.3924398Turn left onto Tucholskystraße. Go for 0.2 miles.72.4380.0-53500024left52.524978613.3928604Turn left onto Oranienburger Straße. Go for 300 feet.30.992.0-53501113left52.524677313.3941345Your destination on Oranienburger Straße is on the right. The trip takes 0.7 miles and 4 mins.0.00.0forward+5349979952.5315361,13.3875332 52.5315819,13.388099738.013.897.774.99.723.9
    DEBerlinBerlinBerlinMitteInvalidenstraße
    +5349978852.5315819,13.3880997 52.5316582,13.3889303 52.5317612,13.3896999110.013.897.7714.19.7211.3
    DEBerlinBerlinBerlinMitteInvalidenstraße
    -81185418852.5317612,13.3896999 52.5316315,13.389900219.05.03.86.942.7
    DEBerlinBerlinBerlinMitteGartenstraße
    -81185418752.5316315,13.3899002 52.5302505,13.3916502193.05.2836.66.9427.8
    DEBerlinBerlinBerlinMitteGartenstraße
    -5349987852.5302505,13.3916502 52.5300598,13.391900127.05.285.16.943.9
    DEBerlinBerlinBerlinMitteGartenstraße
    -5349996052.5300598,13.3919001 52.5286217,13.3937197 52.5285416,13.3938799215.05.2840.76.9431.0
    DEBerlinBerlinBerlinMitteGartenstraße
    -5349998152.5285416,13.3938799 52.5282288,13.3924398103.013.898.3312.46.9414.8
    DEBerlinBerlinBerlinMitteTorstraße
    -5350002452.5282288,13.3924398 52.5276604,13.3927898 52.5274887,13.3929388.04.7218.69.729.1
    DEBerlinBerlinBerlinMitteTucholskystraße
    -5350009152.5274887,13.39293 52.5264091,13.3935404126.04.7226.79.7213.0
    DEBerlinBerlinBerlinMitteTucholskystraße
    -84490623952.5264091,13.3935404 52.5262985,13.3935604 52.5254402,13.3931103112.04.1726.99.7211.5
    DEBerlinBerlinBerlinMitteTucholskystraße
    -84490623852.5254402,13.3931103 52.5249786,13.392860454.03.8913.99.725.6
    DEBerlinBerlinBerlinMitteTucholskystraße
    -5350111352.5249786,13.3928604 52.5246773,13.394134592.013.898.6110.79.729.5
    DEBerlinBerlinBerlinMitteOranienburger Straße
    1177.0309.0237.0
    REMvaQUAAAB4tdyZCURKQJROJJhqxipAAAAAYAlESkAAAADAasYqQAAAAAAAAPB_AAAAAAAA8H9pqM_8V_SHZp4MKQHNgLOULCerAAEAAICiDCkBAQAAADAnqwABAAAAAADA_wEAAAAAAMD_HY0-82dwsAoDCQ-5349979952.531536113.387533252.53154313.387532stopOver-5350111352.524677313.394134552.52464613.394128stopOverfastestNowcarenabled52.5315361,13.3875332 52.5315094,13.3872204 52.5314484,13.3868303 52.5308685,13.3871498 52.5304298,13.3873901 52.5303993,13.3872299 52.5303612,13.3871202 52.5293198,13.3846502 52.5289383,13.3851404 52.5288315,13.3852901 52.5287399,13.3853998 52.5285416,13.3856297 52.5283089,13.38591 52.5281982,13.3860397 52.5280991,13.3861103 52.5274811,13.3867598 52.5271416,13.3869896 52.5270386,13.3870201 52.5262985,13.3871603 52.5262985,13.38727 52.5262489,13.3874302 52.5261917,13.3877001 52.5260506,13.3882999 52.5259895,13.38873 52.5257187,13.3898802 52.5249786,13.3928604 52.5246773,13.394134552.531536113.384650252.524677313.3941345-5349979952.531536113.387533252.53154313.387532stopOver-5350111352.524677313.394134552.52464613.394128stopOver1271.0270.052.531536113.3875332Head toward Eichendorffstraße on Invalidenstraße. Go for 150 feet.5.248.0-53499799forward52.531448413.3868303Turn left onto Eichendorffstraße. Go for 400 feet.47.1119.0-780236888left52.530429813.3873901Turn right onto Schlegelstraße. Go for 0.1 miles.55.9223.0-53499914right52.529319813.3846502Turn left onto Chausseestraße. Go for 0.2 miles.64.9286.0-749446557left52.527141613.3869896Continue on Friedrichstraße, Oranienburger Tor. Go for 300 feet.19.393.0-572708773forward52.526298513.3871603Turn left onto Oranienburger Straße. Go for 0.3 miles.77.6502.0+812293299left52.524677313.3941345Your destination on Oranienburger Straße is on the right. The trip takes 0.8 miles and 5 mins.0.00.0forward-5349979952.5315361,13.3875332 52.5315094,13.3872204 52.5314484,13.386830348.013.899.175.29.724.9
    DEBerlinBerlinBerlinMitteInvalidenstraße
    -78023688852.5314484,13.3868303 52.5308685,13.387149868.05.013.66.949.8
    DEBerlinBerlinBerlinMitteEichendorffstraße
    -78023688752.5308685,13.3871498 52.5304298,13.387390151.05.289.76.947.3
    DEBerlinBerlinBerlinMitteEichendorffstraße
    -5349991452.5304298,13.3873901 52.5303993,13.3872299 52.5303612,13.3871202 52.5293198,13.3846502223.05.2842.36.9432.1
    DEBerlinBerlinBerlinMitteSchlegelstraße
    -74944655752.5293198,13.3846502 52.5289383,13.385140453.013.8910.834.99.725.5
    DEBerlinBerlinBerlinMitteChausseestraße
    -78176317052.5289383,13.3851404 52.5288315,13.385290115.013.8910.831.49.721.5
    DEBerlinBerlinBerlinMitteChausseestraße
    -78176316952.5288315,13.3852901 52.5287399,13.385399812.013.8910.831.19.721.2
    DEBerlinBerlinBerlinMitteChausseestraße
    -78176316452.5287399,13.3853998 52.5285416,13.385629726.013.8910.832.49.722.7
    DEBerlinBerlinBerlinMitteChausseestraße
    -78176316352.5285416,13.3856297 52.5283089,13.3859132.013.8910.833.09.723.3
    DEBerlinBerlinBerlinMitteChausseestraße
    -74944655152.5283089,13.38591 52.5281982,13.386039715.013.8910.831.49.721.5
    DEBerlinBerlinBerlinMitteChausseestraße
    -57270877152.5281982,13.3860397 52.5280991,13.3861103 52.5274811,13.386759893.013.8910.838.69.729.6
    DEBerlinBerlinBerlinMitteChausseestraße
    -5350003952.5274811,13.3867598 52.5271416,13.386989640.013.8910.833.79.724.1
    DEBerlinBerlinBerlinMitteChausseestraße
    -57270877352.5271416,13.3869896 52.5270386,13.387020111.013.8910.281.19.721.1
    DEBerlinBerlinBerlinMitteFriedrichstraße, Oranienburger Tor
    -57270877252.5270386,13.3870201 52.5262985,13.387160382.013.8910.288.09.728.4
    DEBerlinBerlinBerlinMitteFriedrichstraße
    +81229329952.5262985,13.3871603 52.5262985,13.387277.013.893.611.99.720.7
    DEBerlinBerlinBerlinMitteOranienburger Straße
    -73305408252.5262985,13.38727 52.5262489,13.387430212.013.8911.391.19.721.2
    DEBerlinBerlinBerlinMitteOranienburger Straße
    -73305408152.5262489,13.3874302 52.5261917,13.387700119.013.8911.391.79.722.0
    DEBerlinBerlinBerlinMitteOranienburger Straße
    -57268064452.5261917,13.3877001 52.5260506,13.388299943.013.8910.04.39.724.4
    DEBerlinBerlinBerlinMitteOranienburger Straße
    -57268064252.5260506,13.3882999 52.5259895,13.3887329.013.8911.392.59.723.0
    DEBerlinBerlinBerlinMitteOranienburger Straße
    -57268064052.5259895,13.38873 52.5257187,13.389880283.013.8911.117.59.728.5
    DEBerlinBerlinBerlinMitteOranienburger Straße
    -5350109852.5257187,13.3898802 52.5249786,13.3928604217.013.898.3326.09.7222.3
    DEBerlinBerlinBerlinMitteOranienburger Straße
    -5350111352.5249786,13.3928604 52.5246773,13.394134592.013.898.6110.79.729.5
    DEBerlinBerlinBerlinMitteOranienburger Straße
    1271.0243.0
    \ No newline at end of file diff --git a/tests/auto/nokia_services/routing/optim-fastest.xml b/tests/auto/nokia_services/routing/optim-fastest.xml new file mode 100644 index 0000000..43a7a77 --- /dev/null +++ b/tests/auto/nokia_services/routing/optim-fastest.xml @@ -0,0 +1,628 @@ + + + + + 2012-04-26T14:49:24.451Z + 2012-04-26T14:47:00.025+0000 + 5094886 + 2012-04-26T14:47:02.481+0000 + 12015 + 2012-04-26T14:47:02.481+0000 + 857 + 2011Q3 + routeserver,9.2-2012.02.20-hotfix6.2.13.1 + 22 + routing-route-service,6.2.13.1 + + + REMvaQUAAAB4tdyZCURKQJROJJhqxipAAAAAYAlESkAAAADAasYqQAAAAAAAAPB_AAAAAAAA8H9pqM_8V_SHZp4MKQHNgLOULCerAAEAAICiDCkBAQAAADAnqwABAAAAAADA_wEAAAAAAMD_HY0-82dwsAoDCQ + + -53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + + fastestNow + car + enabled + + 52.5315361,13.3875332 52.5315094,13.3872204 52.5314484,13.3868303 52.5308685,13.3871498 52.5304298,13.3873901 52.5303993,13.3872299 52.5303612,13.3871202 52.5293198,13.3846502 52.5289383,13.3851404 52.5288315,13.3852901 52.5287399,13.3853998 52.5285416,13.3856297 52.5283089,13.38591 52.5281982,13.3860397 52.5280991,13.3861103 52.5274811,13.3867598 52.5271416,13.3869896 52.5270386,13.3870201 52.5262985,13.3871603 52.5262985,13.38727 52.5262489,13.3874302 52.5261917,13.3877001 52.5260506,13.3882999 52.5259895,13.38873 52.5257187,13.3898802 52.5249786,13.3928604 52.5246773,13.3941345 + + + 52.5315361 + 13.3846502 + + + 52.5246773 + 13.3941345 + + + + + -53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + 1271.0 + 273.9 + + + 52.5315361 + 13.3875332 + + Head toward Eichendorffstraße on Invalidenstraße. Go for 150 feet. + 5.2 + 48.0 + -53499799 + forward + + + + 52.5314484 + 13.3868303 + + Turn left onto Eichendorffstraße. Go for 400 feet. + 47.1 + 119.0 + -780236888 + left + + + + 52.5304298 + 13.3873901 + + Turn right onto Schlegelstraße. Go for 0.1 miles. + 55.9 + 223.0 + -53499914 + right + + + + 52.5293198 + 13.3846502 + + Turn left onto Chausseestraße. Go for 0.2 miles. + 68.0 + 286.0 + -749446557 + left + + + + 52.5271416 + 13.3869896 + + Continue on Friedrichstraße, Oranienburger Tor. Go for 300 feet. + 20.1 + 93.0 + -572708773 + forward + + + + 52.5262985 + 13.3871603 + + Turn left onto Oranienburger Straße. Go for 0.3 miles. + 77.6 + 502.0 + +812293299 + left + + + + 52.5246773 + 13.3941345 + + Your destination on Oranienburger Straße is on the right. The trip takes 0.8 miles and 5 mins. + 0.0 + 0.0 + forward + + + -53499799 + 52.5315361,13.3875332 52.5315094,13.3872204 52.5314484,13.3868303 + 48.0 + 13.89 + + 9.17 + 5.2 + 9.72 + 4.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Invalidenstraße +
    + + + -780236888 + 52.5314484,13.3868303 52.5308685,13.3871498 + 68.0 + + 5.0 + 13.6 + 6.94 + 9.8 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Eichendorffstraße +
    + + + -780236887 + 52.5308685,13.3871498 52.5304298,13.3873901 + 51.0 + + 5.28 + 9.7 + 6.94 + 7.3 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Eichendorffstraße +
    + + + -53499914 + 52.5304298,13.3873901 52.5303993,13.3872299 52.5303612,13.3871202 52.5293198,13.3846502 + 223.0 + + 5.28 + 42.3 + 6.94 + 32.1 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Schlegelstraße +
    + + + -749446557 + 52.5293198,13.3846502 52.5289383,13.3851404 + 53.0 + 13.89 + + 10.83 + 4.9 + 9.72 + 5.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763170 + 52.5289383,13.3851404 52.5288315,13.3852901 + 15.0 + 13.89 + + 10.83 + 1.4 + 9.72 + 1.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763169 + 52.5288315,13.3852901 52.5287399,13.3853998 + 12.0 + 13.89 + + 10.83 + 1.1 + 9.72 + 1.2 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763164 + 52.5287399,13.3853998 52.5285416,13.3856297 + 26.0 + 13.89 + + 10.83 + 2.4 + 9.72 + 2.7 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763163 + 52.5285416,13.3856297 52.5283089,13.38591 + 32.0 + 13.89 + + 10.83 + 3.0 + 9.72 + 3.3 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -749446551 + 52.5283089,13.38591 52.5281982,13.3860397 + 15.0 + 13.89 + + 10.83 + 1.4 + 9.72 + 1.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -572708771 + 52.5281982,13.3860397 52.5280991,13.3861103 52.5274811,13.3867598 + 93.0 + 13.89 + + 10.83 + 8.6 + 9.72 + 9.6 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -53500039 + 52.5274811,13.3867598 52.5271416,13.3869896 + 40.0 + 13.89 + + 10.83 + 3.7 + 9.72 + 4.1 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -572708773 + 52.5271416,13.3869896 52.5270386,13.3870201 + 11.0 + 13.89 + + 10.28 + 1.1 + 9.72 + 1.1 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Friedrichstraße, Oranienburger Tor +
    + + + -572708772 + 52.5270386,13.3870201 52.5262985,13.3871603 + 82.0 + 13.89 + + 10.28 + 8.0 + 9.72 + 8.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Friedrichstraße +
    + + + +812293299 + 52.5262985,13.3871603 52.5262985,13.38727 + 7.0 + 13.89 + + 3.61 + 1.9 + 9.72 + 0.7 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -733054082 + 52.5262985,13.38727 52.5262489,13.3874302 + 12.0 + 13.89 + + 11.39 + 1.1 + 9.72 + 1.2 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -733054081 + 52.5262489,13.3874302 52.5261917,13.3877001 + 19.0 + 13.89 + + 11.39 + 1.7 + 9.72 + 2.0 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -572680644 + 52.5261917,13.3877001 52.5260506,13.3882999 + 43.0 + 13.89 + + 10.0 + 4.3 + 9.72 + 4.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -572680642 + 52.5260506,13.3882999 52.5259895,13.38873 + 29.0 + 13.89 + + 11.39 + 2.5 + 9.72 + 3.0 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -572680640 + 52.5259895,13.38873 52.5257187,13.3898802 + 83.0 + 13.89 + + 11.11 + 7.5 + 9.72 + 8.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -53501098 + 52.5257187,13.3898802 52.5249786,13.3928604 + 217.0 + 13.89 + + 8.33 + 26.0 + 9.72 + 22.3 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -53501113 + 52.5249786,13.3928604 52.5246773,13.3941345 + 92.0 + 13.89 + + 8.61 + 10.7 + 9.72 + 9.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + +
    + + 1271.0 + 243.0 + +
    +
    +
    \ No newline at end of file diff --git a/tests/auto/nokia_services/routing/optim-shortest.xml b/tests/auto/nokia_services/routing/optim-shortest.xml new file mode 100644 index 0000000..5aa8679 --- /dev/null +++ b/tests/auto/nokia_services/routing/optim-shortest.xml @@ -0,0 +1 @@ +2012-04-26T14:57:30.304Z2012-04-26T14:56:00.027+000050949832012-04-26T14:56:02.406+0000122232012-04-26T14:56:02.406+00005062011Q3routeserver,9.2-2012.02.20-hotfix6.2.13.141routing-route-service,6.2.13.1REMvFQUAAAB4tdyZCURKQJROJJhqxipAAAAAYAlESkAAAADAasYqQAAAAAAAAPB_AAAAAAAA8H-XVzADV_SHZp4MKQHNgLOULCerAAEAAICiDCkBAQAAADAnqwABAAAAAADA_wEAAAAAAMD_HY0-83dwYQCu+5349979952.531536113.387533252.53154313.387532stopOver-5350111352.524677313.394134552.52464613.394128stopOvershortestcar52.5315361,13.3875332 52.5315819,13.3880997 52.5316582,13.3889303 52.5317612,13.3896999 52.5316315,13.3899002 52.5302505,13.3916502 52.5300598,13.3919001 52.5286217,13.3937197 52.5285416,13.3938799 52.5282288,13.3924398 52.5276604,13.3927898 52.5274887,13.39293 52.5264091,13.3935404 52.5262985,13.3935604 52.5254402,13.3931103 52.5249786,13.3928604 52.5246773,13.394134552.531761213.387533252.524677313.3941345+5349979952.531536113.387533252.53154313.387532stopOver-5350111352.524677313.394134552.52464613.394128stopOver1177.0237.752.531536113.3875332Head toward Borsigstraße on Invalidenstraße. Go for 500 feet.15.2148.0+53499799forward52.531761213.3896999Turn right onto Gartenstraße. Go for 0.3 miles.90.8454.0-811854188right52.528541613.3938799Turn right onto Torstraße. Go for 350 feet.28.4103.0-53499981right52.528228813.3924398Turn left onto Tucholskystraße. Go for 0.2 miles.72.4380.0-53500024left52.524978613.3928604Turn left onto Oranienburger Straße. Go for 300 feet.30.992.0-53501113left52.524677313.3941345Your destination on Oranienburger Straße is on the right. The trip takes 0.7 miles and 4 mins.0.00.0forward+5349979952.5315361,13.3875332 52.5315819,13.388099738.013.897.774.99.723.9
    DEBerlinBerlinBerlinMitteInvalidenstraße
    +5349978852.5315819,13.3880997 52.5316582,13.3889303 52.5317612,13.3896999110.013.897.7714.19.7211.3
    DEBerlinBerlinBerlinMitteInvalidenstraße
    -81185418852.5317612,13.3896999 52.5316315,13.389900219.05.03.86.942.7
    DEBerlinBerlinBerlinMitteGartenstraße
    -81185418752.5316315,13.3899002 52.5302505,13.3916502193.05.2836.66.9427.8
    DEBerlinBerlinBerlinMitteGartenstraße
    -5349987852.5302505,13.3916502 52.5300598,13.391900127.05.285.16.943.9
    DEBerlinBerlinBerlinMitteGartenstraße
    -5349996052.5300598,13.3919001 52.5286217,13.3937197 52.5285416,13.3938799215.05.2840.76.9431.0
    DEBerlinBerlinBerlinMitteGartenstraße
    -5349998152.5285416,13.3938799 52.5282288,13.3924398103.013.898.3312.46.9414.8
    DEBerlinBerlinBerlinMitteTorstraße
    -5350002452.5282288,13.3924398 52.5276604,13.3927898 52.5274887,13.3929388.04.7218.69.729.1
    DEBerlinBerlinBerlinMitteTucholskystraße
    -5350009152.5274887,13.39293 52.5264091,13.3935404126.04.7226.79.7213.0
    DEBerlinBerlinBerlinMitteTucholskystraße
    -84490623952.5264091,13.3935404 52.5262985,13.3935604 52.5254402,13.3931103112.04.1726.99.7211.5
    DEBerlinBerlinBerlinMitteTucholskystraße
    -84490623852.5254402,13.3931103 52.5249786,13.392860454.03.8913.99.725.6
    DEBerlinBerlinBerlinMitteTucholskystraße
    -5350111352.5249786,13.3928604 52.5246773,13.394134592.013.898.6110.79.729.5
    DEBerlinBerlinBerlinMitteOranienburger Straße
    1177.0309.0237.0
    \ No newline at end of file diff --git a/tests/auto/nokia_services/routing/routing.pro b/tests/auto/nokia_services/routing/routing.pro new file mode 100644 index 0000000..f0ec3d6 --- /dev/null +++ b/tests/auto/nokia_services/routing/routing.pro @@ -0,0 +1,13 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_nokia_routing + +QT += network location testlib +INCLUDEPATH += $$PWD/../../../../src/plugins/geoservices/nokia + +HEADERS += $$PWD/../../../../src/plugins/geoservices/nokia/qgeonetworkaccessmanager.h +SOURCES += tst_routing.cpp + +OTHER_FILES += *.xml + +TESTDATA = $$OTHER_FILES diff --git a/tests/auto/nokia_services/routing/travelmode-car.xml b/tests/auto/nokia_services/routing/travelmode-car.xml new file mode 100644 index 0000000..43a7a77 --- /dev/null +++ b/tests/auto/nokia_services/routing/travelmode-car.xml @@ -0,0 +1,628 @@ + + + + + 2012-04-26T14:49:24.451Z + 2012-04-26T14:47:00.025+0000 + 5094886 + 2012-04-26T14:47:02.481+0000 + 12015 + 2012-04-26T14:47:02.481+0000 + 857 + 2011Q3 + routeserver,9.2-2012.02.20-hotfix6.2.13.1 + 22 + routing-route-service,6.2.13.1 + + + REMvaQUAAAB4tdyZCURKQJROJJhqxipAAAAAYAlESkAAAADAasYqQAAAAAAAAPB_AAAAAAAA8H9pqM_8V_SHZp4MKQHNgLOULCerAAEAAICiDCkBAQAAADAnqwABAAAAAADA_wEAAAAAAMD_HY0-82dwsAoDCQ + + -53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + + fastestNow + car + enabled + + 52.5315361,13.3875332 52.5315094,13.3872204 52.5314484,13.3868303 52.5308685,13.3871498 52.5304298,13.3873901 52.5303993,13.3872299 52.5303612,13.3871202 52.5293198,13.3846502 52.5289383,13.3851404 52.5288315,13.3852901 52.5287399,13.3853998 52.5285416,13.3856297 52.5283089,13.38591 52.5281982,13.3860397 52.5280991,13.3861103 52.5274811,13.3867598 52.5271416,13.3869896 52.5270386,13.3870201 52.5262985,13.3871603 52.5262985,13.38727 52.5262489,13.3874302 52.5261917,13.3877001 52.5260506,13.3882999 52.5259895,13.38873 52.5257187,13.3898802 52.5249786,13.3928604 52.5246773,13.3941345 + + + 52.5315361 + 13.3846502 + + + 52.5246773 + 13.3941345 + + + + + -53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + 1271.0 + 273.9 + + + 52.5315361 + 13.3875332 + + Head toward Eichendorffstraße on Invalidenstraße. Go for 150 feet. + 5.2 + 48.0 + -53499799 + forward + + + + 52.5314484 + 13.3868303 + + Turn left onto Eichendorffstraße. Go for 400 feet. + 47.1 + 119.0 + -780236888 + left + + + + 52.5304298 + 13.3873901 + + Turn right onto Schlegelstraße. Go for 0.1 miles. + 55.9 + 223.0 + -53499914 + right + + + + 52.5293198 + 13.3846502 + + Turn left onto Chausseestraße. Go for 0.2 miles. + 68.0 + 286.0 + -749446557 + left + + + + 52.5271416 + 13.3869896 + + Continue on Friedrichstraße, Oranienburger Tor. Go for 300 feet. + 20.1 + 93.0 + -572708773 + forward + + + + 52.5262985 + 13.3871603 + + Turn left onto Oranienburger Straße. Go for 0.3 miles. + 77.6 + 502.0 + +812293299 + left + + + + 52.5246773 + 13.3941345 + + Your destination on Oranienburger Straße is on the right. The trip takes 0.8 miles and 5 mins. + 0.0 + 0.0 + forward + + + -53499799 + 52.5315361,13.3875332 52.5315094,13.3872204 52.5314484,13.3868303 + 48.0 + 13.89 + + 9.17 + 5.2 + 9.72 + 4.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Invalidenstraße +
    + + + -780236888 + 52.5314484,13.3868303 52.5308685,13.3871498 + 68.0 + + 5.0 + 13.6 + 6.94 + 9.8 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Eichendorffstraße +
    + + + -780236887 + 52.5308685,13.3871498 52.5304298,13.3873901 + 51.0 + + 5.28 + 9.7 + 6.94 + 7.3 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Eichendorffstraße +
    + + + -53499914 + 52.5304298,13.3873901 52.5303993,13.3872299 52.5303612,13.3871202 52.5293198,13.3846502 + 223.0 + + 5.28 + 42.3 + 6.94 + 32.1 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Schlegelstraße +
    + + + -749446557 + 52.5293198,13.3846502 52.5289383,13.3851404 + 53.0 + 13.89 + + 10.83 + 4.9 + 9.72 + 5.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763170 + 52.5289383,13.3851404 52.5288315,13.3852901 + 15.0 + 13.89 + + 10.83 + 1.4 + 9.72 + 1.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763169 + 52.5288315,13.3852901 52.5287399,13.3853998 + 12.0 + 13.89 + + 10.83 + 1.1 + 9.72 + 1.2 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763164 + 52.5287399,13.3853998 52.5285416,13.3856297 + 26.0 + 13.89 + + 10.83 + 2.4 + 9.72 + 2.7 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -781763163 + 52.5285416,13.3856297 52.5283089,13.38591 + 32.0 + 13.89 + + 10.83 + 3.0 + 9.72 + 3.3 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -749446551 + 52.5283089,13.38591 52.5281982,13.3860397 + 15.0 + 13.89 + + 10.83 + 1.4 + 9.72 + 1.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -572708771 + 52.5281982,13.3860397 52.5280991,13.3861103 52.5274811,13.3867598 + 93.0 + 13.89 + + 10.83 + 8.6 + 9.72 + 9.6 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -53500039 + 52.5274811,13.3867598 52.5271416,13.3869896 + 40.0 + 13.89 + + 10.83 + 3.7 + 9.72 + 4.1 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Chausseestraße +
    + + + -572708773 + 52.5271416,13.3869896 52.5270386,13.3870201 + 11.0 + 13.89 + + 10.28 + 1.1 + 9.72 + 1.1 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Friedrichstraße, Oranienburger Tor +
    + + + -572708772 + 52.5270386,13.3870201 52.5262985,13.3871603 + 82.0 + 13.89 + + 10.28 + 8.0 + 9.72 + 8.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Friedrichstraße +
    + + + +812293299 + 52.5262985,13.3871603 52.5262985,13.38727 + 7.0 + 13.89 + + 3.61 + 1.9 + 9.72 + 0.7 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -733054082 + 52.5262985,13.38727 52.5262489,13.3874302 + 12.0 + 13.89 + + 11.39 + 1.1 + 9.72 + 1.2 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -733054081 + 52.5262489,13.3874302 52.5261917,13.3877001 + 19.0 + 13.89 + + 11.39 + 1.7 + 9.72 + 2.0 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -572680644 + 52.5261917,13.3877001 52.5260506,13.3882999 + 43.0 + 13.89 + + 10.0 + 4.3 + 9.72 + 4.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -572680642 + 52.5260506,13.3882999 52.5259895,13.38873 + 29.0 + 13.89 + + 11.39 + 2.5 + 9.72 + 3.0 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -572680640 + 52.5259895,13.38873 52.5257187,13.3898802 + 83.0 + 13.89 + + 11.11 + 7.5 + 9.72 + 8.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -53501098 + 52.5257187,13.3898802 52.5249786,13.3928604 + 217.0 + 13.89 + + 8.33 + 26.0 + 9.72 + 22.3 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -53501113 + 52.5249786,13.3928604 52.5246773,13.3941345 + 92.0 + 13.89 + + 8.61 + 10.7 + 9.72 + 9.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + +
    + + 1271.0 + 243.0 + +
    +
    +
    \ No newline at end of file diff --git a/tests/auto/nokia_services/routing/travelmode-pedestrian.xml b/tests/auto/nokia_services/routing/travelmode-pedestrian.xml new file mode 100644 index 0000000..56a79e3 --- /dev/null +++ b/tests/auto/nokia_services/routing/travelmode-pedestrian.xml @@ -0,0 +1,798 @@ + + + + + 2012-04-26T14:48:17.998Z + 2012-04-26T14:47:00.026+0000 + 5094886 + 2012-04-26T14:47:02.413+0000 + 12015 + 2012-04-26T14:47:02.413+0000 + 857 + 2011Q3 + routeserver,9.2-2012.02.20-hotfix6.2.13.1 + 96 + routing-route-service,6.2.13.1 + + + REMvSAkAAAB4tdyZCURKQJROJJhqxipAAAAAYAlESkAAAADAasYqQAAAAAAAAPB_AAAAAAAA8H-XVzADV_SHZp4MKQHNgLOULCerAAEAAICiDCkBAQAAADAnqwABAAAAAADA_wEAAAAAAMD_HY0-84fTC85nlF5wPmO1JsEZpheczwc8AOrpMz9xhQUzcABtrGd-FJ0-86Po9JnfAAHBo80GTA + + +53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + + fastestNow + pedestrian + enabled + + 52.5314903,13.3875389 52.5315857,13.3881445 52.5315475,13.3880892 52.5310287,13.388505 52.5309753,13.3885479 52.5307274,13.3887367 52.530674,13.3887777 52.5297775,13.3894777 52.5297241,13.3895216 52.5280571,13.3913355 52.5279846,13.3914194 52.5281792,13.3924217 52.5276413,13.392765 52.527504,13.3928776 52.5274544,13.3929157 52.5274773,13.3929968 52.5264435,13.3935556 52.5263863,13.3935757 52.5262947,13.3935919 52.525425,13.393137 52.5249977,13.3929052 52.5249405,13.3928967 52.5246468,13.3941231 + + + 52.5315857 + 13.3875389 + + + 52.5246468 + 13.3941231 + + + + + +53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + 1107.0 + 798.2 + + + 52.5315361 + 13.3875332 + + Head toward Borsigstraße on Invalidenstraße. Go for 100 feet. + 27.4 + 38.0 + +53499799 + forward + + + + 52.5315819 + 13.3880997 + + Turn right and use the crosswalk. + 6.0 + 8.0 + -811853913 + right + + + + 52.5315819 + 13.3880997 + + Continue on Borsigstraße. Go for 0.3 miles. + 343.4 + 476.0 + -811853915 + forward + + + + 52.5280304 + 13.3914099 + + Cross Torstraße. + 5.0 + 8.0 + -833290988 + forward + + + + 52.5280304 + 13.3914099 + + Turn left onto Torstraße. Go for 250 feet. + 52.6 + 73.0 + +53499991 + left + + + + 52.5282288 + 13.3924398 + + Turn right onto Tucholskystraße. Go for 300 feet. + 63.4 + 88.0 + -53500024 + right + + + + 52.5274887 + 13.39293 + + Cross Linienstraße, Tucholskystraße. + 12.0 + 16.0 + -53520166 + forward + + + + 52.5274887 + 13.39293 + + Turn right onto Tucholskystraße. Go for 0.2 miles. + 216.2 + 300.0 + -53500091 + right + + + + 52.5249786 + 13.3928604 + + Cross Oranienburger Straße. + 6.0 + 8.0 + -53501113 + forward + + + + 52.5249786 + 13.3928604 + + Turn left onto Oranienburger Straße. Go for 300 feet. + 66.2 + 92.0 + -53501113 + left + + + + 52.5246773 + 13.3941345 + + Your destination on Oranienburger Straße is on the right. The trip takes 0.7 miles and 13 mins. + 0.0 + 0.0 + forward + + + +53499799 + 52.5314903,13.3875389 52.5315857,13.3881445 + 38.0 + 13.89 + + 7.77 + 4.9 + 1.39 + 27.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Invalidenstraße +
    + + + -811853913 + 52.5315857,13.3881445 52.5315475,13.3880892 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte +
    + + + +811853913 + 52.5315475,13.3880892 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte +
    + + + -811853915 + 52.5315475,13.3880892 52.5310287,13.388505 + 70.0 + + 1.39 + 50.4 + 1.39 + 50.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Borsigstraße +
    + + + -838257237 + 52.5310287,13.388505 52.5309753,13.3885479 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte +
    + + + +838257237 + 52.5309753,13.3885479 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte +
    + + + -811853914 + 52.5309753,13.3885479 52.5307274,13.3887367 + 36.0 + + 1.39 + 25.9 + 1.39 + 25.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Borsigstraße +
    + + + -53499858 + 52.5307274,13.3887367 52.530674,13.3887777 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Schlegelstraße +
    + + + +53499858 + 52.530674,13.3887777 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Schlegelstraße +
    + + + -53499890 + 52.530674,13.3887777 52.5297775,13.3894777 + 116.0 + + 1.39 + 83.5 + 1.39 + 83.5 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Borsigstraße +
    + + + -53499910 + 52.5297775,13.3894777 52.5297241,13.3895216 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tieckstraße +
    + + + +53499910 + 52.5297241,13.3895216 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tieckstraße +
    + + + -53499992 + 52.5297241,13.3895216 52.5280571,13.3913355 + 230.0 + + 1.39 + 165.6 + 1.39 + 165.6 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Borsigstraße +
    + + + -833290988 + 52.5280571,13.3913355 52.5279846,13.3914194 + 4.0 + 13.89 + + 8.33 + 0.5 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Torstraße +
    + + + +833290988 + 52.5279846,13.3914194 + 4.0 + 13.89 + + 7.5 + 0.5 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Torstraße +
    + + + +53499991 + 52.5279846,13.3914194 52.5281792,13.3924217 + 73.0 + 13.89 + + 7.5 + 9.7 + 1.39 + 52.6 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Torstraße +
    + + + -53500024 + 52.5281792,13.3924217 52.5276413,13.392765 52.527504,13.3928776 + 88.0 + + 1.39 + 63.4 + 1.39 + 63.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tucholskystraße +
    + + + -53520166 + 52.527504,13.3928776 52.5274544,13.3929157 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Linienstraße +
    + + + +53520166 + 52.5274544,13.3929157 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Linienstraße +
    + + + -53500091 + 52.5274544,13.3929157 52.5274773,13.3929968 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tucholskystraße +
    + + + +53500091 + 52.5274773,13.3929968 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tucholskystraße +
    + + + -53500091 + 52.5274773,13.3929968 52.5264435,13.3935556 + 126.0 + + 1.39 + 90.7 + 1.39 + 90.7 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tucholskystraße +
    + + + +53500090 + 52.5264435,13.3935556 52.5263863,13.3935757 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Auguststraße +
    + + + -53500090 + 52.5263863,13.3935757 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Auguststraße +
    + + + -844906239 + 52.5263863,13.3935757 52.5262947,13.3935919 52.525425,13.393137 + 112.0 + + 1.39 + 80.6 + 1.39 + 80.6 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tucholskystraße +
    + + + -844906238 + 52.525425,13.393137 52.5249977,13.3929052 + 54.0 + + 1.39 + 38.9 + 1.39 + 38.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tucholskystraße +
    + + + -53501113 + 52.5249977,13.3929052 52.5249405,13.3928967 + 4.0 + 13.89 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + +53501113 + 52.5249405,13.3928967 + 4.0 + 13.89 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -53501113 + 52.5249405,13.3928967 52.5246468,13.3941231 + 92.0 + 13.89 + + 1.39 + 66.2 + 1.39 + 66.2 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + +
    + + 1107.0 + 798.0 + 798.0 + noThroughRoad + +
    +
    +
    \ No newline at end of file diff --git a/tests/auto/nokia_services/routing/travelmode-public-transport.xml b/tests/auto/nokia_services/routing/travelmode-public-transport.xml new file mode 100644 index 0000000..cf146ea --- /dev/null +++ b/tests/auto/nokia_services/routing/travelmode-public-transport.xml @@ -0,0 +1,343 @@ + + + + + 2012-04-26T14:49:40.902Z + 2012-04-26T14:47:00.026+0000 + 5094886 + 2012-04-26T14:47:02.494+0000 + 12015 + 2012-04-26T14:47:02.494+0000 + 857 + 2011Q3 + routeserver,9.2-2012.02.20-hotfix6.2.13.1 + 142 + routing-route-service,6.2.13.1 + + + REMvTwUAAAB4tdyZCURKQJROJJhqxipAAAAAYAlESkAAAADAasYqQAAAAAAAAPB_AAAAAAAA8H-XVzADV_SHZp4MKQHNgLOULCerAAEAAICiDCkBAQAAADAnqwABAAAAAADA_wEAAAAAAMD_HY0-84f6AgAAvBcAABjgZf7_Hyzz__8Hkw + + +53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + + fastestNow + publicTransport + enabled + + 52.5315819,13.3875275 52.5315819,13.3880997 52.5316086,13.3882599 52.5323792,13.3873796 52.5251007,13.3922596 52.5251007,13.3922596 52.5249786,13.3928604 52.5249443,13.3928757 52.5246468,13.3941231 + + + 52.5323792 + 13.3873796 + + + 52.5246468 + 13.3941231 + + + + + +53499799 + + 52.5315361 + 13.3875332 + + + 52.531543 + 13.387532 + + stopOver + + + -53501113 + + 52.5246773 + 13.3941345 + + + 52.524646 + 13.394128 + + stopOver + + 1388.0 + 641.3 + + + 52.5315361 + 13.3875332 + + Head toward Borsigstraße on Invalidenstraße. Go for 100 feet. + 27.4 + 38.0 + +53499799 + forward + + + + 52.5315819 + 13.3880997 + + Leave Invalidenstraße Go for 400 feet. + 115.7 + 115.0 + +1525 + forward + + + + 52.5323792 + 13.3873796 + + Take REGIONALMETRO "?es "?ains (Deutsche Bahn), departure 18:55. + 378.7 + 1092.0 + + + + + 52.5251007 + 13.3922596 + + Go through the virtual connection. Go for 150 feet. + 47.4 + 43.0 + -3281 + forward + + + + 52.5249786 + 13.3928604 + + Turn right and cross Tucholskystraße. + 6.0 + 8.0 + -53501259 + right + + + + 52.5249786 + 13.3928604 + + Continue on Oranienburger Straße. Go for 300 feet. + 66.2 + 92.0 + -53501113 + forward + + + + 52.5246773 + 13.3941345 + + Your destination on Oranienburger Straße is on the right. The trip takes 0.9 miles and 11 mins. + 0.0 + 0.0 + forward + + + +53499799 + 52.5315819,13.3875275 52.5315819,13.3880997 + 38.0 + 13.89 + + 7.77 + 4.9 + 1.39 + 27.4 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Invalidenstraße +
    + + + +1525 + 52.5315819,13.3880997 52.5316086,13.3882599 + 11.0 + 1.39 + + 1.39 + 7.9 + 1.39 + 7.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Nordbahnhof +
    + + + +1519 + 52.5316086,13.3882599 52.5323792,13.3873796 + 104.0 + 1.39 + + 1.39 + 74.9 + 1.39 + 74.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Nordbahnhof +
    + + + -8016 + 52.5323792,13.3873796 52.5251007,13.3922596 + 1092.0 + + + -3281 + 52.5251007,13.3922596 52.5251007,13.3922596 + 1.0 + 1.39 + + 1.39 + 0.7 + 1.39 + 0.7 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -3284 + 52.5251007,13.3922596 52.5249786,13.3928604 + 42.0 + 1.39 + + 1.39 + 30.2 + 1.39 + 30.2 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + + + -53501259 + 52.5249786,13.3928604 52.5249443,13.3928757 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tucholskystraße +
    + + + +53501259 + 52.5249443,13.3928757 + 4.0 + + 1.39 + 2.9 + 1.39 + 2.9 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Tucholskystraße +
    + + + -53501113 + 52.5249443,13.3928757 52.5246468,13.3941231 + 92.0 + 13.89 + + 1.39 + 66.2 + 1.39 + 66.2 + +
    + + DE + Berlin + Berlin + Berlin + Mitte + Oranienburger Straße +
    + +
    + + 1388.0 + 641.0 + 641.0 + noThroughRoad + unpaved + publicTransport + +
    +
    +
    \ No newline at end of file diff --git a/tests/auto/nokia_services/routing/tst_routing.cpp b/tests/auto/nokia_services/routing/tst_routing.cpp new file mode 100644 index 0000000..c214556 --- /dev/null +++ b/tests/auto/nokia_services/routing/tst_routing.cpp @@ -0,0 +1,517 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +#define CHECK_CLOSE_E(expected, actual, e) QVERIFY((qAbs(actual - expected) <= e)) +#define CHECK_CLOSE(expected, actual) CHECK_CLOSE_E(expected, actual, qreal(1e-6)) + +class MockGeoNetworkReply : public QNetworkReply +{ +public: + MockGeoNetworkReply( QObject* parent = 0); + virtual void abort(); + + void setFile(QFile* file); + void complete(); + using QNetworkReply::setRequest; + using QNetworkReply::setOperation; + using QNetworkReply::setError; + +protected: + virtual qint64 readData(char *data, qint64 maxlen); + virtual qint64 writeData(const char *data, qint64 len); + +private: + QFile* m_file; +}; + +MockGeoNetworkReply::MockGeoNetworkReply(QObject* parent) +: QNetworkReply(parent) +, m_file(0) +{ + setOpenMode(QIODevice::ReadOnly); +} + +void MockGeoNetworkReply::abort() +{} + +qint64 MockGeoNetworkReply::readData(char *data, qint64 maxlen) +{ + if (m_file) { + const qint64 read = m_file->read(data, maxlen); + if (read <= 0) + return -1; + return read; + } + return -1; +} + +qint64 MockGeoNetworkReply::writeData(const char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + return -1; +} + +void MockGeoNetworkReply::setFile(QFile* file) +{ + delete m_file; + m_file = file; + if (m_file) + m_file->setParent(this); +} + +void MockGeoNetworkReply::complete() +{ + if (error() != QNetworkReply::NoError) + emit error(error()); + setFinished(true); + emit finished(); +} + +class MockGeoNetworkAccessManager : public QGeoNetworkAccessManager +{ +public: + MockGeoNetworkAccessManager(QObject* parent = 0); + QNetworkReply* get(const QNetworkRequest& request); + QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data); + + void setReply(MockGeoNetworkReply* reply); + +private: + MockGeoNetworkReply* m_reply; +}; + +MockGeoNetworkAccessManager::MockGeoNetworkAccessManager(QObject* parent) +: QGeoNetworkAccessManager(parent) +, m_reply(0) +{} + +QNetworkReply* MockGeoNetworkAccessManager::get(const QNetworkRequest& request) +{ + MockGeoNetworkReply* r = m_reply; + m_reply = 0; + if (r) { + r->setRequest(request); + r->setOperation(QNetworkAccessManager::GetOperation); + r->setParent(0); + } + + return r; +} + +QNetworkReply* MockGeoNetworkAccessManager::post(const QNetworkRequest &request, const QByteArray &data) +{ + Q_UNUSED(request); + Q_UNUSED(data); + QTest::qFail("Not implemented", __FILE__, __LINE__); + return new MockGeoNetworkReply(); +} + +void MockGeoNetworkAccessManager::setReply(MockGeoNetworkReply* reply) +{ + delete m_reply; + m_reply = reply; + if (m_reply) + m_reply->setParent(this); +} + +class tst_nokia_routing : public QObject +{ + Q_OBJECT + +public: + tst_nokia_routing(); + +private: + void calculateRoute(); + void loadReply(const QString& filename); + void onReply(QGeoRouteReply* reply); + void verifySaneRoute(const QGeoRoute& route); + + // Infrastructure slots +private Q_SLOTS: + void routingFinished(QGeoRouteReply* reply); + void routingError(QGeoRouteReply* reply, QGeoRouteReply::Error error, QString errorString); + + // Test slots +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void cleanup(); + void can_compute_route_for_all_supported_travel_modes(); + void can_compute_route_for_all_supported_travel_modes_data(); + void can_compute_route_for_all_supported_optimizations(); + void can_compute_route_for_all_supported_optimizations_data(); + void can_handle_multiple_routes_in_response(); + void can_handle_no_route_exists_case(); + void can_handle_invalid_server_responses(); + void can_handle_invalid_server_responses_data(); + void can_handle_additions_to_routing_xml(); + void foobar(); + void foobar_data(); + +private: + QGeoServiceProvider* m_geoServiceProvider; + MockGeoNetworkAccessManager* m_networkManager; + QGeoRoutingManager* m_routingManager; + QGeoRouteReply* m_reply; + MockGeoNetworkReply* m_replyUnowned; + QGeoRouteRequest m_dummyRequest; + bool m_calculationDone; + bool m_expectError; +}; + +tst_nokia_routing::tst_nokia_routing() +: m_geoServiceProvider(0) +, m_networkManager(0) +, m_routingManager(0) +, m_reply(0) +, m_replyUnowned() +, m_calculationDone(true) +, m_expectError(false) +{ +} + +void tst_nokia_routing::loadReply(const QString& filename) +{ + QFile* file = new QFile(QFINDTESTDATA(filename)); + if (!file->open(QIODevice::ReadOnly)) { + delete file; + file = 0; + qDebug() << filename; + QTest::qFail("Failed to open file", __FILE__, __LINE__); + } + + m_replyUnowned = new MockGeoNetworkReply(); + m_replyUnowned->setFile(file); + m_networkManager->setReply(m_replyUnowned); +} + +void tst_nokia_routing::calculateRoute() +{ + QVERIFY2(m_replyUnowned, "No reply set"); + m_calculationDone = false; + m_routingManager->calculateRoute(m_dummyRequest); + m_replyUnowned->complete(); + m_replyUnowned = 0; + QTRY_VERIFY_WITH_TIMEOUT(m_calculationDone, 100); +} + +void tst_nokia_routing::onReply(QGeoRouteReply* reply) +{ + QVERIFY(reply); + //QVERIFY(0 == m_reply); + m_reply = reply; + if (m_reply) + m_reply->setParent(0); + m_calculationDone = true; +} + +void tst_nokia_routing::verifySaneRoute(const QGeoRoute& route) +{ + QVERIFY(route.distance() > 0); + QVERIFY(route.travelTime() > 0); + QVERIFY(route.travelMode() != 0); + + const QGeoRectangle bounds = route.bounds(); + QVERIFY(bounds.width() > 0); + QVERIFY(bounds.height() > 0); + + const QList path = route.path(); + QVERIFY(path.size() >= 2); + + foreach (const QGeoCoordinate& coord, path) { + QVERIFY(coord.isValid()); + QVERIFY(bounds.contains(coord)); + } + + QGeoRouteSegment segment = route.firstRouteSegment(); + bool first = true, last = false; + + do { + const QGeoRouteSegment next = segment.nextRouteSegment(); + last = next.isValid(); + + QVERIFY(segment.isValid()); + QVERIFY(segment.distance() >= 0); + QVERIFY(segment.travelTime() >= 0); // times are rounded and thus may end up being zero + + const QList path = segment.path(); + foreach (const QGeoCoordinate& coord, path) { + QVERIFY(coord.isValid()); + if (!first && !last) { + QVERIFY(bounds.contains(coord)); // on pt and pedestrian + } + } + + const QGeoManeuver maneuver = segment.maneuver(); + + if (maneuver.isValid()) { + QVERIFY(!maneuver.instructionText().isEmpty()); + QVERIFY(maneuver.position().isValid()); + if (!first && !last) { + QVERIFY(bounds.contains(maneuver.position())); // on pt and pedestrian + } + } + + segment = next; + first = false; + } while (!last); +} + +void tst_nokia_routing::routingFinished(QGeoRouteReply* reply) +{ + onReply(reply); +} + +void tst_nokia_routing::routingError(QGeoRouteReply* reply, QGeoRouteReply::Error error, QString errorString) +{ + Q_UNUSED(error); + + if (!m_expectError) { + QFAIL(qPrintable(errorString)); + } else { + onReply(reply); + } +} + +void tst_nokia_routing::initTestCase() +{ + QStringList providers = QGeoServiceProvider::availableServiceProviders(); + QVERIFY(providers.contains(QStringLiteral("here"))); + + m_networkManager = new MockGeoNetworkAccessManager(); + + QVariantMap parameters; + parameters.insert(QStringLiteral("nam"), QVariant::fromValue(m_networkManager)); + parameters.insert(QStringLiteral("here.app_id"), "stub"); + parameters.insert(QStringLiteral("here.token"), "stub"); + + m_geoServiceProvider = new QGeoServiceProvider(QStringLiteral("here"), parameters); + QVERIFY(m_geoServiceProvider); + + m_routingManager = m_geoServiceProvider->routingManager(); + QVERIFY(m_routingManager); + + connect(m_routingManager, SIGNAL(finished(QGeoRouteReply*)), + this, SLOT(routingFinished(QGeoRouteReply*))); + connect(m_routingManager, SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString)), + this, SLOT(routingError(QGeoRouteReply*,QGeoRouteReply::Error,QString))); + + QList waypoints; + waypoints.push_back(QGeoCoordinate(1, 1)); + waypoints.push_back(QGeoCoordinate(2, 2)); + m_dummyRequest.setWaypoints(waypoints); +} + +void tst_nokia_routing::cleanupTestCase() +{ + delete m_geoServiceProvider; + + // network access manager will be deleted by plugin + + m_geoServiceProvider = 0; + m_networkManager = 0; + m_routingManager = 0; +} + +void tst_nokia_routing::cleanup() +{ + delete m_reply; + m_reply = 0; + m_replyUnowned = 0; + m_expectError = false; +} + +void tst_nokia_routing::can_compute_route_for_all_supported_travel_modes() +{ + QFETCH(int, travelMode); + QFETCH(QString, file); + QFETCH(qreal, distance); + QFETCH(int, duration); + + loadReply(file); + calculateRoute(); + + QList routes = m_reply->routes(); + QCOMPARE(1, routes.size()); + QGeoRoute& route = routes[0]; + QCOMPARE(travelMode, (int)route.travelMode()); + CHECK_CLOSE(distance, route.distance()); + QCOMPARE(duration, route.travelTime()); + verifySaneRoute(route); +} + +void tst_nokia_routing::can_compute_route_for_all_supported_travel_modes_data() +{ + QTest::addColumn("travelMode"); + QTest::addColumn("file"); + QTest::addColumn("distance"); + QTest::addColumn("duration"); + + QTest::newRow("Car") << (int)QGeoRouteRequest::CarTravel << QString("travelmode-car.xml") << (qreal)1271.0 << 243; + QTest::newRow("Pedestrian") << (int)QGeoRouteRequest::PedestrianTravel << QString("travelmode-pedestrian.xml") << (qreal)1107.0 << 798; + QTest::newRow("Public Transport") << (int)QGeoRouteRequest::PublicTransitTravel << QString("travelmode-public-transport.xml") << (qreal)1388.0 << 641; +} + +void tst_nokia_routing::can_compute_route_for_all_supported_optimizations() +{ + QFETCH(int, optimization); + QFETCH(QString, file); + QFETCH(qreal, distance); + QFETCH(int, duration); + m_dummyRequest.setRouteOptimization((QGeoRouteRequest::RouteOptimization)optimization); + loadReply(file); + calculateRoute(); + QList routes = m_reply->routes(); + QCOMPARE(1, routes.size()); + QGeoRoute& route = routes[0]; + CHECK_CLOSE(distance, route.distance()); + QCOMPARE(duration, route.travelTime()); + verifySaneRoute(route); +} + +void tst_nokia_routing::can_compute_route_for_all_supported_optimizations_data() +{ + QTest::addColumn("optimization"); + QTest::addColumn("file"); + QTest::addColumn("distance"); + QTest::addColumn("duration"); + + QTest::newRow("Shortest") << (int)QGeoRouteRequest::ShortestRoute << QString("optim-shortest.xml") << qreal(1177.0) << 309; + QTest::newRow("Fastest") << (int)QGeoRouteRequest::FastestRoute << QString("optim-fastest.xml") << qreal(1271.0) << 243; +} + +void tst_nokia_routing::can_handle_multiple_routes_in_response() +{ + loadReply(QStringLiteral("multiple-routes-in-response.xml")); + calculateRoute(); + QList routes = m_reply->routes(); + QCOMPARE(2, routes.size()); + + verifySaneRoute(routes[0]); + verifySaneRoute(routes[1]); +} + +void tst_nokia_routing::can_handle_no_route_exists_case() +{ + loadReply(QStringLiteral("error-no-route.xml")); + calculateRoute(); + QCOMPARE(QGeoRouteReply::NoError, m_reply->error()); + QList routes = m_reply->routes(); + QCOMPARE(0, routes.size()); +} + +void tst_nokia_routing::can_handle_additions_to_routing_xml() +{ + loadReply(QStringLiteral("littered-with-new-tags.xml")); + calculateRoute(); + QCOMPARE(QGeoRouteReply::NoError, m_reply->error()); + QList routes = m_reply->routes(); + QVERIFY(routes.size() > 0); +} + +void tst_nokia_routing::can_handle_invalid_server_responses() +{ + QFETCH(QString, file); + + m_expectError = true; + + loadReply(file); + calculateRoute(); + QCOMPARE(QGeoRouteReply::ParseError, m_reply->error()); +} + +void tst_nokia_routing::can_handle_invalid_server_responses_data() +{ + QTest::addColumn("file"); + + QTest::newRow("Trash") << QString("invalid-response-trash.xml"); + QTest::newRow("Half way through") << QString("invalid-response-half-way-through.xml"); + QTest::newRow("No tag") << QString("invalid-response-no-calculateroute-tag.xml"); +} + +void tst_nokia_routing::foobar() +{ + QFETCH(int, code); + + m_expectError = true; + m_replyUnowned = new MockGeoNetworkReply(); + m_replyUnowned->setError(static_cast(code), QStringLiteral("Test error")); + m_networkManager->setReply(m_replyUnowned); + calculateRoute(); + QCOMPARE(QGeoRouteReply::CommunicationError, m_reply->error()); +} + +void tst_nokia_routing::foobar_data() +{ + QTest::addColumn("code"); + + QTest::newRow("QNetworkReply::ConnectionRefusedError") << int(QNetworkReply::ConnectionRefusedError); + QTest::newRow("QNetworkReply::RemoteHostClosedError") << int(QNetworkReply::RemoteHostClosedError); + QTest::newRow("QNetworkReply::HostNotFoundError") << int(QNetworkReply::HostNotFoundError); + QTest::newRow("QNetworkReply::TimeoutError") << int(QNetworkReply::TimeoutError); + QTest::newRow("QNetworkReply::OperationCanceledError") << int(QNetworkReply::OperationCanceledError); + QTest::newRow("QNetworkReply::SslHandshakeFailedError") << int(QNetworkReply::SslHandshakeFailedError); + QTest::newRow("QNetworkReply::TemporaryNetworkFailureError") << int(QNetworkReply::TemporaryNetworkFailureError); + QTest::newRow("QNetworkReply::ProxyConnectionRefusedError") << int(QNetworkReply::ProxyConnectionRefusedError); + QTest::newRow("QNetworkReply::ProxyConnectionClosedError") << int(QNetworkReply::ProxyConnectionClosedError); + QTest::newRow("QNetworkReply::ProxyNotFoundError") << int(QNetworkReply::ProxyNotFoundError); + QTest::newRow("QNetworkReply::ProxyTimeoutError") << int(QNetworkReply::ProxyTimeoutError); + QTest::newRow("QNetworkReply::ProxyAuthenticationRequiredError") << int(QNetworkReply::ProxyAuthenticationRequiredError); + QTest::newRow("QNetworkReply::ContentAccessDenied") << int(QNetworkReply::ContentAccessDenied); + QTest::newRow("QNetworkReply::ContentOperationNotPermittedError") << int(QNetworkReply::ContentOperationNotPermittedError); + QTest::newRow("QNetworkReply::ContentNotFoundError") << int(QNetworkReply::ContentNotFoundError); + QTest::newRow("QNetworkReply::ContentReSendError") << int(QNetworkReply::ContentReSendError); + QTest::newRow("QNetworkReply::ProtocolUnknownError") << int(QNetworkReply::ProtocolUnknownError); + QTest::newRow("QNetworkReply::ProtocolInvalidOperationError") << int(QNetworkReply::ProtocolInvalidOperationError); + QTest::newRow("QNetworkReply::UnknownNetworkError") << int(QNetworkReply::UnknownNetworkError); + QTest::newRow("QNetworkReply::UnknownProxyError") << int(QNetworkReply::UnknownProxyError); + QTest::newRow("QNetworkReply::ProxyAuthenticationRequiredError") << int(QNetworkReply::ProxyAuthenticationRequiredError); + QTest::newRow("QNetworkReply::ProtocolFailure") << int(QNetworkReply::ProtocolFailure); +} + + +QTEST_MAIN(tst_nokia_routing) + +#include "tst_routing.moc" diff --git a/tests/auto/placemanager_utils/placemanager_utils.cpp b/tests/auto/placemanager_utils/placemanager_utils.cpp new file mode 100644 index 0000000..d5ba61f --- /dev/null +++ b/tests/auto/placemanager_utils/placemanager_utils.cpp @@ -0,0 +1,376 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "placemanager_utils.h" + +#include +#include +#include +#include +#include +#include +#include + +//constant for timeout to verify signals +const int PlaceManagerUtils::Timeout(10000); + +PlaceManagerUtils::PlaceManagerUtils(QObject *parent) + : QObject(parent), placeManager(0) +{ +} + +bool PlaceManagerUtils::doSavePlace(QPlaceManager *manager, + const QPlace &place, + QPlaceReply::Error expectedError, + QString *placeId) +{ + Q_ASSERT(manager); + QPlaceIdReply *saveReply = manager->savePlace(place); + bool isSuccessful = checkSignals(saveReply, expectedError, manager); + if (placeId != 0) { + *placeId = saveReply->id(); + } + + if (saveReply->id().isEmpty() && expectedError == QPlaceReply::NoError) { + qWarning("ID is empty in reply for save operation"); + qWarning() << "Error string = " << saveReply->errorString(); + isSuccessful = false; + } + + if (!isSuccessful) + qWarning() << "Error string = " << saveReply->errorString(); + + return isSuccessful; +} + +void PlaceManagerUtils::doSavePlaces(QPlaceManager *manager, QList &places) +{ + QPlaceIdReply *saveReply; + + foreach (QPlace place, places) { + saveReply = manager->savePlace(place); + QSignalSpy saveSpy(saveReply, SIGNAL(finished())); + QTRY_VERIFY_WITH_TIMEOUT(saveSpy.count() == 1, Timeout); + QCOMPARE(saveReply->error(), QPlaceReply::NoError); + saveSpy.clear(); + } +} + +void PlaceManagerUtils::doSavePlaces(QPlaceManager *manager, const QList &places) +{ + QPlaceIdReply *saveReply; + + static int count= 0; + foreach (QPlace *place, places) { + count++; + saveReply = manager->savePlace(*place); + QSignalSpy saveSpy(saveReply, SIGNAL(finished())); + QTRY_VERIFY_WITH_TIMEOUT(saveSpy.count() == 1, Timeout); + QCOMPARE(saveReply->error(), QPlaceReply::NoError); + place->setPlaceId(saveReply->id()); + saveSpy.clear(); + } +} + +bool PlaceManagerUtils::doSearch(QPlaceManager *manager, + const QPlaceSearchRequest &request, + QList *results, + QPlaceReply::Error expectedError) +{ + QPlaceSearchReply *searchReply= manager->search(request); + bool success = checkSignals(searchReply, expectedError, manager); + *results = searchReply->results(); + return success; +} + +bool PlaceManagerUtils::doSearch(QPlaceManager *manager, + const QPlaceSearchRequest &request, + QList *results, QPlaceReply::Error expectedError) +{ + bool success = false; + results->clear(); + QList searchResults; + success = doSearch(manager, request, &searchResults, expectedError); + foreach (const QPlaceSearchResult &searchResult, searchResults) { + if (searchResult.type() == QPlaceSearchResult::PlaceResult) { + QPlaceResult placeResult = searchResult; + results->append(placeResult.place()); + } + } + return success; +} + +bool PlaceManagerUtils::doSearchSuggestions(QPlaceManager *manager, + const QPlaceSearchRequest &request, + QStringList *results, + QPlaceReply::Error expectedError) +{ + QPlaceSearchSuggestionReply *reply = manager->searchSuggestions(request); + bool success = checkSignals(reply, expectedError, manager); + *results = reply->suggestions(); + + if (!success) + qDebug() << "Error string = " << reply->errorString(); + + return success; +} + +bool PlaceManagerUtils::doRemovePlace(QPlaceManager *manager, + const QPlace &place, + QPlaceReply::Error expectedError) +{ + QPlaceIdReply *removeReply = manager->removePlace(place.placeId()); + bool isSuccessful = false; + isSuccessful = checkSignals(removeReply, expectedError, manager) + && (removeReply->id() == place.placeId()); + + if (!isSuccessful) + qWarning() << "Place removal unsuccessful errorString = " << removeReply->errorString(); + + return isSuccessful; +} + +bool PlaceManagerUtils::doFetchDetails(QPlaceManager *manager, + QString placeId, QPlace *place, + QPlaceReply::Error expectedError) +{ + QPlaceDetailsReply *detailsReply = manager->getPlaceDetails(placeId); + bool success = checkSignals(detailsReply, expectedError, manager); + *place = detailsReply->place(); + + if (!success) + qDebug() << "Error string = " << detailsReply->errorString(); + + return success; +} + +bool PlaceManagerUtils::doInitializeCategories(QPlaceManager *manager, + QPlaceReply::Error expectedError) +{ + QPlaceReply *reply = manager->initializeCategories(); + bool success = checkSignals(reply, expectedError, manager); + + if (!success) + qDebug() << "Error string = " << reply->errorString(); + + delete reply; + return success; +} + +bool PlaceManagerUtils::doSaveCategory(QPlaceManager *manager, + const QPlaceCategory &category, + const QString &parentId, + QPlaceReply::Error expectedError, + QString *categoryId) +{ + QPlaceIdReply *idReply = manager->saveCategory(category, parentId); + bool isSuccessful = checkSignals(idReply, expectedError, manager) + && (idReply->error() == expectedError); + + if (categoryId != 0) + *categoryId = idReply->id(); + + if (!isSuccessful) + qDebug() << "Error string =" << idReply->errorString(); + return isSuccessful; +} + +bool PlaceManagerUtils::doRemoveCategory(QPlaceManager *manager, + const QPlaceCategory &category, + QPlaceReply::Error expectedError) +{ + QPlaceIdReply *idReply = manager->removeCategory(category.categoryId()); + + bool isSuccessful = checkSignals(idReply, expectedError, manager) && + (idReply->error() == expectedError); + return isSuccessful; +} + +bool PlaceManagerUtils::doFetchCategory(QPlaceManager *manager, + const QString &categoryId, + QPlaceCategory *category, + QPlaceReply::Error expectedError) +{ + Q_ASSERT(category); + QPlaceReply * catInitReply = manager->initializeCategories(); + bool isSuccessful = checkSignals(catInitReply, expectedError, manager); + *category = manager->category(categoryId); + + if (!isSuccessful) + qDebug() << "Error initializing categories, error string = " + << catInitReply->errorString(); + + if (category->categoryId() != categoryId) + isSuccessful = false; + return isSuccessful; +} + +bool PlaceManagerUtils::doFetchContent(QPlaceManager *manager, + const QPlaceContentRequest &request, + QPlaceContent::Collection *results, + QPlaceReply::Error expectedError) +{ + Q_ASSERT(results); + QPlaceContentReply *reply = manager->getPlaceContent(request); + bool isSuccessful = checkSignals(reply, expectedError, manager); + *results = reply->content(); + + if (!isSuccessful) + qDebug() << "Error during content fetch, error string = " + << reply->errorString(); + + return isSuccessful; +} + +bool PlaceManagerUtils::doMatch(QPlaceManager *manager, + const QPlaceMatchRequest &request, + QList *places, + QPlaceReply::Error expectedError) +{ + QPlaceMatchReply *reply = manager->matchingPlaces(request); + bool isSuccessful = checkSignals(reply, expectedError, manager) && + (reply->error() == expectedError); + *places = reply->places(); + if (!isSuccessful) + qDebug() << "Error for matching operation, error string = " + << reply->errorString(); + return isSuccessful; +} + +bool PlaceManagerUtils::checkSignals(QPlaceReply *reply, QPlaceReply::Error expectedError, + QPlaceManager *manager) +{ + Q_ASSERT(reply); + QSignalSpy finishedSpy(reply, SIGNAL(finished())); + QSignalSpy errorSpy(reply, SIGNAL(error(QPlaceReply::Error,QString))); + QSignalSpy managerFinishedSpy(manager, SIGNAL(finished(QPlaceReply*))); + QSignalSpy managerErrorSpy(manager,SIGNAL(error(QPlaceReply*,QPlaceReply::Error,QString))); + + if (expectedError != QPlaceReply::NoError) { + //check that we get an error signal from the reply + WAIT_UNTIL(errorSpy.count() == 1); + if (errorSpy.count() != 1) { + qWarning() << "Error signal for search operation not received"; + return false; + } + + //check that we get the correct error from the reply's signal + QPlaceReply::Error actualError = qvariant_cast(errorSpy.at(0).at(0)); + if (actualError != expectedError) { + qWarning() << "Actual error code in reply signal does not match expected error code"; + qWarning() << "Actual error code = " << actualError; + qWarning() << "Expected error coe =" << expectedError; + return false; + } + + //check that we get an error signal from the manager + WAIT_UNTIL(managerErrorSpy.count() == 1); + if (managerErrorSpy.count() !=1) { + qWarning() << "Error signal from manager for search operation not received"; + return false; + } + + //check that we get the correct reply instance in the error signal from the manager + if (qvariant_cast(managerErrorSpy.at(0).at(0)) != reply) { + qWarning() << "Reply instance in error signal from manager is incorrect"; + return false; + } + + //check that we get the correct error from the signal of the manager + actualError = qvariant_cast(managerErrorSpy.at(0).at(1)); + if (actualError != expectedError) { + qWarning() << "Actual error code from manager signal does not match expected error code"; + qWarning() << "Actual error code =" << actualError; + qWarning() << "Expected error code = " << expectedError; + return false; + } + } + + //check that we get a finished signal + WAIT_UNTIL(finishedSpy.count() == 1); + if (finishedSpy.count() !=1) { + qWarning() << "Finished signal from reply not received"; + return false; + } + + if (reply->error() != expectedError) { + qWarning() << "Actual error code does not match expected error code"; + qWarning() << "Actual error code: " << reply->error(); + qWarning() << "Expected error code" << expectedError; + return false; + } + + if (expectedError == QPlaceReply::NoError && !reply->errorString().isEmpty()) { + qWarning() << "Expected error was no error but error string was not empty"; + qWarning() << "Error string=" << reply->errorString(); + return false; + } + + //check that we get the finished signal from the manager + WAIT_UNTIL(managerFinishedSpy.count() == 1); + if (managerFinishedSpy.count() != 1) { + qWarning() << "Finished signal from manager not received"; + return false; + } + + //check that the reply instance in the finished signal from the manager is correct + if (qvariant_cast(managerFinishedSpy.at(0).at(0)) != reply) { + qWarning() << "Reply instance in finished signal from manager is incorrect"; + return false; + } + + return true; +} + +bool PlaceManagerUtils::compare(const QList &actualResults, + const QList &expectedResults) +{ + QSet actualIds; + foreach (const QPlace &place, actualResults) + actualIds.insert(place.placeId()); + + QSet expectedIds; + foreach (const QPlace &place, expectedResults) + expectedIds.insert(place.placeId()); + + bool isMatch = (actualIds == expectedIds); + if (actualResults.count() != expectedResults.count() || !isMatch) { + qWarning() << "comparison of results by name does not match"; + qWarning() << "actual result ids: " << actualIds; + qWarning() << "expected result ids : " << expectedIds; + return false; + } + + return isMatch; +} + +void PlaceManagerUtils::setVisibility(QList places, QLocation::Visibility visibility) +{ + foreach (QPlace *place, places) + place->setVisibility(visibility); +} diff --git a/tests/auto/placemanager_utils/placemanager_utils.h b/tests/auto/placemanager_utils/placemanager_utils.h new file mode 100644 index 0000000..982bd85 --- /dev/null +++ b/tests/auto/placemanager_utils/placemanager_utils.h @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PLACEMANAGER_UTILS_H +#define PLACEMANAGER_UTILS_H + +#include +#include +#include +#include + +#ifndef WAIT_UNTIL +#define WAIT_UNTIL(__expr) \ + do { \ + const int __step = 50; \ + const int __timeout = 25000; \ + if (!(__expr)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + } while (0) +#endif + +QT_BEGIN_NAMESPACE + +class QPlaceManager; +class QPlace; +class QPlaceSearchResult; +class QPlaceSearchRequest; +class QPlaceCategory; +class QPlaceContentRequest; +class QPlaceMatchRequest; + +QT_END_NAMESPACE + +class PlaceManagerUtils : public QObject +{ + Q_OBJECT +public: + PlaceManagerUtils(QObject *parent = 0); + + static bool doSavePlace(QPlaceManager *manager, + const QPlace &place, + QPlaceReply::Error expectedError = QPlaceReply::NoError, + QString *placeId = 0); + + static void doSavePlaces(QPlaceManager *manager, QList &places); + + //sets the id for saved places + static void doSavePlaces(QPlaceManager *manager, const QList &places); + + static bool doSearch(QPlaceManager *manager, const QPlaceSearchRequest &request, + QList *results, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool doSearch(QPlaceManager *manager, const QPlaceSearchRequest &request, + QList *results, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool doSearchSuggestions(QPlaceManager *manager, + const QPlaceSearchRequest &request, + QStringList *results, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool doRemovePlace(QPlaceManager *manager, const QPlace &place, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool doFetchDetails(QPlaceManager *manager, + QString placeId, + QPlace *place, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool doInitializeCategories(QPlaceManager *manager, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool doSaveCategory(QPlaceManager *manager, + const QPlaceCategory &category, + const QString &parentId, + QPlaceReply::Error expectedError = QPlaceReply::NoError, + QString *categoryId = 0); + + static bool doRemoveCategory(QPlaceManager *manager, const QPlaceCategory &category, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool doFetchCategory(QPlaceManager *manager, + const QString &categoryId, + QPlaceCategory *category, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool doFetchContent(QPlaceManager *manager, + const QPlaceContentRequest &request, + QPlaceContent::Collection *results, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool doMatch(QPlaceManager *manager, + const QPlaceMatchRequest &request, + QList *places, + QPlaceReply::Error expectedError = QPlaceReply::NoError); + + static bool checkSignals(QPlaceReply *reply, QPlaceReply::Error expectedError, + QPlaceManager *manager); + + static bool compare(const QList &actualResults, + const QList &expectedResults); + + static void setVisibility(QListplaces, QLocation::Visibility visibility); + + static const int Timeout; + +protected: + bool doSavePlace(const QPlace &place, + QPlaceReply::Error expectedError = QPlaceReply::NoError, + QString *placeId = 0) { + return doSavePlace(placeManager, place, expectedError, placeId); + } + + void doSavePlaces(QList &places) { + return doSavePlaces(placeManager, places); + } + + void doSavePlaces(const QList &places) { + return doSavePlaces(placeManager, places); + } + + bool doRemovePlace(const QPlace &place, + QPlaceReply::Error expectedError = QPlaceReply::NoError) + { + return doRemovePlace(placeManager, place, expectedError); + } + + bool doSearch(const QPlaceSearchRequest &request, + QList *results, + QPlaceReply::Error expectedError = QPlaceReply::NoError) { + return doSearch(placeManager, request, results,expectedError); + } + + bool doSearchSuggestions(const QPlaceSearchRequest &request, + QStringList *results, + QPlaceReply::Error expectedError) { + return doSearchSuggestions(placeManager, request, results, expectedError); + } + + bool doFetchDetails(QString placeId, + QPlace *place, + QPlaceReply::Error expectedError = QPlaceReply::NoError) { + return doFetchDetails(placeManager, placeId, place, expectedError); + } + + bool doInitializeCategories(QPlaceReply::Error expectedError = QPlaceReply::NoError) { + return doInitializeCategories(placeManager, expectedError); + } + + bool doSaveCategory(const QPlaceCategory &category, + QPlaceReply::Error expectedError = QPlaceReply::NoError, + QString *categoryId = 0) { + return doSaveCategory(placeManager, category, QString(), + expectedError,categoryId); + } + + bool doSaveCategory(const QPlaceCategory &category, + const QString &parentId, + QPlaceReply::Error expectedError = QPlaceReply::NoError, + QString *categoryId = 0) { + return doSaveCategory(placeManager, category, parentId, + expectedError, categoryId); + } + + bool doRemoveCategory(const QPlaceCategory &category, + QPlaceReply::Error expectedError = QPlaceReply::NoError) + { + return doRemoveCategory(placeManager, category, expectedError); + } + + bool doFetchCategory(const QString &categoryId, + QPlaceCategory *category, + QPlaceReply::Error expectedError = QPlaceReply::NoError) { + return doFetchCategory(placeManager, categoryId, + category, expectedError); + } + + bool doFetchContent(const QPlaceContentRequest &request, + QPlaceContent::Collection *results, + QPlaceReply::Error expectedError = QPlaceReply::NoError) + { + return doFetchContent(placeManager, request, results, expectedError); + } + + bool doMatch(const QPlaceMatchRequest &request, + QList *places, + QPlaceReply::Error expectedError = QPlaceReply::NoError) { + return doMatch(placeManager, request, + places, expectedError); + } + + bool checkSignals(QPlaceReply *reply, QPlaceReply::Error expectedError) { + return checkSignals(reply, expectedError, placeManager); + } + + QPlaceManager *placeManager; +}; + +#endif + diff --git a/tests/auto/placesplugin_unsupported/placesplugin.json b/tests/auto/placesplugin_unsupported/placesplugin.json new file mode 100644 index 0000000..6749962 --- /dev/null +++ b/tests/auto/placesplugin_unsupported/placesplugin.json @@ -0,0 +1,8 @@ +{ + "Keys": ["test.places.unsupported"], + "Provider": "test.places.unsupported", + "Version": 1, + "Experimental": true, + "Features": [ + ] +} diff --git a/tests/auto/placesplugin_unsupported/placesplugin_unsupported.pro b/tests/auto/placesplugin_unsupported/placesplugin_unsupported.pro new file mode 100644 index 0000000..65c6a39 --- /dev/null +++ b/tests/auto/placesplugin_unsupported/placesplugin_unsupported.pro @@ -0,0 +1,14 @@ +TARGET = qtgeoservices_placesplugin_unsupported +QT += location + +PLUGIN_TYPE = geoservices +PLUGIN_CLASS_NAME = UnsupportedPlacesGeoServicePlugin +PLUGIN_EXTENDS = - +load(qt_plugin) + +HEADERS += qgeoserviceproviderplugin_test.h + +SOURCES += qgeoserviceproviderplugin_test.cpp + +OTHER_FILES += \ + placesplugin.json diff --git a/tests/auto/placesplugin_unsupported/qgeoserviceproviderplugin_test.cpp b/tests/auto/placesplugin_unsupported/qgeoserviceproviderplugin_test.cpp new file mode 100644 index 0000000..f94e52c --- /dev/null +++ b/tests/auto/placesplugin_unsupported/qgeoserviceproviderplugin_test.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoserviceproviderplugin_test.h" + +#include +#include + +QGeoServiceProviderFactoryTest::QGeoServiceProviderFactoryTest() +{ +} + +QGeoServiceProviderFactoryTest::~QGeoServiceProviderFactoryTest() +{ +} + +QPlaceManagerEngine *QGeoServiceProviderFactoryTest::createPlaceManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) const +{ + Q_UNUSED(error); + Q_UNUSED(errorString); + + return new QPlaceManagerEngine(parameters); +} diff --git a/tests/auto/placesplugin_unsupported/qgeoserviceproviderplugin_test.h b/tests/auto/placesplugin_unsupported/qgeoserviceproviderplugin_test.h new file mode 100644 index 0000000..11c30d2 --- /dev/null +++ b/tests/auto/placesplugin_unsupported/qgeoserviceproviderplugin_test.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDER_TEST_H +#define QGEOSERVICEPROVIDER_TEST_H + +#include + +QT_USE_NAMESPACE + +class QGeoServiceProviderFactoryTest : public QObject, public QGeoServiceProviderFactory +{ + Q_OBJECT + Q_INTERFACES(QGeoServiceProviderFactory) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0" + FILE "placesplugin.json") + +public: + QGeoServiceProviderFactoryTest(); + ~QGeoServiceProviderFactoryTest(); + + QPlaceManagerEngine *createPlaceManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) const; +}; + +#endif + + diff --git a/tests/auto/positionplugin/plugin.cpp b/tests/auto/positionplugin/plugin.cpp new file mode 100644 index 0000000..919549d --- /dev/null +++ b/tests/auto/positionplugin/plugin.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class DummySource : public QGeoPositionInfoSource +{ + Q_OBJECT + +public: + DummySource(QObject *parent=0); + ~DummySource(); + + void startUpdates(); + void stopUpdates(); + void requestUpdate(int timeout=5000); + + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const; + PositioningMethods supportedPositioningMethods() const; + + void setUpdateInterval(int msec); + int minimumUpdateInterval() const; + Error error() const; + +private: + QTimer *timer; + QTimer *timeoutTimer; + QTimer *singleTimer; + QGeoPositionInfo lastPosition; + QDateTime lastUpdateTime; + +private slots: + void updatePosition(); + void doTimeout(); +}; + +DummySource::DummySource(QObject *parent) : + QGeoPositionInfoSource(parent), + timer(new QTimer(this)), + timeoutTimer(new QTimer(this)), + singleTimer(new QTimer(this)), + lastPosition(QGeoCoordinate(0,0), QDateTime::currentDateTime()) +{ + timer->setInterval(1000); + connect(timer, SIGNAL(timeout()), + this, SLOT(updatePosition())); + connect(singleTimer, SIGNAL(timeout()), + this, SLOT(updatePosition())); + connect(timeoutTimer, SIGNAL(timeout()), + this, SLOT(doTimeout())); +} + +QGeoPositionInfoSource::Error DummySource::error() const +{ + return QGeoPositionInfoSource::NoError; +} + + +void DummySource::setUpdateInterval(int msec) +{ + if (msec == 0) { + timer->setInterval(1000); + } else if (msec < 1000) { + msec = 1000; + timer->setInterval(msec); + } else { + timer->setInterval(msec); + } + + QGeoPositionInfoSource::setUpdateInterval(msec); +} + +int DummySource::minimumUpdateInterval() const +{ + return 1000; +} + +QGeoPositionInfo DummySource::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const +{ + Q_UNUSED(fromSatellitePositioningMethodsOnly); + return lastPosition; +} + +QGeoPositionInfoSource::PositioningMethods DummySource::supportedPositioningMethods() const +{ + return QGeoPositionInfoSource::AllPositioningMethods; +} + +void DummySource::startUpdates() +{ + timer->start(); +} + +void DummySource::stopUpdates() +{ + timer->stop(); +} + +void DummySource::requestUpdate(int timeout) +{ + if (timeout == 0) + timeout = 5000; + if (timeout < 0) + timeout = 0; + + timeoutTimer->setInterval(timeout); + timeoutTimer->start(); + + if (timer->isActive()) { + timer->stop(); + timer->start(); + } + + singleTimer->setInterval(1000); + singleTimer->start(); +} + +DummySource::~DummySource() +{} + +void DummySource::updatePosition() +{ + timeoutTimer->stop(); + singleTimer->stop(); + + const QDateTime now = QDateTime::currentDateTime(); + + QGeoCoordinate coord(lastPosition.coordinate().latitude() + 0.1, + lastPosition.coordinate().longitude() + 0.1); + + QGeoPositionInfo info(coord, now); + info.setAttribute(QGeoPositionInfo::Direction, lastPosition.coordinate().azimuthTo(coord)); + if (lastUpdateTime.isValid()) { + double speed = lastPosition.coordinate().distanceTo(coord) / lastUpdateTime.msecsTo(now); + info.setAttribute(QGeoPositionInfo::GroundSpeed, 1000 * speed); + } + + lastUpdateTime = now; + lastPosition = info; + emit positionUpdated(info); +} + +void DummySource::doTimeout() +{ + timeoutTimer->stop(); + singleTimer->stop(); + emit updateTimeout(); +} + + +class QGeoPositionInfoSourceFactoryTest : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + Q_INTERFACES(QGeoPositionInfoSourceFactory) + +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent); + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent); + QGeoAreaMonitorSource *areaMonitor(QObject *parent); +}; + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactoryTest::positionInfoSource(QObject *parent) +{ + return new DummySource(parent); +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactoryTest::satelliteInfoSource(QObject *parent) +{ + Q_UNUSED(parent) + // not implemented + return 0; +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactoryTest::areaMonitor(QObject* parent) +{ + Q_UNUSED(parent) + return 0; +} + +#include "plugin.moc" diff --git a/tests/auto/positionplugin/plugin.json b/tests/auto/positionplugin/plugin.json new file mode 100644 index 0000000..68acade --- /dev/null +++ b/tests/auto/positionplugin/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["test.source"], + "Provider": "test.source", + "Position": true, + "Satellite": false, + "Monitor": false, + "Priority": 0, + "Testable": true +} diff --git a/tests/auto/positionplugin/positionplugin.pro b/tests/auto/positionplugin/positionplugin.pro new file mode 100644 index 0000000..dd04e7f --- /dev/null +++ b/tests/auto/positionplugin/positionplugin.pro @@ -0,0 +1,12 @@ +TARGET = qtposition_testplugin +QT += positioning + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = TestPositionPlugin +PLUGIN_EXTENDS = - +load(qt_plugin) + +SOURCES += plugin.cpp + +OTHER_FILES += \ + plugin.json diff --git a/tests/auto/positionplugintest/positionplugintest.pro b/tests/auto/positionplugintest/positionplugintest.pro new file mode 100644 index 0000000..4602be7 --- /dev/null +++ b/tests/auto/positionplugintest/positionplugintest.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_positionplugin + +SOURCES += tst_positionplugin.cpp + +CONFIG -= app_bundle + +QT += positioning testlib diff --git a/tests/auto/positionplugintest/tst_positionplugin.cpp b/tests/auto/positionplugintest/tst_positionplugin.cpp new file mode 100644 index 0000000..34d0509 --- /dev/null +++ b/tests/auto/positionplugintest/tst_positionplugin.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QGeoPositionInfo) + +class tst_PositionPlugin : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + void availableSources(); + void create(); + void getUpdates(); +}; + +void tst_PositionPlugin::initTestCase() +{ + /* + * Set custom path since CI doesn't install test plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif + qRegisterMetaType(); +} + +void tst_PositionPlugin::availableSources() +{ + QVERIFY(QGeoPositionInfoSource::availableSources().contains("test.source")); + QVERIFY(!QGeoSatelliteInfoSource::availableSources().contains("test.source")); + QVERIFY(!QGeoAreaMonitorSource::availableSources().contains("test.source")); +} + +void tst_PositionPlugin::create() +{ + QGeoPositionInfoSource *src = 0; + src = QGeoPositionInfoSource::createSource("test.source", 0); + QVERIFY(src != 0); + + QVERIFY(src->minimumUpdateInterval() == 1000); + + src = QGeoPositionInfoSource::createSource("invalid source that will never exist", 0); + QVERIFY(src == 0); + + QGeoSatelliteInfoSource *ssrc = 0; + ssrc = QGeoSatelliteInfoSource::createSource("test.source", 0); + QVERIFY(ssrc == 0); +} + +void tst_PositionPlugin::getUpdates() +{ + QGeoPositionInfoSource *src = QGeoPositionInfoSource::createSource("test.source", 0); + src->setUpdateInterval(1000); + + QSignalSpy spy(src, SIGNAL(positionUpdated(QGeoPositionInfo))); + src->startUpdates(); + QTest::qWait(1500); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy[0].size(), 1); + + QGeoPositionInfo info = qvariant_cast(spy[0][0]); + QCOMPARE(info.coordinate().latitude(), 0.1); + QCOMPARE(info.coordinate().longitude(), 0.1); +} + + + +QTEST_GUILESS_MAIN(tst_PositionPlugin) +#include "tst_positionplugin.moc" diff --git a/tests/auto/qgeoaddress/qgeoaddress.pro b/tests/auto/qgeoaddress/qgeoaddress.pro new file mode 100644 index 0000000..e12b9e1 --- /dev/null +++ b/tests/auto/qgeoaddress/qgeoaddress.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qgeoaddress + +SOURCES += tst_qgeoaddress.cpp + +QT += positioning testlib diff --git a/tests/auto/qgeoaddress/tst_qgeoaddress.cpp b/tests/auto/qgeoaddress/tst_qgeoaddress.cpp new file mode 100644 index 0000000..7b012f1 --- /dev/null +++ b/tests/auto/qgeoaddress/tst_qgeoaddress.cpp @@ -0,0 +1,559 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class tst_QGeoAddress : public QObject +{ + Q_OBJECT + +public: + tst_QGeoAddress(); + +private Q_SLOTS: + void constructorTest(); + void textTest(); +//TODO: there are various field we don't have yet in QGeoAddress +// will need to either remove or enable these tests +// void additionalDataTest(); +// void alternativeAttributesTest(); + void cityTest(); + void countryCodeTest(); + void countryTest(); + void countyTest(); + void districtTest(); +// void floorTest(); +// void houseNumberTest(); +// void labelTest(); + void postalCodeTest(); + void stateTest(); + void streetTest(); +// void suiteTest(); + void generatedText(); + void generatedText_data(); + void operatorsTest(); + void emptyClearTest(); +}; + +tst_QGeoAddress::tst_QGeoAddress() +{ +} + +void tst_QGeoAddress::constructorTest() +{ + QGeoAddress testObj; + + testObj.setStreet("testId"); + QGeoAddress *testObjPtr = new QGeoAddress(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + delete testObjPtr; +} + +void tst_QGeoAddress::textTest() +{ + QGeoAddress address; + QVERIFY(address.text().isEmpty()); + address.setText(QStringLiteral("123 Fake Street\nSpringfield")); + QCOMPARE(address.text(), QStringLiteral("123 Fake Street\nSpringfield")); +} + +void tst_QGeoAddress::cityTest() +{ + QGeoAddress testObj; + QVERIFY2(testObj.city() == QString(), "Wrong default value"); + testObj.setCity("testText"); + QVERIFY2(testObj.city() == "testText", "Wrong value returned"); +} + +void tst_QGeoAddress::countryCodeTest() +{ + QGeoAddress testObj; + QVERIFY2(testObj.countryCode() == QString(), "Wrong default value"); + testObj.setCountryCode("testText"); + QVERIFY2(testObj.countryCode() == "testText", "Wrong value returned"); +} + +void tst_QGeoAddress::countryTest() +{ + QGeoAddress testObj; + QVERIFY2(testObj.country() == QString(), "Wrong default value"); + testObj.setCountry("testText"); + QVERIFY2(testObj.country() == "testText", "Wrong value returned"); +} + +void tst_QGeoAddress::countyTest() +{ + QGeoAddress testObj; + QVERIFY2(testObj.county() == QString(), "Wrong default value"); + testObj.setCounty("testText"); + QVERIFY2(testObj.county() == "testText", "Wrong value returned"); +} + +void tst_QGeoAddress::districtTest() +{ + QGeoAddress testObj; + QVERIFY2(testObj.district() == QString(), "Wrong default value"); + testObj.setDistrict("testText"); + QVERIFY2(testObj.district() == "testText", "Wrong value returned"); +} + +// TODO: currently don't have floor in QGeoAddress +//void tst_QGeoAddress::floorTest() +//{ +// QGeoAddress testObj; +// QVERIFY2(testObj.floor() == QString(), "Wrong default value"); +// testObj.setFloor("testText"); +// QVERIFY2(testObj.floor() == "testText", "Wrong value returned"); +//} + +//TODO: Atm not sure if we will have house number in API. +//void tst_QGeoAddress::houseNumberTest() +//{ +// QGeoAddress testObj; +// QVERIFY2(testObj.houseNumber() == QString(), "Wrong default value"); +// testObj.setHouseNumber("testText"); +// QVERIFY2(testObj.houseNumber() == "testText", "Wrong value returned"); +//} + +//void tst_QGeoAddress::labelTest() +//{ +// QGeoAddress testObj; +// QVERIFY2(testObj.label() == QString(), "Wrong default value"); +// testObj.setLabel("testText"); +// QVERIFY2(testObj.label() == "testText", "Wrong value returned"); +//} + +void tst_QGeoAddress::postalCodeTest() +{ + QGeoAddress testObj; + QVERIFY2(testObj.postalCode() == QString(), "Wrong default value"); + testObj.setPostalCode("testText"); + QVERIFY2(testObj.postalCode() == "testText", "Wrong value returned"); +} + +void tst_QGeoAddress::stateTest() +{ + QGeoAddress testObj; + QVERIFY2(testObj.state() == QString(), "Wrong default value"); + testObj.setState("testText"); + QVERIFY2(testObj.state() == "testText", "Wrong value returned"); +} + +void tst_QGeoAddress::streetTest() +{ + QGeoAddress testObj; + QVERIFY2(testObj.street() == QString(), "Wrong default value"); + testObj.setStreet("testText"); + QVERIFY2(testObj.street() == "testText", "Wrong value returned"); +} + +void tst_QGeoAddress::generatedText() +{ + QFETCH(QString, countryCode); + QFETCH(QString, expectedPostalCodeOnly); + QFETCH(QString, expectedFullAddress); + + QGeoAddress streetOnly; + streetOnly.setStreet("street"); + streetOnly.setCountryCode(countryCode); + + QCOMPARE(streetOnly.text(), QStringLiteral("street")); + + QGeoAddress cityOnly; + cityOnly.setCity("city"); + cityOnly.setCountryCode(countryCode); + if (countryCode == QLatin1String("CYM") || countryCode == QLatin1String("IRL")) + QCOMPARE(cityOnly.text(), QString()); + else + QCOMPARE(cityOnly.text(), QStringLiteral("city")); + + QGeoAddress postalCodeOnly; + postalCodeOnly.setPostalCode("postcode"); + postalCodeOnly.setCountryCode(countryCode); + QCOMPARE(postalCodeOnly.text(), expectedPostalCodeOnly); + + QGeoAddress fullAddress; + fullAddress.setStreet("street"); + fullAddress.setDistrict("district"); + fullAddress.setPostalCode("postcode"); + fullAddress.setCity("city"); + fullAddress.setState("state"); + fullAddress.setCountry("country"); + fullAddress.setCountryCode(countryCode); + + QCOMPARE(fullAddress.text(), expectedFullAddress); +} + +void tst_QGeoAddress::generatedText_data() +{ + QTest::addColumn("countryCode"); + QTest::addColumn("expectedPostalCodeOnly"); + QTest::addColumn("expectedFullAddress"); + + QTest::newRow("Albania") << QString::fromLatin1("ALB") + << QString::fromLatin1("postcode") /* postal code only */ + << QString::fromLatin1("street
    " /* full address */ + "postcode, city
    " + "country"); + + QTest::newRow("Andorra") << QString::fromLatin1("AND") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("United Arab Emirates") << QString::fromLatin1("ARE") + << QString() + << QString::fromLatin1("street
    " + "district city
    " + "country"); + QTest::newRow("Australia") << QString::fromLatin1("AUS") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district state postcode
    " + "country"); + QTest::newRow("Austria") << QString::fromLatin1("AUT") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("Bahamas") << QString::fromLatin1("BHS") + << QString() + << QString::fromLatin1("street
    " + "district city
    " + "country"); + QTest::newRow("Bahrain") << QString::fromLatin1("BHR") + << QString() + << QString::fromLatin1("street
    " + "district, city, state
    " + "country"); + QTest::newRow("Brazil") << QString::fromLatin1("BRA") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district city-state postcode
    " + "country"); + QTest::newRow("Brunei Darussalam") << QString::fromLatin1("BRN") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district city postcode
    " + "country"); + QTest::newRow("Canada") << QString::fromLatin1("CAN") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city, state postcode
    " + "country"); + QTest::newRow("China") << QString::fromLatin1("CHN") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street, city
    " + "postcode state
    " + "country"); + QTest::newRow("Chile") << QString::fromLatin1("CHL") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode district, city, state
    " + "country"); + QTest::newRow("Cayman Islands") << QString::fromLatin1("CYM") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "state postcode
    " + "country"); + QTest::newRow("France") << QString::fromLatin1("FRA") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + + QTest::newRow("United Kingdom") << QString::fromLatin1("GBR") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district, city, postcode
    " + "country"); + QTest::newRow("Gibraltar") << QString::fromLatin1("GIB") + << QString() + << QString::fromLatin1("street
    " + "city
    " + "country"); + QTest::newRow("Guadeloupe") << QString::fromLatin1("GLP") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("French Guiana") << QString::fromLatin1("GUF") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("Hong Kong") << QString::fromLatin1("HKG") + << QString() + << QString::fromLatin1("street
    " + "district
    " + "city"); + QTest::newRow("India") << QString::fromLatin1("IND") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city postcode state
    " + "country"); + QTest::newRow("Indonesia") << QString::fromLatin1("IDN") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city, postcode
    " + "country"); + QTest::newRow("Ireland") << QString::fromLatin1("IRL") + << QString() + << QString::fromLatin1("street
    " + "district, state
    " + "country"); + QTest::newRow("Italy") << QString::fromLatin1("ITA") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("Jersey") << QString::fromLatin1("JEY") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city, postcode
    " + "country"); + QTest::newRow("Jordan") << QString::fromLatin1("JOR") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district city postcode
    " + "country"); + QTest::newRow("Kuwait") << QString::fromLatin1("KWT") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode, district, city
    " + "country"); + QTest::newRow("Latvia") << QString::fromLatin1("LVA") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city, postcode
    " + "country"); + QTest::newRow("Lebanon") << QString::fromLatin1("LBN") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district city postcode
    " + "country"); + QTest::newRow("Luxembourg") << QString::fromLatin1("LUX") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("Malta") << QString::fromLatin1("MLT") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city postcode
    " + "country"); + QTest::newRow("Monaco") << QString::fromLatin1("MCO") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("Mexico") << QString::fromLatin1("MEX") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district
    " + "postcode city, state
    " + "country"); + QTest::newRow("Martinique") << QString::fromLatin1("MTQ") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode, city
    " + "country"); + QTest::newRow("Malaysia") << QString::fromLatin1("MYS") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "state
    " + "country"); + QTest::newRow("New Zealand") << QString::fromLatin1("NZL") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district city postcode
    " + "country"); + QTest::newRow("Oman") << QString::fromLatin1("OMN") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district, postcode, city, country"); + QTest::newRow("Puerto Rico") << QString::fromLatin1("PRI") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "district, city, state, postcode
    " + "country"); + QTest::newRow("Qatar") << QString::fromLatin1("QAT") + << QString() + << QString::fromLatin1("street
    " + "district city, country"); + QTest::newRow("Reunion") << QString::fromLatin1("REU") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("Russian Federation") << QString::fromLatin1("RUS") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("Saudi Arabia") << QString::fromLatin1("SAU") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street district
    " + "city postcode
    " + "country"); + QTest::newRow("Singapore") << QString::fromLatin1("SGP") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city postcode
    " + "country"); + QTest::newRow("Marino") << QString::fromLatin1("SMR") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("Taiwan") << QString::fromLatin1("TWN") + << QString() + << QString::fromLatin1("street, district, city
    " + "country"); + QTest::newRow("Thailand") << QString::fromLatin1("THA") + << QString("postcode") + << QString::fromLatin1("street
    " + "district, city postcode
    " + "country"); + QTest::newRow("Turkey") << QString::fromLatin1("TUR") + << QString("postcode") + << QString::fromLatin1("street
    " + "postcode district, city
    " + "country"); + QTest::newRow("Ukraine") << QString::fromLatin1("UKR") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city postcode
    " + "country"); + QTest::newRow("United States") << QString::fromLatin1("USA") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city, state postcode
    " + "country"); + QTest::newRow("Virgin Islands, US") << QString::fromLatin1("VIR") + << QString("postcode") + << QString::fromLatin1("street
    " + "city, state postcode
    " + "country"); + QTest::newRow("Vatican City State") << QString::fromLatin1("VAT") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); + QTest::newRow("Venezuela") << QString::fromLatin1("VEN") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "city postcode, state
    " + "country"); + QTest::newRow("South Africa") << QString::fromLatin1("ZAF") + << QString() + << QString::fromLatin1("street
    " + "district, city
    " + "country"); + QTest::newRow("Finland") << QString::fromLatin1("FIN") + << QString::fromLatin1("postcode") + << QString::fromLatin1("street
    " + "postcode city
    " + "country"); +} + +// TODO: curenlty we don't have suite in QGeoAddress +// will need to either remove or enable +//void tst_QGeoAddress::suiteTest() +//{ +// QGeoAddress testObj; +// QVERIFY2(testObj.suite() == QString(), "Wrong default value"); +// testObj.setSuite("testText"); +// QVERIFY2(testObj.suite() == "testText", "Wrong value returned"); +//} + +void tst_QGeoAddress::operatorsTest() +{ + QGeoAddress testObj; + testObj.setStreet("testValue"); + QGeoAddress testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setCountry("testValue2"); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +void tst_QGeoAddress::emptyClearTest() +{ + QGeoAddress testObj; + QVERIFY(testObj.isEmpty()); + + testObj.setCountry(QStringLiteral("country")); + QVERIFY(!testObj.isEmpty()); + testObj.clear(); + + testObj.setCountryCode(QStringLiteral("countryCode")); + QVERIFY(!testObj.isEmpty()); + testObj.clear(); + + testObj.setState(QStringLiteral("state")); + QVERIFY(!testObj.isEmpty()); + testObj.clear(); + + testObj.setCounty(QStringLiteral("county")); + QVERIFY(!testObj.isEmpty()); + testObj.clear(); + + testObj.setCity(QStringLiteral("city")); + QVERIFY(!testObj.isEmpty()); + testObj.clear(); + + testObj.setDistrict(QStringLiteral("district")); + QVERIFY(!testObj.isEmpty()); + testObj.clear(); + + testObj.setPostalCode(QStringLiteral("postalCode")); + QVERIFY(!testObj.isEmpty()); + testObj.clear(); + + testObj.setStreet(QStringLiteral("street")); + QVERIFY(!testObj.isEmpty()); + testObj.clear(); + + testObj.setText(QStringLiteral("formatted address")); + QVERIFY(!testObj.isEmpty()); + testObj.clear(); + + QVERIFY(testObj.isEmpty()); +} + +QTEST_APPLESS_MAIN(tst_QGeoAddress) + +#include "tst_qgeoaddress.moc" diff --git a/tests/auto/qgeoareamonitor/logfilepositionsource.cpp b/tests/auto/qgeoareamonitor/logfilepositionsource.cpp new file mode 100644 index 0000000..a613d6e --- /dev/null +++ b/tests/auto/qgeoareamonitor/logfilepositionsource.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "logfilepositionsource.h" + +LogFilePositionSource::LogFilePositionSource(QObject *parent) + : QGeoPositionInfoSource(parent), + logFile(new QFile(this)), + timer(new QTimer(this)) +{ + connect(timer, SIGNAL(timeout()), this, SLOT(readNextPosition())); + + logFile->setFileName(QFINDTESTDATA("simplelog.txt")); + if (!logFile->open(QIODevice::ReadOnly)) + qWarning() << "Error: cannot open source file" << logFile->fileName(); +} + +QGeoPositionInfo LogFilePositionSource::lastKnownPosition(bool /*fromSatellitePositioningMethodsOnly*/) const +{ + return lastPosition; +} + +LogFilePositionSource::PositioningMethods LogFilePositionSource::supportedPositioningMethods() const +{ + return AllPositioningMethods; +} + +int LogFilePositionSource::minimumUpdateInterval() const +{ + return 200; +} + +void LogFilePositionSource::startUpdates() +{ + int interval = updateInterval(); + if (interval < minimumUpdateInterval()) + interval = minimumUpdateInterval(); + + timer->start(interval); +} + +void LogFilePositionSource::stopUpdates() +{ + timer->stop(); +} + +void LogFilePositionSource::requestUpdate(int /*timeout*/) +{ + // For simplicity, ignore timeout - assume that if data is not available + // now, no data will be added to the file later + if (logFile->canReadLine()) + readNextPosition(); + else + emit updateTimeout(); +} + +void LogFilePositionSource::readNextPosition() +{ + QByteArray line = logFile->readLine().trimmed(); + if (!line.isEmpty()) { + QList data = line.split(' '); + double latitude; + double longitude; + bool hasLatitude = false; + bool hasLongitude = false; + QDateTime timestamp = QDateTime::fromString(QString(data.value(0)), Qt::ISODate); + latitude = data.value(1).toDouble(&hasLatitude); + longitude = data.value(2).toDouble(&hasLongitude); + + if (hasLatitude && hasLongitude && timestamp.isValid()) { + QGeoCoordinate coordinate(latitude, longitude); + QGeoPositionInfo info(coordinate, timestamp); + if (info.isValid()) { + lastPosition = info; + emit positionUpdated(info); + } + } + } +} + +QGeoPositionInfoSource::Error LogFilePositionSource::error() const +{ + return QGeoPositionInfoSource::NoError; +} diff --git a/tests/auto/qgeoareamonitor/logfilepositionsource.h b/tests/auto/qgeoareamonitor/logfilepositionsource.h new file mode 100644 index 0000000..3768d75 --- /dev/null +++ b/tests/auto/qgeoareamonitor/logfilepositionsource.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef LOGFILEPOSITIONSOURCE_H +#define LOGFILEPOSITIONSOURCE_H + +#include + +QT_BEGIN_NAMESPACE +class QFile; +class QTimer; +QT_END_NAMESPACE + +class LogFilePositionSource : public QGeoPositionInfoSource +{ + Q_OBJECT +public: + LogFilePositionSource(QObject *parent = 0); + + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const; + + PositioningMethods supportedPositioningMethods() const; + int minimumUpdateInterval() const; + Error error() const; + +public slots: + virtual void startUpdates(); + virtual void stopUpdates(); + + virtual void requestUpdate(int timeout = 5000); + +private slots: + void readNextPosition(); + +private: + QFile *logFile; + QTimer *timer; + QGeoPositionInfo lastPosition; +}; + +#endif diff --git a/tests/auto/qgeoareamonitor/qgeoareamonitor.pro b/tests/auto/qgeoareamonitor/qgeoareamonitor.pro new file mode 100644 index 0000000..d084df6 --- /dev/null +++ b/tests/auto/qgeoareamonitor/qgeoareamonitor.pro @@ -0,0 +1,14 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeoareamonitor + +SOURCES += tst_qgeoareamonitor.cpp \ + logfilepositionsource.cpp + +HEADERS += logfilepositionsource.h + +OTHER_FILES += *.txt + +CONFIG -= app_bundle + +QT += positioning testlib diff --git a/tests/auto/qgeoareamonitor/simplelog.txt b/tests/auto/qgeoareamonitor/simplelog.txt new file mode 100644 index 0000000..5a14fb8 --- /dev/null +++ b/tests/auto/qgeoareamonitor/simplelog.txt @@ -0,0 +1,87 @@ +2009-08-24T22:24:34 -27.54 153.090718 +2009-08-24T22:24:35 -27.55 153.090718 +2009-08-24T22:24:36 -27.56 153.090718 +2009-08-24T22:24:37 -27.57 153.090718 +2009-08-24T22:24:38 -27.58 153.090783 +2009-08-24T22:24:39 -27.59 153.090845 +2009-08-24T22:24:40 -27.60 153.090908 +2009-08-24T22:24:41 -27.61 153.090971 +2009-08-24T22:24:42 -27.62 153.091036 +2009-08-24T22:24:43 -27.63 153.091102 +2009-08-24T22:24:44 -27.64 153.091167 +2009-08-24T22:24:45 -27.65 153.091232 +2009-08-24T22:24:46 -27.65 153.091298 +2009-08-24T22:24:47 -27.65 153.091366 +2009-08-24T22:24:48 -27.65 153.091435 +2009-08-24T22:24:49 -27.66 153.091507 +2009-08-24T22:24:50 -27.67 153.091581 +2009-08-24T22:24:51 -27.68 153.091654 +2009-08-24T22:24:52 -27.69 153.091729 +2009-08-24T22:24:53 -27.70 153.091800 +2009-08-24T22:24:54 -27.71 153.091870 +2009-08-24T22:24:55 -27.72 153.091940 +2009-08-24T22:24:56 -27.73 153.092010 +2009-08-24T22:24:57 -27.74 153.092078 +2009-08-24T22:24:58 -27.75 153.092144 +2009-08-24T22:24:59 -27.78 153.092218 +2009-08-24T22:25:00 -27.79 153.092308 +2009-08-24T22:25:01 -27.80 153.092415 +2009-08-24T22:25:02 -27.81 153.092530 +2009-08-24T22:25:03 -27.82 153.092648 +2009-08-24T22:25:04 -27.83 153.092763 +2009-08-24T22:25:05 -27.84 153.092879 +2009-08-24T22:25:06 -27.85 153.092990 +2009-08-24T22:25:07 -27.84 153.093099 +2009-08-24T22:25:08 -27.83 153.093204 +2009-08-24T22:25:09 -27.82 153.093303 +2009-08-24T22:25:10 -27.81 153.093396 +2009-08-24T22:25:11 -27.80 153.093484 +2009-08-24T22:25:12 -27.79 153.093568 +2009-08-24T22:25:13 -27.78 153.093647 +2009-08-24T22:25:14 -27.77 153.093727 +2009-08-24T22:25:15 -27.76 153.093810 +2009-08-24T22:25:16 -27.75 153.093896 +2009-08-24T22:25:17 -27.74 153.093984 +2009-08-24T22:25:18 -27.72 153.094074 +2009-08-24T22:25:19 -27.70 153.094168 +2009-08-24T22:25:20 -27.71 153.094267 +2009-08-24T22:25:21 -27.69 153.094370 +2009-08-24T22:25:22 -27.68 153.094474 +2009-08-24T22:25:23 -27.67 153.094581 +2009-08-24T22:25:24 -27.66 153.094688 +2009-08-24T22:25:25 -27.65 153.094796 +2009-08-24T22:25:26 -27.64 153.094905 +2009-08-24T22:25:27 -27.63 153.095012 +2009-08-24T22:25:28 -27.62 153.095121 +2009-08-24T22:25:29 -27.61 153.095231 +2009-08-24T22:25:30 -27.60 153.095340 +2009-08-24T22:25:31 -27.59 153.095449 +2009-08-24T22:25:32 -27.58 153.095558 +2009-08-24T22:25:33 -27.57 153.095667 +2009-08-24T22:25:34 -27.56 153.095776 +2009-08-24T22:25:35 -27.55 153.095885 +2009-08-24T22:25:36 -27.54 153.095995 +2009-08-24T22:25:37 -27.53 153.096109 +2009-08-24T22:25:38 -27.52 153.096226 +2009-08-24T22:25:39 -27.51 153.096337 +2009-08-24T22:25:40 -27.50 153.096441 +2009-08-24T22:25:41 -27.49 153.096537 +2009-08-24T22:25:42 -27.48 153.096628 +2009-08-24T22:25:43 -27.47 153.096714 +2009-08-24T22:25:44 -27.46 153.096795 +2009-08-24T22:25:45 -27.45 153.096847 +2009-08-24T22:25:46 -27.44 153.096855 +2009-08-24T22:25:47 -27.43 153.096873 +2009-08-24T22:25:48 -27.42 153.096875 +2009-08-24T22:25:49 -27.41 153.096878 +2009-08-24T22:25:50 -27.40 153.096880 +2009-08-24T22:25:51 -27.39 153.096880 +2009-08-24T22:25:52 -27.38 153.096881 +2009-08-24T22:25:53 -27.37 153.096882 +2009-08-24T22:25:54 -27.36 153.096883 +2009-08-24T22:25:55 -27.35 153.096883 +2009-08-24T22:25:56 -27.34 153.096883 +2009-08-24T22:25:57 -27.33 153.096890 +2009-08-24T22:25:58 -27.32 153.096919 +2009-08-24T22:25:59 -27.31 153.096985 +2009-08-24T22:26:00 -27.30 153.097060 diff --git a/tests/auto/qgeoareamonitor/tst_qgeoareamonitor.cpp b/tests/auto/qgeoareamonitor/tst_qgeoareamonitor.cpp new file mode 100644 index 0000000..5526198 --- /dev/null +++ b/tests/auto/qgeoareamonitor/tst_qgeoareamonitor.cpp @@ -0,0 +1,760 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "logfilepositionsource.h" + + +QT_USE_NAMESPACE +#define UPDATE_INTERVAL 200 + +Q_DECLARE_METATYPE(QGeoPositionInfo) +Q_DECLARE_METATYPE(QGeoAreaMonitorInfo) + +QString tst_qgeoareamonitorinfo_debug; + +void tst_qgeoareamonitorinfo_messageHandler(QtMsgType type, + const QMessageLogContext &, + const QString &msg) +{ + switch (type) { + case QtDebugMsg : + tst_qgeoareamonitorinfo_debug = msg; + break; + default: + break; + } +} + +class tst_QGeoAreaMonitorSource : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + /* + * Set custom path since CI doesn't install plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif + qRegisterMetaType(); + qRegisterMetaType(); + } + + void init() + { + } + + void cleanup() + { + QGeoAreaMonitorSource *obj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(obj != 0); + QCOMPARE(obj->sourceName(), QStringLiteral("positionpoll")); + + QList list = obj->activeMonitors(); + if (list.count() > 0) { + //cleanup installed monitors + foreach (const QGeoAreaMonitorInfo& info, list) { + QVERIFY(obj->stopMonitoring(info)); + } + } + QVERIFY(obj->activeMonitors().count() == 0); + } + + void cleanupTestCase() + { + } + + void tst_monitor() + { + QGeoAreaMonitorInfo defaultMonitor; + QVERIFY(defaultMonitor.name().isEmpty()); + QVERIFY(!defaultMonitor.identifier().isEmpty()); + QCOMPARE(defaultMonitor.isPersistent(), false); + QVERIFY(!defaultMonitor.area().isValid()); + QVERIFY(!defaultMonitor.isValid()); + QCOMPARE(defaultMonitor.expiration(), QDateTime()); + QCOMPARE(defaultMonitor.notificationParameters(), QVariantMap()); + + QGeoAreaMonitorSource *obj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(obj != 0); + QCOMPARE(obj->sourceName(), QStringLiteral("positionpoll")); + QVERIFY(!obj->startMonitoring(defaultMonitor)); + QCOMPARE(obj->activeMonitors().count(), 0); + QVERIFY(!obj->requestUpdate(defaultMonitor, + SIGNAL(areaEntered(QGeoMonitorInfo,QGeoAreaPositionInfo)))); + delete obj; + + //copy constructor based + QGeoAreaMonitorInfo copy(defaultMonitor); + QVERIFY(copy.name().isEmpty()); + QCOMPARE(copy.identifier(), defaultMonitor.identifier()); + QVERIFY(copy == defaultMonitor); + QVERIFY(!(copy != defaultMonitor)); + QCOMPARE(copy.isPersistent(), false); + + copy.setName(QString("my name")); + QCOMPARE(copy.name(), QString("my name")); + + + QDateTime now = QDateTime::currentDateTime().addSecs(1000); //little bit in the future + copy.setExpiration(now); + QVERIFY(copy != defaultMonitor); + QCOMPARE(copy.expiration(), now); + + QCOMPARE(copy.isPersistent(), defaultMonitor.isPersistent()); + copy.setPersistent(true); + QCOMPARE(copy.isPersistent(), true); + QCOMPARE(defaultMonitor.isPersistent(), false); + copy.setPersistent(false); + + QVERIFY(copy.area() == defaultMonitor.area()); + QVERIFY(!copy.area().isValid()); + copy.setArea(QGeoCircle(QGeoCoordinate(1, 2), 4)); + QVERIFY(copy.area().isValid()); + QVERIFY(copy.area() != defaultMonitor.area()); + QVERIFY(copy.area().contains(QGeoCoordinate(1, 2))); + + QVERIFY(copy.notificationParameters().isEmpty()); + QVariantMap map; + map.insert(QString("MyKey"), QVariant(123)); + copy.setNotificationParameters(map); + QVERIFY(!copy.notificationParameters().isEmpty()); + QCOMPARE(copy.notificationParameters().value(QString("MyKey")).toInt(), 123); + QCOMPARE(defaultMonitor.notificationParameters().value(QString("MyKey")).toInt(), 0); + + QCOMPARE(defaultMonitor.identifier(), copy.identifier()); + + //assignment operator based + QGeoAreaMonitorInfo assignmentCopy; + assignmentCopy = copy; + QVERIFY(copy == assignmentCopy); + QVERIFY(assignmentCopy != defaultMonitor); + + QVERIFY(assignmentCopy.area().contains(QGeoCoordinate(1, 2))); + QCOMPARE(assignmentCopy.expiration(), now); + QCOMPARE(assignmentCopy.isPersistent(), false); + QCOMPARE(assignmentCopy.notificationParameters().value(QString("MyKey")).toInt(), 123); + QCOMPARE(defaultMonitor.identifier(), assignmentCopy.identifier()); + QCOMPARE(assignmentCopy.name(), QString("my name")); + + //validity checks for requestUpdate() + obj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(obj != 0); + QCOMPARE(obj->sourceName(), QStringLiteral("positionpoll")); + QCOMPARE(obj->activeMonitors().count(), 0); + //reference -> should work + QVERIFY(obj->requestUpdate(copy, SIGNAL(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo)))); + QCOMPARE(obj->activeMonitors().count(), 1); + //replaces areaEntered single shot + QVERIFY(obj->requestUpdate(copy, SIGNAL(areaExited(QGeoAreaMonitorInfo,QGeoPositionInfo)))); + QCOMPARE(obj->activeMonitors().count(), 1); + //replaces areaExited single shot + QVERIFY(obj->startMonitoring(copy)); + QCOMPARE(obj->activeMonitors().count(), 1); + + + //invalid signal + QVERIFY(!obj->requestUpdate(copy, 0)); + QCOMPARE(obj->activeMonitors().count(), 1); + + //signal that doesn't exist + QVERIFY(!obj->requestUpdate(copy, SIGNAL(areaEntered(QGeoMonitor)))); + QCOMPARE(obj->activeMonitors().count(), 1); + + QVERIFY(!obj->requestUpdate(copy, "SIGNAL(areaEntered(QGeoMonitor))")); + QCOMPARE(obj->activeMonitors().count(), 1); + + //ensure that we cannot add a persistent monitor to a source + //that doesn't support persistence + QGeoAreaMonitorInfo persistenceMonitor(copy); + persistenceMonitor.setPersistent(obj->supportedAreaMonitorFeatures() & QGeoAreaMonitorSource::PersistentAreaMonitorFeature); + persistenceMonitor.setPersistent(!persistenceMonitor.isPersistent()); + + QVERIFY(!obj->requestUpdate(persistenceMonitor, SIGNAL(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo)))); + QCOMPARE(obj->activeMonitors().count(), 1); + QVERIFY(!obj->startMonitoring(persistenceMonitor)); + QCOMPARE(obj->activeMonitors().count(), 1); + + //ensure that persistence was only reason for rejection + persistenceMonitor.setPersistent(!persistenceMonitor.isPersistent()); + QVERIFY(obj->startMonitoring(persistenceMonitor)); + //persistenceMonitor is copy of already added monitor + //the last call was an update + QCOMPARE(obj->activeMonitors().count(), 1); + + delete obj; + } + + void tst_monitorValid() + { + QGeoAreaMonitorInfo mon; + QVERIFY(!mon.isValid()); + QCOMPARE(mon.name(), QString()); + QCOMPARE(mon.area().isValid(), false); + + QGeoAreaMonitorInfo mon2 = mon; + QVERIFY(!mon2.isValid()); + + QGeoShape invalidShape; + QGeoCircle emptyCircle(QGeoCoordinate(0,1), 0); + QGeoCircle validCircle(QGeoCoordinate(0,1), 1); + + //all invalid since no name set yet + mon2.setArea(invalidShape); + QVERIFY(mon2.area() == invalidShape); + QVERIFY(!mon2.isValid()); + + mon2.setArea(emptyCircle); + QVERIFY(mon2.area() == emptyCircle); + QVERIFY(!mon2.isValid()); + + mon2.setArea(validCircle); + QVERIFY(mon2.area() == validCircle); + QVERIFY(!mon2.isValid()); + + //valid since name and non-empy shape has been set + QGeoAreaMonitorInfo validMonitor("TestMonitor"); + QVERIFY(validMonitor.name() == QString("TestMonitor")); + QVERIFY(!validMonitor.isValid()); + + validMonitor.setArea(invalidShape); + QVERIFY(validMonitor.area() == invalidShape); + QVERIFY(!validMonitor.isValid()); + + validMonitor.setArea(emptyCircle); + QVERIFY(validMonitor.area() == emptyCircle); + QVERIFY(!validMonitor.isValid()); + + validMonitor.setArea(validCircle); + QVERIFY(validCircle == validMonitor.area()); + QVERIFY(validMonitor.isValid()); + } + + void tst_monitorStreaming() + { + QByteArray container; + QDataStream stream(&container, QIODevice::ReadWrite); + + QGeoAreaMonitorInfo monitor("someName"); + monitor.setArea(QGeoCircle(QGeoCoordinate(1,3), 5.4)); + QVERIFY(monitor.isValid()); + QCOMPARE(monitor.name(), QString("someName")); + + QGeoAreaMonitorInfo target; + QVERIFY(!target.isValid()); + QVERIFY(target.name().isEmpty()); + + QVERIFY(target != monitor); + + stream << monitor; + stream.device()->seek(0); + stream >> target; + + QVERIFY(target == monitor); + QVERIFY(target.isValid()); + QCOMPARE(target.name(), QString("someName")); + QVERIFY(target.area() == QGeoCircle(QGeoCoordinate(1,3), 5.4)); + } + + void tst_createDefaultSource() + { + QObject* parent = new QObject; + QGeoAreaMonitorSource* obj = QGeoAreaMonitorSource::createDefaultSource(parent); + QVERIFY(obj != 0); + QVERIFY(obj->parent() == parent); + delete obj; + + const QStringList monitors = QGeoAreaMonitorSource::availableSources(); + QVERIFY(!monitors.isEmpty()); + QVERIFY(monitors.contains(QStringLiteral("positionpoll"))); + + obj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), parent); + QVERIFY(obj != 0); + QCOMPARE(obj->sourceName(), QStringLiteral("positionpoll")); + delete parent; + + obj = QGeoAreaMonitorSource::createSource(QStringLiteral("randomNonExistingName"), 0); + QVERIFY(obj == 0); + } + + void tst_activeMonitors() + { + QGeoAreaMonitorSource *obj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(obj != 0); + QCOMPARE(obj->sourceName(), QStringLiteral("positionpoll")); + + LogFilePositionSource *source = new LogFilePositionSource(this); + source->setUpdateInterval(UPDATE_INTERVAL); + obj->setPositionInfoSource(source); + QCOMPARE(obj->positionInfoSource(), source); + + + QVERIFY(obj->activeMonitors().isEmpty()); + + QGeoAreaMonitorInfo mon("Monitor_Circle"); + mon.setArea(QGeoCircle(QGeoCoordinate(1,1), 1000)); + QVERIFY(obj->startMonitoring(mon)); + + QGeoAreaMonitorInfo mon2("Monitor_rectangle_below"); + QGeoRectangle r_below(QGeoCoordinate(1,1),2,2); + mon2.setArea(r_below); + QVERIFY(obj->startMonitoring(mon2)); + + QGeoAreaMonitorInfo mon3("Monitor_rectangle_above"); + QGeoRectangle r_above(QGeoCoordinate(2,1),2,2); + mon3.setArea(r_above); + QVERIFY(obj->startMonitoring(mon3)); + + QList results = obj->activeMonitors(); + QCOMPARE(results.count(), 3); + foreach (const QGeoAreaMonitorInfo& info, results) { + QVERIFY(info == mon || info == mon2 || info == mon3); + } + + results = obj->activeMonitors(QGeoShape()); + QCOMPARE(results.count(), 0); + + results = obj->activeMonitors(QGeoRectangle(QGeoCoordinate(1,1),0.2, 0.2)); + QCOMPARE(results.count(), 2); + foreach (const QGeoAreaMonitorInfo& info, results) { + QVERIFY(info == mon || info == mon2); + } + + results = obj->activeMonitors(QGeoCircle(QGeoCoordinate(1,1),1000)); + QCOMPARE(results.count(), 2); + foreach (const QGeoAreaMonitorInfo& info, results) { + QVERIFY(info == mon || info == mon2); + } + + results = obj->activeMonitors(QGeoCircle(QGeoCoordinate(2,1),1000)); + QCOMPARE(results.count(), 1); + foreach (const QGeoAreaMonitorInfo& info, results) { + QVERIFY(info == mon3); + } + + //same as above except that we use a different monitor source object instance + //all monitor objects of same type share same active monitors + QGeoAreaMonitorSource *secondObj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(secondObj != 0); + QCOMPARE(secondObj->sourceName(), QStringLiteral("positionpoll")); + + results = secondObj->activeMonitors(); + QCOMPARE(results.count(), 3); + foreach (const QGeoAreaMonitorInfo& info, results) { + QVERIFY(info == mon || info == mon2 || info == mon3); + } + + results = secondObj->activeMonitors(QGeoShape()); + QCOMPARE(results.count(), 0); + + results = secondObj->activeMonitors(QGeoRectangle(QGeoCoordinate(1,1),0.2, 0.2)); + QCOMPARE(results.count(), 2); + foreach (const QGeoAreaMonitorInfo& info, results) { + QVERIFY(info == mon || info == mon2); + } + + results = secondObj->activeMonitors(QGeoCircle(QGeoCoordinate(1,1),1000)); + QCOMPARE(results.count(), 2); + foreach (const QGeoAreaMonitorInfo& info, results) { + QVERIFY(info == mon || info == mon2); + } + + results = secondObj->activeMonitors(QGeoCircle(QGeoCoordinate(2,1),1000)); + QCOMPARE(results.count(), 1); + foreach (const QGeoAreaMonitorInfo& info, results) { + QVERIFY(info == mon3); + } + + delete obj; + delete secondObj; + } + + void tst_testExpiryTimeout() + { + QGeoAreaMonitorSource *obj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(obj != 0); + QCOMPARE(obj->sourceName(), QStringLiteral("positionpoll")); + + QGeoAreaMonitorSource *secondObj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(secondObj != 0); + QCOMPARE(secondObj->sourceName(), QStringLiteral("positionpoll")); + + LogFilePositionSource *source = new LogFilePositionSource(this); + source->setUpdateInterval(UPDATE_INTERVAL); + obj->setPositionInfoSource(source); + + //Singleton pattern behind QGeoAreaMonitorSource ensures same position info source + QCOMPARE(obj->positionInfoSource(), source); + QCOMPARE(secondObj->positionInfoSource(), source); + + QSignalSpy expirySpy(obj, SIGNAL(monitorExpired(QGeoAreaMonitorInfo))); + QSignalSpy expirySpy2(secondObj, SIGNAL(monitorExpired(QGeoAreaMonitorInfo))); + + QDateTime now = QDateTime::currentDateTime(); + + const int monitorCount = 4; + for (int i = 1; i <= monitorCount; i++) { + QGeoAreaMonitorInfo mon(QString::number(i)); + mon.setArea(QGeoRectangle(QGeoCoordinate(i,i), i, i)); + mon.setExpiration(now.addSecs(i*5)); + QVERIFY(mon.isValid()); + QVERIFY(obj->startMonitoring(mon)); + } + + + + QCOMPARE(obj->activeMonitors().count(), monitorCount); + QCOMPARE(secondObj->activeMonitors().count(), monitorCount); + + QGeoAreaMonitorInfo info("InvalidExpiry"); + info.setArea(QGeoRectangle(QGeoCoordinate(10,10), 1, 1 )); + QVERIFY(info.isValid()); + info.setExpiration(now.addSecs(-1000)); + QVERIFY(info.expiration() < now); + QVERIFY(!obj->startMonitoring(info)); + QCOMPARE(obj->activeMonitors().count(), monitorCount); + QVERIFY(!obj->requestUpdate(info, SIGNAL(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo)))); + QCOMPARE(obj->activeMonitors().count(), monitorCount); + + for (int i = 1; i <= monitorCount; i++) { + QTRY_VERIFY_WITH_TIMEOUT(expirySpy.count() == 1, 7000); //each expiry within 5 s + QGeoAreaMonitorInfo mon = expirySpy.takeFirst().at(0).value(); + QCOMPARE(obj->activeMonitors().count(), monitorCount-i); + QCOMPARE(mon.name(), QString::number(i)); + } + + QCOMPARE(expirySpy2.count(), monitorCount); + QCOMPARE(secondObj->activeMonitors().count(), 0); //all monitors expired + for (int i = 1; i <= monitorCount; i++) { + QGeoAreaMonitorInfo mon = expirySpy2.takeFirst().at(0).value(); + QCOMPARE(mon.name(), QString::number(i)); + } + + delete obj; + delete secondObj; + } + + void tst_enteredExitedSignal() + { + QGeoAreaMonitorSource *obj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(obj != 0); + QCOMPARE(obj->sourceName(), QStringLiteral("positionpoll")); + obj->setObjectName("firstObject"); + QSignalSpy enteredSpy(obj, SIGNAL(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo))); + QSignalSpy exitedSpy(obj, SIGNAL(areaExited(QGeoAreaMonitorInfo,QGeoPositionInfo))); + + LogFilePositionSource *source = new LogFilePositionSource(this); + source->setUpdateInterval(UPDATE_INTERVAL); + obj->setPositionInfoSource(source); + QCOMPARE(obj->positionInfoSource(), source); + + QGeoAreaMonitorSource *secondObj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(secondObj != 0); + QCOMPARE(secondObj->sourceName(), QStringLiteral("positionpoll")); + QSignalSpy enteredSpy2(secondObj, SIGNAL(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo))); + QSignalSpy exitedSpy2(secondObj, SIGNAL(areaExited(QGeoAreaMonitorInfo,QGeoPositionInfo))); + secondObj->setObjectName("secondObject"); + + QGeoAreaMonitorInfo infoRectangle("Rectangle"); + infoRectangle.setArea(QGeoRectangle(QGeoCoordinate(-27.65, 153.093), 0.2, 0.2)); + QVERIFY(infoRectangle.isValid()); + QVERIFY(obj->startMonitoring(infoRectangle)); + + QGeoAreaMonitorInfo infoCircle("Circle"); + infoCircle.setArea(QGeoCircle(QGeoCoordinate(-27.70, 153.093),10000)); + QVERIFY(infoCircle.isValid()); + QVERIFY(obj->startMonitoring(infoCircle)); + + QGeoAreaMonitorInfo singleShot_enter("SingleShot_on_Entered"); + singleShot_enter.setArea(QGeoRectangle(QGeoCoordinate(-27.67, 153.093), 0.2, 0.2)); + QVERIFY(singleShot_enter.isValid()); + QVERIFY(obj->requestUpdate(singleShot_enter, + SIGNAL(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo)))); + + QGeoAreaMonitorInfo singleShot_exit("SingleShot_on_Exited"); + singleShot_exit.setArea(QGeoRectangle(QGeoCoordinate(-27.70, 153.093), 0.2, 0.2)); + QVERIFY(singleShot_exit.isValid()); + QVERIFY(obj->requestUpdate(singleShot_exit, + SIGNAL(areaExited(QGeoAreaMonitorInfo,QGeoPositionInfo)))); + + QVERIFY(obj->activeMonitors().count() == 4); //all monitors active + QVERIFY(secondObj->activeMonitors().count() == 4); //all monitors active + + static const int Number_Of_Entered_Events = 6; + static const int Number_Of_Exited_Events = 5; + //takes 87 (lines)*200(timeout)/1000 seconds to finish + QTRY_VERIFY_WITH_TIMEOUT(enteredSpy.count() == Number_Of_Entered_Events, 20000); + QTRY_VERIFY_WITH_TIMEOUT(exitedSpy.count() == Number_Of_Exited_Events, 20000); + QCOMPARE(enteredSpy.count(), Number_Of_Entered_Events); + QCOMPARE(exitedSpy.count(), Number_Of_Exited_Events); + + QList monitorsInExpectedEnteredEventOrder; + monitorsInExpectedEnteredEventOrder << infoRectangle << singleShot_enter << singleShot_exit + << infoCircle << infoCircle << infoRectangle; + + QList monitorsInExpectedExitedEventOrder; + monitorsInExpectedExitedEventOrder << infoRectangle << infoCircle + << singleShot_exit << infoCircle << infoRectangle; + + QList enteredEventCoordinateOrder; + enteredEventCoordinateOrder << QGeoCoordinate(-27.55, 153.090718) //infoRectangle + << QGeoCoordinate(-27.57, 153.090718) //singleshot_enter + << QGeoCoordinate(-27.60, 153.090908) //singleshot_exit + << QGeoCoordinate(-27.62, 153.091036) //infoCircle + << QGeoCoordinate(-27.78, 153.093647) //infoCircle + << QGeoCoordinate(-27.75, 153.093896);//infoRectangle + QCOMPARE(enteredEventCoordinateOrder.count(), Number_Of_Entered_Events); + QCOMPARE(monitorsInExpectedEnteredEventOrder.count(), Number_Of_Entered_Events); + + QList exitedEventCoordinateOrder; + exitedEventCoordinateOrder << QGeoCoordinate(-27.78, 153.092218) //infoRectangle + << QGeoCoordinate(-27.79, 153.092308) //infoCircle + << QGeoCoordinate(-27.81, 153.092530) //singleshot_exit + << QGeoCoordinate(-27.61, 153.095231) //infoCircle + << QGeoCoordinate(-27.54, 153.095995);//infoCircle + QCOMPARE(exitedEventCoordinateOrder.count(), Number_Of_Exited_Events); + QCOMPARE(monitorsInExpectedExitedEventOrder.count(), Number_Of_Exited_Events); + + //verify that both sources got the same signals + for (int i = 0; i < Number_Of_Entered_Events; i++) { + //first source + QGeoAreaMonitorInfo monInfo = enteredSpy.first().at(0).value(); + QGeoPositionInfo posInfo = enteredSpy.takeFirst().at(1).value(); + QVERIFY2(monInfo == monitorsInExpectedEnteredEventOrder.at(i), + qPrintable(QString::number(i) + ": " + monInfo.name())); + QVERIFY2(posInfo.coordinate() == enteredEventCoordinateOrder.at(i), + qPrintable(QString::number(i) + ". posInfo")); + + //reset info objects to avoid comparing the same + monInfo = QGeoAreaMonitorInfo(); + posInfo = QGeoPositionInfo(); + + //second source + monInfo = enteredSpy2.first().at(0).value(); + posInfo = enteredSpy2.takeFirst().at(1).value(); + QVERIFY2(monInfo == monitorsInExpectedEnteredEventOrder.at(i), + qPrintable(QString::number(i) + ": " + monInfo.name())); + QVERIFY2(posInfo.coordinate() == enteredEventCoordinateOrder.at(i), + qPrintable(QString::number(i) + ". posInfo")); + } + + for (int i = 0; i < Number_Of_Exited_Events; i++) { + //first source + QGeoAreaMonitorInfo monInfo = exitedSpy.first().at(0).value(); + QGeoPositionInfo posInfo = exitedSpy.takeFirst().at(1).value(); + QVERIFY2(monInfo == monitorsInExpectedExitedEventOrder.at(i), + qPrintable(QString::number(i) + ": " + monInfo.name())); + QVERIFY2(posInfo.coordinate() == exitedEventCoordinateOrder.at(i), + qPrintable(QString::number(i) + ". posInfo")); + + //reset info objects to avoid comparing the same + monInfo = QGeoAreaMonitorInfo(); + posInfo = QGeoPositionInfo(); + + //second source + monInfo = exitedSpy2.first().at(0).value(); + posInfo = exitedSpy2.takeFirst().at(1).value(); + QVERIFY2(monInfo == monitorsInExpectedExitedEventOrder.at(i), + qPrintable(QString::number(i) + ": " + monInfo.name())); + QVERIFY2(posInfo.coordinate() == exitedEventCoordinateOrder.at(i), + qPrintable(QString::number(i) + ". posInfo")); + } + + QCOMPARE(obj->activeMonitors().count(), 2); //single shot monitors have been removed + QCOMPARE(secondObj->activeMonitors().count(), 2); + + delete obj; + delete secondObj; + } + + void tst_swapOfPositionSource() + { + QGeoAreaMonitorSource *obj = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(obj != 0); + QCOMPARE(obj->sourceName(), QStringLiteral("positionpoll")); + obj->setObjectName("firstObject"); + QSignalSpy enteredSpy(obj, SIGNAL(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo))); + QSignalSpy exitedSpy(obj, SIGNAL(areaExited(QGeoAreaMonitorInfo,QGeoPositionInfo))); + + QGeoAreaMonitorSource *obj2 = QGeoAreaMonitorSource::createSource(QStringLiteral("positionpoll"), 0); + QVERIFY(obj2 != 0); + QCOMPARE(obj2->sourceName(), QStringLiteral("positionpoll")); + obj2->setObjectName("secondObject"); + QSignalSpy enteredSpy2(obj2, SIGNAL(areaEntered(QGeoAreaMonitorInfo,QGeoPositionInfo))); + QSignalSpy exitedSpy2(obj2, SIGNAL(areaExited(QGeoAreaMonitorInfo,QGeoPositionInfo))); + + LogFilePositionSource *source = new LogFilePositionSource(this); + source->setUpdateInterval(UPDATE_INTERVAL); + source->setObjectName("FirstLogFileSource"); + + LogFilePositionSource *source2 = new LogFilePositionSource(this); + source2->setUpdateInterval(UPDATE_INTERVAL); + source2->setObjectName("SecondLogFileSource"); + + obj->setPositionInfoSource(source); + QCOMPARE(obj->positionInfoSource(), obj2->positionInfoSource()); + QCOMPARE(obj2->positionInfoSource(), source); + + QGeoAreaMonitorInfo infoRectangle("Rectangle"); + infoRectangle.setArea(QGeoRectangle(QGeoCoordinate(-27.70, 153.092), 0.2, 0.2)); + QVERIFY(infoRectangle.isValid()); + QVERIFY(obj->startMonitoring(infoRectangle)); + + QCOMPARE(obj->activeMonitors().count(), 1); + QCOMPARE(obj2->activeMonitors().count(), 1); + + QGeoCoordinate firstBorder(-27.6, 153.090908); + QGeoCoordinate secondBorder(-27.81, 153.092530); + + /***********************************/ + //1. trigger events on source (until areaExit + QTRY_VERIFY_WITH_TIMEOUT(exitedSpy.count() == 1, 20000); + QCOMPARE(enteredSpy.count(), enteredSpy2.count()); + QCOMPARE(exitedSpy.count(), exitedSpy2.count()); + + //compare entered event + QVERIFY(enteredSpy.first().at(0).value() == + enteredSpy2.first().at(0).value()); + QGeoPositionInfo info = enteredSpy.takeFirst().at(1).value(); + QVERIFY(info == enteredSpy2.takeFirst().at(1).value()); + QVERIFY(info.coordinate() == firstBorder); + //compare exit event + QVERIFY(exitedSpy.first().at(0).value() == + exitedSpy2.first().at(0).value()); + info = exitedSpy.takeFirst().at(1).value(); + QVERIFY(info == exitedSpy2.takeFirst().at(1).value()); + QVERIFY(info.coordinate() == secondBorder); + + QCOMPARE(exitedSpy.count(), 0); + QCOMPARE(enteredSpy.count(), 0); + QCOMPARE(exitedSpy2.count(), 0); + QCOMPARE(enteredSpy2.count(), 0); + + /***********************************/ + //2. change position source -> which restarts at beginning again + obj2->setPositionInfoSource(source2); + QCOMPARE(obj->positionInfoSource(), obj2->positionInfoSource()); + QCOMPARE(obj2->positionInfoSource(), source2); + + QTRY_VERIFY_WITH_TIMEOUT(exitedSpy.count() == 1, 20000); + QCOMPARE(enteredSpy.count(), enteredSpy2.count()); + QCOMPARE(exitedSpy.count(), exitedSpy2.count()); + + //compare entered event + QVERIFY(enteredSpy.first().at(0).value() == + enteredSpy2.first().at(0).value()); + info = enteredSpy.takeFirst().at(1).value(); + QVERIFY(info == enteredSpy2.takeFirst().at(1).value()); + QVERIFY(info.coordinate() == firstBorder); + //compare exit event + QVERIFY(exitedSpy.first().at(0).value() == + exitedSpy2.first().at(0).value()); + info = exitedSpy.takeFirst().at(1).value(); + QVERIFY(info == exitedSpy2.takeFirst().at(1).value()); + QVERIFY(info.coordinate() == secondBorder); + + + //obj was deleted when setting new source + delete obj2; + } + + void debug_data() + { + QTest::addColumn("info"); + QTest::addColumn("nextValue"); + QTest::addColumn("debugString"); + + QGeoAreaMonitorInfo info; + QTest::newRow("uninitialized") << info << 45 + << QString("QGeoAreaMonitorInfo(\"\", QGeoShape(Unknown), " + "persistent: false, expiry: QDateTime( Qt::TimeSpec(LocalTime))) 45"); + + info.setArea(QGeoRectangle()); + info.setPersistent(true); + info.setName("RectangleAreaMonitor"); + QTest::newRow("Rectangle Test") << info << 45 + << QString("QGeoAreaMonitorInfo(\"RectangleAreaMonitor\", QGeoShape(Rectangle), " + "persistent: true, expiry: QDateTime( Qt::TimeSpec(LocalTime))) 45"); + + info = QGeoAreaMonitorInfo(); + info.setArea(QGeoCircle()); + info.setPersistent(false); + info.setName("CircleAreaMonitor"); + QVariantMap map; + map.insert(QString("foobarKey"), QVariant(45)); //should be ignored + info.setNotificationParameters(map); + QTest::newRow("Circle Test") << info << 45 + << QString("QGeoAreaMonitorInfo(\"CircleAreaMonitor\", QGeoShape(Circle), " + "persistent: false, expiry: QDateTime( Qt::TimeSpec(LocalTime))) 45"); + + // we ignore any further QDateTime related changes to avoid depending on QDateTime related + // failures in case its QDebug string changes + } + + void debug() + { + QFETCH(QGeoAreaMonitorInfo, info); + QFETCH(int, nextValue); + QFETCH(QString, debugString); + + qInstallMessageHandler(tst_qgeoareamonitorinfo_messageHandler); + qDebug() << info << nextValue; + qInstallMessageHandler(0); + QCOMPARE(tst_qgeoareamonitorinfo_debug, debugString); + } +}; + + +QTEST_GUILESS_MAIN(tst_QGeoAreaMonitorSource) +#include "tst_qgeoareamonitor.moc" diff --git a/tests/auto/qgeocameracapabilities/qgeocameracapabilities.pro b/tests/auto/qgeocameracapabilities/qgeocameracapabilities.pro new file mode 100644 index 0000000..f629a15 --- /dev/null +++ b/tests/auto/qgeocameracapabilities/qgeocameracapabilities.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qgeocameracapabilities + +INCLUDEPATH += ../../../src/location/maps + +SOURCES += tst_qgeocameracapabilities.cpp + +QT += location positioning-private testlib diff --git a/tests/auto/qgeocameracapabilities/tst_qgeocameracapabilities.cpp b/tests/auto/qgeocameracapabilities/tst_qgeocameracapabilities.cpp new file mode 100644 index 0000000..5475542 --- /dev/null +++ b/tests/auto/qgeocameracapabilities/tst_qgeocameracapabilities.cpp @@ -0,0 +1,297 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qgeocameracapabilities_p.h" +#include "qgeotiledmap_p.h" + +QT_USE_NAMESPACE + +class tst_QGeoCameraCapabilities : public QObject +{ + Q_OBJECT + +public: + tst_QGeoCameraCapabilities(); + +private: + void populateGeoCameraCapabilitiesData(); + +private Q_SLOTS: + void constructorTest_data(); + void constructorTest(); + void minimumZoomLevelTest(); + void maximumZoomLevelTest(); + void supportsBearingTest(); + void supportsRollingTest(); + void supportsTiltingTest(); + void minimumTiltTest(); + void maximumTiltTest(); + void operatorsTest_data(); + void operatorsTest(); + void isValidTest(); +}; + +tst_QGeoCameraCapabilities::tst_QGeoCameraCapabilities() +{ +} + +void tst_QGeoCameraCapabilities::populateGeoCameraCapabilitiesData(){ + QTest::addColumn("minimumZoomLevel"); + QTest::addColumn("maximumZoomLevel"); + QTest::addColumn("minimumTilt"); + QTest::addColumn("maximumTilt"); + QTest::addColumn("bearingSupport"); + QTest::addColumn("rollingSupport"); + QTest::addColumn("tiltingSupport"); + QTest::newRow("zeros") << 0.0 << 0.0 << 0.0 << 0.0 << false << false << false; + QTest::newRow("valid") << 1.0 << 2.0 << 0.5 << 1.5 << true << true << true; + QTest::newRow("negative values") << 0.0 << 0.5 << -0.5 << -0.1 << true << true << true; +} + +void tst_QGeoCameraCapabilities::constructorTest_data(){ + populateGeoCameraCapabilitiesData(); +} + +void tst_QGeoCameraCapabilities::constructorTest() +{ + QFETCH(double, minimumZoomLevel); + QFETCH(double, maximumZoomLevel); + QFETCH(double, minimumTilt); + QFETCH(double, maximumTilt); + QFETCH(bool, bearingSupport); + QFETCH(bool, rollingSupport); + QFETCH(bool, tiltingSupport); + + // contructor test with default values + QGeoCameraCapabilities cameraCapabilities; + QGeoCameraCapabilities cameraCapabilities2(cameraCapabilities); + QCOMPARE(cameraCapabilities.minimumZoomLevel(), cameraCapabilities2.minimumZoomLevel()); + QCOMPARE(cameraCapabilities.maximumZoomLevel(), cameraCapabilities2.maximumZoomLevel()); + QVERIFY2(cameraCapabilities.supportsBearing() == cameraCapabilities2.supportsBearing(), "Copy constructor failed for bearing support"); + QVERIFY2(cameraCapabilities.supportsRolling() == cameraCapabilities2.supportsRolling(), "Copy constructor failed for rolling support "); + QVERIFY2(cameraCapabilities.supportsTilting() == cameraCapabilities2.supportsTilting(), "Copy constructor failed for tilting support"); + QCOMPARE(cameraCapabilities.minimumTilt(), cameraCapabilities2.minimumTilt()); + QCOMPARE(cameraCapabilities.maximumTilt(), cameraCapabilities2.maximumTilt()); + + // constructor test after setting values + cameraCapabilities.setMinimumZoomLevel(minimumZoomLevel); + cameraCapabilities.setMaximumZoomLevel(maximumZoomLevel); + cameraCapabilities.setMinimumTilt(minimumTilt); + cameraCapabilities.setMaximumTilt(maximumTilt); + cameraCapabilities.setSupportsBearing(bearingSupport); + cameraCapabilities.setSupportsRolling(rollingSupport); + cameraCapabilities.setSupportsTilting(tiltingSupport); + + QGeoCameraCapabilities cameraCapabilities3(cameraCapabilities); + // test the correctness of the constructor copy + QCOMPARE(cameraCapabilities3.minimumZoomLevel(), minimumZoomLevel); + QCOMPARE(cameraCapabilities3.maximumZoomLevel(), maximumZoomLevel); + QCOMPARE(cameraCapabilities3.minimumTilt(), minimumTilt); + QCOMPARE(cameraCapabilities3.maximumTilt(), maximumTilt); + QVERIFY2(cameraCapabilities3.supportsBearing() == bearingSupport, "Copy constructor failed for bearing support"); + QVERIFY2(cameraCapabilities3.supportsRolling() == rollingSupport, "Copy constructor failed for rolling support "); + QVERIFY2(cameraCapabilities3.supportsTilting() == tiltingSupport, "Copy constructor failed for tilting support"); + // verify that values have not changed after a constructor copy + QCOMPARE(cameraCapabilities.minimumZoomLevel(), cameraCapabilities3.minimumZoomLevel()); + QCOMPARE(cameraCapabilities.maximumZoomLevel(), cameraCapabilities3.maximumZoomLevel()); + QVERIFY2(cameraCapabilities.supportsBearing() == cameraCapabilities3.supportsBearing(), "Copy constructor failed for bearing support"); + QVERIFY2(cameraCapabilities.supportsRolling() == cameraCapabilities3.supportsRolling(), "Copy constructor failed for rolling support "); + QVERIFY2(cameraCapabilities.supportsTilting() == cameraCapabilities3.supportsTilting(), "Copy constructor failed for tilting support"); + QCOMPARE(cameraCapabilities.minimumTilt(), cameraCapabilities3.minimumTilt()); + QCOMPARE(cameraCapabilities.maximumTilt(), cameraCapabilities3.maximumTilt()); +} + +void tst_QGeoCameraCapabilities::minimumZoomLevelTest() +{ + QGeoCameraCapabilities cameraCapabilities; + cameraCapabilities.setMinimumZoomLevel(1.5); + QCOMPARE(cameraCapabilities.minimumZoomLevel(), 1.5); + + QGeoCameraCapabilities cameraCapabilities2 = cameraCapabilities; + QCOMPARE(cameraCapabilities2.minimumZoomLevel(), 1.5); + cameraCapabilities.setMinimumZoomLevel(2.5); + QCOMPARE(cameraCapabilities2.minimumZoomLevel(), 1.5); +} + +void tst_QGeoCameraCapabilities::maximumZoomLevelTest() +{ + QGeoCameraCapabilities cameraCapabilities; + cameraCapabilities.setMaximumZoomLevel(3.5); + QCOMPARE(cameraCapabilities.maximumZoomLevel(), 3.5); + + QGeoCameraCapabilities cameraCapabilities2 = cameraCapabilities; + QCOMPARE(cameraCapabilities2.maximumZoomLevel(), 3.5); + cameraCapabilities.setMaximumZoomLevel(4.5); + QCOMPARE(cameraCapabilities2.maximumZoomLevel(), 3.5); +} + +void tst_QGeoCameraCapabilities::supportsBearingTest(){ + QGeoCameraCapabilities cameraCapabilities; + QVERIFY(!cameraCapabilities.supportsBearing()); + cameraCapabilities.setSupportsBearing(true); + QVERIFY2(cameraCapabilities.supportsBearing(), "Camera capabilities should support bearing"); + + QGeoCameraCapabilities cameraCapabilities2 = cameraCapabilities; + QVERIFY(cameraCapabilities2.supportsBearing()); + cameraCapabilities.setSupportsBearing(false); + QVERIFY2(cameraCapabilities2.supportsBearing(), "Camera capabilities should support bearing"); +} + +void tst_QGeoCameraCapabilities::supportsRollingTest(){ + QGeoCameraCapabilities cameraCapabilities; + QVERIFY(!cameraCapabilities.supportsRolling()); + cameraCapabilities.setSupportsRolling(true); + QVERIFY2(cameraCapabilities.supportsRolling(), "Camera capabilities should support rolling"); + + QGeoCameraCapabilities cameraCapabilities2 = cameraCapabilities; + QVERIFY(cameraCapabilities2.supportsRolling()); + cameraCapabilities.setSupportsRolling(false); + QVERIFY2(cameraCapabilities2.supportsRolling(), "Camera capabilities should support rolling"); +} + +void tst_QGeoCameraCapabilities::supportsTiltingTest(){ + QGeoCameraCapabilities cameraCapabilities; + QVERIFY(!cameraCapabilities.supportsTilting()); + cameraCapabilities.setSupportsTilting(true); + QVERIFY2(cameraCapabilities.supportsTilting(), "Camera capabilities should support tilting"); + + QGeoCameraCapabilities cameraCapabilities2 = cameraCapabilities; + QVERIFY(cameraCapabilities2.supportsTilting()); + cameraCapabilities.setSupportsTilting(false); + QVERIFY2(cameraCapabilities2.supportsTilting(), "Camera capabilities should support tilting"); +} + +void tst_QGeoCameraCapabilities::minimumTiltTest(){ + QGeoCameraCapabilities cameraCapabilities; + QCOMPARE(cameraCapabilities.minimumTilt(),0.0); + cameraCapabilities.setMinimumTilt(0.5); + QCOMPARE(cameraCapabilities.minimumTilt(),0.5); + + QGeoCameraCapabilities cameraCapabilities2 = cameraCapabilities; + QCOMPARE(cameraCapabilities2.minimumTilt(), 0.5); + cameraCapabilities.setMinimumTilt(1.5); + QCOMPARE(cameraCapabilities2.minimumTilt(), 0.5); +} + +void tst_QGeoCameraCapabilities::maximumTiltTest(){ + QGeoCameraCapabilities cameraCapabilities; + QCOMPARE(cameraCapabilities.maximumTilt(),0.0); + cameraCapabilities.setMaximumTilt(1.5); + QCOMPARE(cameraCapabilities.maximumTilt(),1.5); + + QGeoCameraCapabilities cameraCapabilities2 = cameraCapabilities; + QCOMPARE(cameraCapabilities2.maximumTilt(), 1.5); + cameraCapabilities.setMaximumTilt(2.5); + QCOMPARE(cameraCapabilities2.maximumTilt(), 1.5); +} + +void tst_QGeoCameraCapabilities::operatorsTest_data(){ + populateGeoCameraCapabilitiesData(); +} + +void tst_QGeoCameraCapabilities::operatorsTest(){ + + QFETCH(double, minimumZoomLevel); + QFETCH(double, maximumZoomLevel); + QFETCH(double, minimumTilt); + QFETCH(double, maximumTilt); + QFETCH(bool, bearingSupport); + QFETCH(bool, rollingSupport); + QFETCH(bool, tiltingSupport); + + QGeoCameraCapabilities cameraCapabilities; + cameraCapabilities.setMinimumZoomLevel(minimumZoomLevel); + cameraCapabilities.setMaximumZoomLevel(maximumZoomLevel); + cameraCapabilities.setMinimumTilt(minimumTilt); + cameraCapabilities.setMaximumTilt(maximumTilt); + cameraCapabilities.setSupportsBearing(bearingSupport); + cameraCapabilities.setSupportsRolling(rollingSupport); + cameraCapabilities.setSupportsTilting(tiltingSupport); + QGeoCameraCapabilities cameraCapabilities2; + cameraCapabilities2 = cameraCapabilities; + // test the correctness of the assignment + QCOMPARE(cameraCapabilities2.minimumZoomLevel(), minimumZoomLevel); + QCOMPARE(cameraCapabilities2.maximumZoomLevel(), maximumZoomLevel); + QCOMPARE(cameraCapabilities2.minimumTilt(), minimumTilt); + QCOMPARE(cameraCapabilities2.maximumTilt(), maximumTilt); + QVERIFY2(cameraCapabilities2.supportsBearing() == bearingSupport, "Copy constructor failed for bearing support"); + QVERIFY2(cameraCapabilities2.supportsRolling() == rollingSupport, "Copy constructor failed for rolling support "); + QVERIFY2(cameraCapabilities2.supportsTilting() == tiltingSupport, "Copy constructor failed for tilting support"); + // verify that values have not changed after a constructor copy + QCOMPARE(cameraCapabilities.minimumZoomLevel(), cameraCapabilities2.minimumZoomLevel()); + QCOMPARE(cameraCapabilities.maximumZoomLevel(), cameraCapabilities2.maximumZoomLevel()); + QVERIFY2(cameraCapabilities.supportsBearing() == cameraCapabilities2.supportsBearing(), "Copy constructor failed for bearing support"); + QVERIFY2(cameraCapabilities.supportsRolling() == cameraCapabilities2.supportsRolling(), "Copy constructor failed for rolling support "); + QVERIFY2(cameraCapabilities.supportsTilting() == cameraCapabilities2.supportsTilting(), "Copy constructor failed for tilting support"); + QCOMPARE(cameraCapabilities.minimumTilt(), cameraCapabilities2.minimumTilt()); + QCOMPARE(cameraCapabilities.maximumTilt(), cameraCapabilities2.maximumTilt()); +} + +void tst_QGeoCameraCapabilities::isValidTest(){ + QGeoCameraCapabilities cameraCapabilities; + QVERIFY2(!cameraCapabilities.isValid(), "Camera capabilities should default to invalid"); + cameraCapabilities.setSupportsBearing(true); + QVERIFY2(cameraCapabilities.isValid(), "Camera capabilities should be valid"); + + QGeoCameraCapabilities cameraCapabilities2; + QVERIFY2(!cameraCapabilities2.isValid(), "Camera capabilities should default to invalid"); + cameraCapabilities2.setSupportsRolling(true); + QVERIFY2(cameraCapabilities2.isValid(), "Camera capabilities should be valid"); + + QGeoCameraCapabilities cameraCapabilities3; + QVERIFY2(!cameraCapabilities3.isValid(), "Camera capabilities should default to invalid"); + cameraCapabilities3.setSupportsTilting(true); + QVERIFY2(cameraCapabilities3.isValid(), "Camera capabilities should be valid"); + + QGeoCameraCapabilities cameraCapabilities4; + QVERIFY2(!cameraCapabilities4.isValid(), "Camera capabilities should default to invalid"); + cameraCapabilities4.setMinimumZoomLevel(1.0); + QVERIFY2(cameraCapabilities4.isValid(), "Camera capabilities should be valid"); + + QGeoCameraCapabilities cameraCapabilities5; + QVERIFY2(!cameraCapabilities5.isValid(), "Camera capabilities should default to invalid"); + cameraCapabilities5.setMaximumZoomLevel(1.5); + QVERIFY2(cameraCapabilities5.isValid(), "Camera capabilities should be valid"); + + QGeoCameraCapabilities cameraCapabilities6; + QVERIFY2(!cameraCapabilities6.isValid(), "Camera capabilities should default to invalid"); + cameraCapabilities6.setMinimumTilt(0.2); + QVERIFY2(cameraCapabilities6.isValid(), "Camera capabilities should be valid"); + + QGeoCameraCapabilities cameraCapabilities7; + QVERIFY2(!cameraCapabilities7.isValid(), "Camera capabilities should default to invalid"); + cameraCapabilities7.setMaximumTilt(0.8); + QVERIFY2(cameraCapabilities7.isValid(), "Camera capabilities should be valid"); +} + +QTEST_APPLESS_MAIN(tst_QGeoCameraCapabilities) + +#include "tst_qgeocameracapabilities.moc" diff --git a/tests/auto/qgeocameradata/qgeocameradata.pro b/tests/auto/qgeocameradata/qgeocameradata.pro new file mode 100644 index 0000000..f2d6d5d --- /dev/null +++ b/tests/auto/qgeocameradata/qgeocameradata.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qgeocameradata + +INCLUDEPATH += ../../../src/location/maps + +SOURCES += tst_qgeocameradata.cpp + +QT += location positioning-private testlib diff --git a/tests/auto/qgeocameradata/tst_qgeocameradata.cpp b/tests/auto/qgeocameradata/tst_qgeocameradata.cpp new file mode 100644 index 0000000..be75acf --- /dev/null +++ b/tests/auto/qgeocameradata/tst_qgeocameradata.cpp @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qgeocameradata_p.h" + +QT_USE_NAMESPACE + +class tst_QGeoCameraData : public QObject +{ + Q_OBJECT + +public: + tst_QGeoCameraData(); + +private: + void populateCameraData(); + +private Q_SLOTS: + void constructorTest_data(); + void constructorTest(); + void centerTest(); + void bearingTest(); + void tiltTest(); + void rollTest(); + void zoomLevelTest(); + void operatorsTest_data(); + void operatorsTest(); +}; + +tst_QGeoCameraData::tst_QGeoCameraData() +{ +} + + +void tst_QGeoCameraData::populateCameraData() +{ + QTest::addColumn("center"); + QTest::addColumn("bearing"); + QTest::addColumn("tilt"); + QTest::addColumn("roll"); + QTest::addColumn("zoomLevel"); + QTest::newRow("zeros") << QGeoCoordinate() << 0.0 << 0.0 << 0.0 << 0.0; + QTest::newRow("valid") << QGeoCoordinate(10.0,20.5,30.8) << 0.1 << 0.2 << 0.3 << 2.0; + QTest::newRow("negative values") << QGeoCoordinate(-50,-20,100) << -0.1 << -0.2 << -0.3 << 1.0; +} + +void tst_QGeoCameraData::constructorTest_data(){ + populateCameraData(); +} + +void tst_QGeoCameraData::constructorTest() +{ + QFETCH(QGeoCoordinate, center); + QFETCH(double, bearing); + QFETCH(double, tilt); + QFETCH(double, roll); + QFETCH(double, zoomLevel); + + // constructor test with default values + QGeoCameraData cameraData; + QGeoCameraData cameraData2(cameraData); + QCOMPARE(cameraData.center(), cameraData2.center()); + QCOMPARE(cameraData.bearing(), cameraData2.bearing()); + QCOMPARE(cameraData.tilt(), cameraData2.tilt()); + QCOMPARE(cameraData.roll(), cameraData2.roll()); + QCOMPARE(cameraData.zoomLevel(), cameraData2.zoomLevel()); + + // constructor test after setting values + cameraData.setCenter(center); + cameraData.setBearing(bearing); + cameraData.setTilt(tilt); + cameraData.setRoll(roll); + cameraData.setZoomLevel(zoomLevel); + QGeoCameraData cameraData3(cameraData); + // test the correctness of the constructor copy + QCOMPARE(cameraData3.center(), center); + QCOMPARE(cameraData3.bearing(), bearing); + QCOMPARE(cameraData3.tilt(), tilt); + QCOMPARE(cameraData3.roll(), roll); + QCOMPARE(cameraData3.zoomLevel(), zoomLevel); + // verify that values have not changed after a constructor copy + QCOMPARE(cameraData.center(), cameraData3.center()); + QCOMPARE(cameraData.bearing(), cameraData3.bearing()); + QCOMPARE(cameraData.tilt(), cameraData3.tilt()); + QCOMPARE(cameraData.roll(), cameraData3.roll()); + QCOMPARE(cameraData.zoomLevel(), cameraData3.zoomLevel()); +} + + +void tst_QGeoCameraData::centerTest() +{ + QGeoCameraData cameraData; //center currently default to (-27.5, 153) + cameraData.setCenter(QGeoCoordinate(10.0,20.4,30.8)); + QCOMPARE(cameraData.center(),QGeoCoordinate(10.0,20.4,30.8)); +} + +void tst_QGeoCameraData::bearingTest(){ + QGeoCameraData cameraData; + QCOMPARE(cameraData.bearing(),0.0); + cameraData.setBearing(0.1); + QCOMPARE(cameraData.bearing(),0.1); + + QGeoCameraData cameraData2 = cameraData; + QCOMPARE(cameraData2.bearing(),0.1); + cameraData.setBearing(0.2); + QCOMPARE(cameraData2.bearing(),0.1); +} + +void tst_QGeoCameraData::tiltTest(){ + QGeoCameraData cameraData; + QCOMPARE(cameraData.tilt(),0.0); + cameraData.setTilt(0.4); + QCOMPARE(cameraData.tilt(),0.4); + + QGeoCameraData cameraData2 = cameraData; + QCOMPARE(cameraData2.tilt(),0.4); + cameraData.setTilt(0.5); + QCOMPARE(cameraData2.tilt(),0.4); +} + +void tst_QGeoCameraData::rollTest(){ + QGeoCameraData cameraData; + QCOMPARE(cameraData.roll(),0.0); + cameraData.setRoll(0.5); + QCOMPARE(cameraData.roll(),0.5); + + QGeoCameraData cameraData2 = cameraData; + QCOMPARE(cameraData2.roll(),0.5); + cameraData.setRoll(0.6); + QCOMPARE(cameraData2.roll(),0.5); +} + +void tst_QGeoCameraData::zoomLevelTest(){ + QGeoCameraData cameraData; //zoom level currently defaults to 9.0 + cameraData.setZoomLevel(8.0); + QCOMPARE(cameraData.zoomLevel(),8.0); + + QGeoCameraData cameraData2 = cameraData; + QCOMPARE(cameraData2.zoomLevel(),8.0); + cameraData.setZoomLevel(9.0); + QCOMPARE(cameraData2.zoomLevel(),8.0); +} + +void tst_QGeoCameraData::operatorsTest_data(){ + populateCameraData(); +} + +void tst_QGeoCameraData::operatorsTest(){ + QGeoCameraData cameraData; + QGeoCameraData cameraData2; + QVERIFY2(cameraData == cameraData2, "Camera data with default values are not copied correctly"); + + QFETCH(QGeoCoordinate, center); + QFETCH(double, bearing); + QFETCH(double, tilt); + QFETCH(double, roll); + QFETCH(double, zoomLevel); + cameraData.setCenter(center); + cameraData.setBearing(bearing); + cameraData.setTilt(tilt); + cameraData.setRoll(roll); + cameraData.setZoomLevel(zoomLevel); + + QGeoCameraData cameraData3; + cameraData3 = cameraData; + QVERIFY2(cameraData == cameraData3, "Camera data not copied correctly"); + + // test QGeoCameraData pairs where they differ in one field + QGeoCameraData cameraData4; + cameraData4 = cameraData; + cameraData4.setCenter(QGeoCoordinate(10.0,20.0,30.0)); + QVERIFY2(cameraData != cameraData4, "Camera data should be different"); + QGeoCameraData cameraData5; + cameraData5 = cameraData; + cameraData5.setBearing(bearing+1.0); + QVERIFY2(cameraData != cameraData5, "Camera data should be different"); + QGeoCameraData cameraData6; + cameraData6 = cameraData; + cameraData6.setTilt(tilt+0.1); + QVERIFY2(cameraData != cameraData6, "Camera data should be different"); + QGeoCameraData cameraData7; + cameraData7 = cameraData; + cameraData7.setRoll(roll+0.1); + QVERIFY2(cameraData != cameraData7, "Camera data should be different"); + QGeoCameraData cameraData8; + cameraData8 = cameraData; + cameraData8.setZoomLevel(zoomLevel+1.0); + QVERIFY2(cameraData != cameraData8, "Camera data should be different"); +} + +QTEST_APPLESS_MAIN(tst_QGeoCameraData) + +#include "tst_qgeocameradata.moc" diff --git a/tests/auto/qgeocameratiles/qgeocameratiles.pro b/tests/auto/qgeocameratiles/qgeocameratiles.pro new file mode 100644 index 0000000..816e424 --- /dev/null +++ b/tests/auto/qgeocameratiles/qgeocameratiles.pro @@ -0,0 +1,8 @@ +CONFIG += testcase +TARGET = tst_qgeocameratiles + +INCLUDEPATH += ../../../src/location/maps + +SOURCES += tst_qgeocameratiles.cpp + +QT += location positioning-private testlib diff --git a/tests/auto/qgeocameratiles/tst_qgeocameratiles.cpp b/tests/auto/qgeocameratiles/tst_qgeocameratiles.cpp new file mode 100644 index 0000000..8d9cec3 --- /dev/null +++ b/tests/auto/qgeocameratiles/tst_qgeocameratiles.cpp @@ -0,0 +1,1806 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location/maps + +#include "qgeotilespec_p.h" +#include "qgeocameratiles_p.h" +#include "qgeocameradata_p.h" +#include "qgeomaptype_p.h" + +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +struct PositionTestInfo { + QString xyString; + QString zoomString; + QString wString; + QString hString; + double x; + double y; + double zoom; + double w; + double h; +}; + +class tst_QGeoCameraTiles : public QObject +{ + Q_OBJECT + +private: + + void row(const PositionTestInfo &pti, int xOffset, int yOffset, int tileX, int tileY, int tileW, int tileH); + void test_group(const PositionTestInfo &pti, QList &xVals, QList &wVals, QList &yVals, QList &hVals); + +private slots: + + void tilesPlugin(); + void tilesMapType(); + void tilesPositions(); + void tilesPositions_data(); +}; + +void tst_QGeoCameraTiles::row(const PositionTestInfo &pti, int xOffset, int yOffset, int tileX, int tileY, int tileW, int tileH) +{ + double step = 1 / (qPow(2.0, 4.0) * 4); + + QString row = pti.xyString; + row += QStringLiteral(" - "); + row += pti.zoomString; + row += QStringLiteral(" - ("); + row += QString::number(xOffset); + row += QStringLiteral(","); + row += QString::number(yOffset); + row += QStringLiteral(") - "); + row += pti.wString; + row += QStringLiteral(" x "); + row += pti.hString; + + QList xRow; + QList yRow; + + for (int y = 0; y < tileH; ++y) { + for (int x = 0; x < tileW; ++x) { + if (tileX + x < 16) + xRow << tileX + x; + else + xRow << tileX + x - 16; + yRow << tileY + y; + } + } + + QTest::newRow(qPrintable(row)) + << pti.x + step * xOffset << pti.y + step * yOffset + << pti.zoom << pti.w << pti.h + << xRow + << yRow; +} + +void tst_QGeoCameraTiles::test_group(const PositionTestInfo &pti, QList &xVals, QList &wVals, QList &yVals, QList &hVals) +{ + for (int x = 0; x < 5; ++x) { + for (int y = 0; y < 5; ++y) { + row(pti, x, y, xVals.at(x), yVals.at(y), wVals.at(x), hVals.at(y)); + } + } +} + +void tst_QGeoCameraTiles::tilesPlugin() +{ + QGeoCameraData camera; + camera.setZoomLevel(4.0); + camera.setCenter(QGeoCoordinate(0.0, 0.0)); + + QGeoCameraTiles ct; + ct.setTileSize(16); + ct.setCameraData(camera); + ct.setScreenSize(QSize(32, 32)); + ct.setMapType(QGeoMapType(QGeoMapType::StreetMap, "street map", "street map", false, false, 1)); + + QSet tiles1 = ct.createTiles(); + + ct.setPluginString("pluginA"); + + QSet tiles2 = ct.createTiles(); + + typedef QSet::const_iterator iter; + iter i1 = tiles1.constBegin(); + iter end1 = tiles1.constEnd(); + + QSet tiles2_check; + + for (; i1 != end1; ++i1) { + QGeoTileSpec tile = *i1; + tiles2_check.insert(QGeoTileSpec("pluginA", tile.mapId(), tile.zoom(), tile.x(), tile.y())); + } + + QCOMPARE(tiles2, tiles2_check); + + ct.setPluginString("pluginB"); + + QSet tiles3 = ct.createTiles(); + + iter i2 = tiles2.constBegin(); + iter end2 = tiles2.constEnd(); + + QSet tiles3_check; + + for (; i2 != end2; ++i2) { + QGeoTileSpec tile = *i2; + tiles3_check.insert(QGeoTileSpec("pluginB", tile.mapId(), tile.zoom(), tile.x(), tile.y())); + } + + QCOMPARE(tiles3, tiles3_check); +} + +void tst_QGeoCameraTiles::tilesMapType() +{ + QGeoCameraData camera; + camera.setZoomLevel(4.0); + camera.setCenter(QGeoCoordinate(0.0, 0.0)); + + QGeoCameraTiles ct; + ct.setTileSize(16); + ct.setCameraData(camera); + ct.setScreenSize(QSize(32, 32)); + ct.setPluginString("pluginA"); + + QSet tiles1 = ct.createTiles(); + + QGeoMapType mapType1 = QGeoMapType(QGeoMapType::StreetMap, "street map", "street map", false, false, 1); + ct.setMapType(mapType1); + + QSet tiles2 = ct.createTiles(); + + typedef QSet::const_iterator iter; + iter i1 = tiles1.constBegin(); + iter end1 = tiles1.constEnd(); + + QSet tiles2_check; + + for (; i1 != end1; ++i1) { + QGeoTileSpec tile = *i1; + tiles2_check.insert(QGeoTileSpec(tile.plugin(), mapType1.mapId(), tile.zoom(), tile.x(), tile.y())); + } + + QCOMPARE(tiles2, tiles2_check); + + QGeoMapType mapType2 = QGeoMapType(QGeoMapType::StreetMap, "satellite map", "satellite map", false, false, 2); + ct.setMapType(mapType2); + + QSet tiles3 = ct.createTiles(); + + iter i2 = tiles2.constBegin(); + iter end2 = tiles2.constEnd(); + + QSet tiles3_check; + + for (; i2 != end2; ++i2) { + QGeoTileSpec tile = *i2; + tiles3_check.insert(QGeoTileSpec(tile.plugin(), mapType2.mapId(), tile.zoom(), tile.x(), tile.y())); + } + + QCOMPARE(tiles3, tiles3_check); +} + +void tst_QGeoCameraTiles::tilesPositions() +{ + QFETCH(double, mercatorX); + QFETCH(double, mercatorY); + QFETCH(double, zoom); + QFETCH(double, width); + QFETCH(double, height); + QFETCH(QList , tilesX); + QFETCH(QList , tilesY); + + QGeoCameraData camera; + camera.setZoomLevel(zoom); + camera.setCenter(QGeoProjection::mercatorToCoord(QDoubleVector2D(mercatorX, mercatorY))); + + QGeoCameraTiles ct; + ct.setTileSize(16); + ct.setCameraData(camera); + ct.setScreenSize(QSize(qCeil(width), qCeil(height))); + + QSet tiles; + + QVERIFY2(tilesX.size() == tilesY.size(), "tilesX and tilesY have different size"); + + for (int i = 0; i < tilesX.size(); ++i) + tiles.insert(QGeoTileSpec("", 0, static_cast(qFloor(zoom)), tilesX.at(i), tilesY.at(i))); + + QCOMPARE(ct.createTiles(), tiles); +} + +void tst_QGeoCameraTiles::tilesPositions_data() +{ + QTest::addColumn("mercatorX"); + QTest::addColumn("mercatorY"); + QTest::addColumn("zoom"); + QTest::addColumn("width"); + QTest::addColumn("height"); + QTest::addColumn >("tilesX"); + QTest::addColumn >("tilesY"); + + int t = 16; + + PositionTestInfo pti; + + /* + This test sets up various viewports onto a 16x16 map, + and checks which tiles are visible against those that + are expected to be visible. + + The tests are run in 5 groups, corresponding to where + the viewport is centered on the map. + + The groups are named as follows, with the tile in + which the viewport is centered listed in parenthesis: + - mid (8, 8) + - top (8, 0) + - bottom (8, 15) + - left (0, 8) + - right (15, 8) + + For each of these groups a number of tests are run, + which involve modifying various parameters. + + If "t" is the width of a tile, the width and height + of the viewport take on values including: + - (t - 1) + - t + - (t + 1) + - (2t - 1) + - 2t + - (2t + 1) + + The viewport is also offset by fractions of a tile + in both the x and y directions. The offsets are in + quarters of a tile. + + The diagrams below present a justification for our + test expectations. + + The diagrams show variations in viewport width and + x offset into the target tile , although can easily + be taken to be the variations in viewport height and + y offset into the target tile. + + The symbols have the following meanings: + "+" - tile boundaries + "*" - viewport boundary + "T" - boundary of tile the viewport is centered on + "O" - same as "T" but coincident with the viewport boundary + + Whenever the viewport boundary is coincident with a tile boundary, + the tiles on both sides of the boundary are expected to be fetched. + + Those tiles are needed in case we perform bilinear antialiasing, + provide us with at least a pixel width of tolerance for errors in + other parts of the code or the system. There is a decent chance + that some or all of those extra tiles will need to be fetched before + long, so getting them into the cache sooner rather than later is + likely to be beneficial on average. + + The tests are carried out per viewport height / width. + + Lists are created of the first tile along an axis that is expected to + be in the viewport and for the number of tiles along an axis that + are expected to be in the viewport. + + These lists are used for both the x and y axes, although alternative + lists are created for the x axis when the viewport spans the dateline + and for the y axis when the viewport is clipped by the top or bottom of + the map. + + These 5 areas are checked at an integral zoom level to see that the + expected visible tiles match the actual visible tiles generated by + the code under test. + + After that a fractional zoom level is set and the width and height of + the viewport are scaled such that the same tiles should be visible, + and the tests are repeated. + */ + + // TODO + // nail down semantics, modify tests and code to suite + // add corners of the map + + /* + width = t - 1 + */ + + QList mid_tm1x; + QList mid_tm1w; + QList top_tm1x; + QList top_tm1w; + QList bottom_tm1x; + QList bottom_tm1w; + QList left_tm1x; + QList left_tm1w; + QList right_tm1x; + QList right_tm1w; + + pti.w = t - 1; + pti.h = t - 1; + pti.wString = QStringLiteral("(1T - 1)"); + pti.hString = QStringLiteral("(1T - 1)"); + + /* + + offset = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * *+* * + + + + + + * + * + + + + + + + + + + +*+ T T*T T T + + + + + + + + + + + * T * T + + + + + * *T* * T + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 2 tiles + */ + + mid_tm1x << 7; + mid_tm1w << 2; + + top_tm1x << 0; + top_tm1w << 1; + + bottom_tm1x << 14; + bottom_tm1w << 2; + + left_tm1x << 15; + left_tm1w << 2; + + right_tm1x << 14; + right_tm1w << 2; + + /* + offset = 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *+* * * + + + + + + *+ * + + + + + + + + + + + +*T T T*T T + + + + + + + + + + + *T * T + + + + + *T* * * T + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 2 tiles + */ + + mid_tm1x << 7; + mid_tm1w << 2; + + top_tm1x << 0; + top_tm1w << 1; + + bottom_tm1x << 14; + bottom_tm1w << 2; + + left_tm1x << 15; + left_tm1w << 2; + + right_tm1x << 14; + right_tm1w << 2; + + /* + offset = 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +* * * *+ + + + + + +* *+ + + + + + + + + + + + T*T T T*T + + + + + + + + + + + T* *T + + + + + T* * * *T + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T + Covers: 1 tile + */ + + mid_tm1x << 8; + mid_tm1w << 1; + + top_tm1x << 0; + top_tm1w << 1; + + bottom_tm1x << 15; + bottom_tm1w << 1; + + left_tm1x << 0; + left_tm1w << 1; + + right_tm1x << 15; + right_tm1w << 1; + + /* + offset = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * *+* + + + + + + * +* + + + + + + + + + + + T T*T T T*+ + + + + + + + + + + T * T* + + + + + T * * *T* + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T + Covers: 2 tiles + */ + + mid_tm1x << 8; + mid_tm1w << 2; + + top_tm1x << 0; + top_tm1w << 2; + + bottom_tm1x << 15; + bottom_tm1w << 1; + + left_tm1x << 0; + left_tm1w << 2; + + right_tm1x << 15; + right_tm1w << 2; + + /* + offset = 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * *+* * + + + + + + * + * + + + + + + + + + + + T T T*T T +*+ + + + + + + + + + T * T * + + + + + T * *T* * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T + Covers: 2 tiles + */ + + mid_tm1x << 8; + mid_tm1w << 2; + + top_tm1x << 0; + top_tm1w << 2; + + bottom_tm1x << 15; + bottom_tm1w << 1; + + left_tm1x << 0; + left_tm1w << 2; + + right_tm1x << 15; + right_tm1w << 2; + + pti.zoom = 4.0; + pti.zoomString = QStringLiteral("int zoom"); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_tm1x, mid_tm1w, mid_tm1x, mid_tm1w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_tm1x, mid_tm1w, top_tm1x, top_tm1w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_tm1x, mid_tm1w, bottom_tm1x, bottom_tm1w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_tm1x, left_tm1w, mid_tm1x, mid_tm1w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_tm1x, right_tm1w, mid_tm1x, mid_tm1w); + + pti.zoom = 4.5; + pti.zoomString = QStringLiteral("frac zoom"); + pti.w = pti.w * qPow(2.0, 0.5); + pti.h = pti.h * qPow(2.0, 0.5); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_tm1x, mid_tm1w, mid_tm1x, mid_tm1w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_tm1x, mid_tm1w, top_tm1x, top_tm1w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_tm1x, mid_tm1w, bottom_tm1x, bottom_tm1w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_tm1x, left_tm1w, mid_tm1x, mid_tm1w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_tm1x, right_tm1w, mid_tm1x, mid_tm1w); + + /* + width = t + */ + + QList mid_tx; + QList mid_tw; + QList top_tx; + QList top_tw; + QList bottom_tx; + QList bottom_tw; + QList left_tx; + QList left_tw; + QList right_tx; + QList right_tw; + + pti.w = t; + pti.h = t; + pti.wString = QStringLiteral("1T"); + pti.hString = QStringLiteral("1T"); + + /* + + offset = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * + + + + + + * + * + + + + + + + + + + * + T T O T T + + + + + + + + + + + * T * T + + + + + * * O * * T + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 2 tiles + */ + + mid_tx << 7; + mid_tw << 2; + + top_tx << 0; + top_tw << 1; + + bottom_tx << 14; + bottom_tw << 2; + + left_tx << 15; + left_tw << 2; + + right_tx << 14; + right_tw << 2; + + /* + offset = 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * + + + + + + * + * + + + + + + + + + + + * T T T O T + + + + + + + + + + + * T * T + + + + + * O * * * T + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 2 tiles + */ + + mid_tx << 7; + mid_tw << 2; + + top_tx << 0; + top_tw << 1; + + bottom_tx << 14; + bottom_tw << 2; + + left_tx << 15; + left_tw << 2; + + right_tx << 14; + right_tw << 2; + + /* + offset = 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * + + + + + * * + + + + + + + + + + + O T T T O + + + + + + + + + + + O O + + + + + O * * * O + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_tx << 7; + mid_tw << 3; + + top_tx << 0; + top_tw << 2; + + bottom_tx << 14; + bottom_tw << 2; + + left_tx << 15; + left_tw << 3; + + right_tx << 14; + right_tw << 3; + + /* + offset = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * + + + + + + * * + + + + + + + + + + + T O T T T * + + + + + + + + + + T * T * + + + + + T * * * O * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T + Covers: 2 tiles + */ + + mid_tx << 8; + mid_tw << 2; + + top_tx << 0; + top_tw << 2; + + bottom_tx << 15; + bottom_tw << 1; + + left_tx << 0; + left_tw << 2; + + right_tx << 15; + right_tw << 2; + + /* + offset = 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * + + + + + + * + * + + + + + + + + + + + T T O T T + * + + + + + + + + + T * T * + + + + + T * * O * * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T + Covers: 2 tiles + */ + + mid_tx << 8; + mid_tw << 2; + + top_tx << 0; + top_tw << 2; + + bottom_tx << 15; + bottom_tw << 1; + + left_tx << 0; + left_tw << 2; + + right_tx << 15; + right_tw << 2; + + pti.zoom = 4.0; + pti.zoomString = QStringLiteral("int zoom"); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_tx, mid_tw, mid_tx, mid_tw); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_tx, mid_tw, top_tx, top_tw); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_tx, mid_tw, bottom_tx, bottom_tw); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_tx, left_tw, mid_tx, mid_tw); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_tx, right_tw, mid_tx, mid_tw); + + pti.zoom = 4.5; + pti.zoomString = QStringLiteral("frac zoom"); + pti.w = pti.w * qPow(2.0, 0.5); + pti.h = pti.h * qPow(2.0, 0.5); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_tx, mid_tw, mid_tx, mid_tw); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_tx, mid_tw, top_tx, top_tw); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_tx, mid_tw, bottom_tx, bottom_tw); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_tx, left_tw, mid_tx, mid_tw); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_tx, right_tw, mid_tx, mid_tw); + + /* + width = t + 1 + */ + + QList mid_tp1x; + QList mid_tp1w; + QList top_tp1x; + QList top_tp1w; + QList bottom_tp1x; + QList bottom_tp1w; + QList left_tp1x; + QList left_tp1w; + QList right_tp1x; + QList right_tp1w; + + pti.w = t + 1; + pti.h = t + 1; + pti.wString = QStringLiteral("(1T + 1)"); + pti.hString = QStringLiteral("(1T + 1)"); + + /* + + offset = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * *+* * * + + + + + + * + * + + + + + + + + + +*+ + T T T*T T + + + + + + + + + + + * T * T + + + + + * * *T* * * T + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 2 tiles + */ + + mid_tp1x << 7; + mid_tp1w << 2; + + top_tp1x << 0; + top_tp1w << 1; + + bottom_tp1x << 14; + bottom_tp1w << 2; + + left_tp1x << 15; + left_tp1w << 2; + + right_tp1x << 14; + right_tp1w << 2; + + /* + offset = 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * *+* * * *+ + + + + + * + *+ + + + + + + + + + +*+ T T T T*T + + + + + + + + + + + * T *T + + + + + * *T* * * *T + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 2 tiles + */ + + mid_tp1x << 7; + mid_tp1w << 2; + + top_tp1x << 0; + top_tp1w << 1; + + bottom_tp1x << 14; + bottom_tp1w << 2; + + left_tp1x << 15; + left_tp1w << 2; + + right_tp1x << 14; + right_tp1w << 2; + + /* + offset = 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *+* * * *+* + + + + + *+ +* + + + + + + + + + + +*T T T T T*+ + + + + + + + + + + *T T* + + + + + *T* * * *T* + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_tp1x << 7; + mid_tp1w << 3; + + top_tp1x << 0; + top_tp1w << 2; + + bottom_tp1x << 14; + bottom_tp1w << 2; + + left_tp1x << 15; + left_tp1w << 3; + + right_tp1x << 14; + right_tp1w << 3; + + /* + offset = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +* * * *+* * + + + + + +* + * + + + + + + + + + + + T*T T T T +*+ + + + + + + + + + T* T * + + + + + T* * * *T* * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T + Covers: 2 tiles + */ + + mid_tp1x << 8; + mid_tp1w << 2; + + top_tp1x << 0; + top_tp1w << 2; + + bottom_tp1x << 15; + bottom_tp1w << 1; + + left_tp1x << 0; + left_tp1w << 2; + + right_tp1x << 15; + right_tp1w << 2; + + /* + offset = 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * *+* * * + + + + + + * + * + + + + + + + + + + + T T*T T T + +*+ + + + + + + + + T * T * + + + + + T * * *T* * * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T + Covers: 2 tiles + */ + + mid_tp1x << 8; + mid_tp1w << 2; + + top_tp1x << 0; + top_tp1w << 2; + + bottom_tp1x << 15; + bottom_tp1w << 1; + + left_tp1x << 0; + left_tp1w << 2; + + right_tp1x << 15; + right_tp1w << 2; + + pti.zoom = 4.0; + pti.zoomString = QStringLiteral("int zoom"); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_tp1x, mid_tp1w, mid_tp1x, mid_tp1w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_tp1x, mid_tp1w, top_tp1x, top_tp1w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_tp1x, mid_tp1w, bottom_tp1x, bottom_tp1w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_tp1x, left_tp1w, mid_tp1x, mid_tp1w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_tp1x, right_tp1w, mid_tp1x, mid_tp1w); + + pti.zoom = 4.5; + pti.zoomString = QStringLiteral("frac zoom"); + pti.w = pti.w * qPow(2.0, 0.5); + pti.h = pti.h * qPow(2.0, 0.5); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_tp1x, mid_tp1w, mid_tp1x, mid_tp1w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_tp1x, mid_tp1w, top_tp1x, top_tp1w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_tp1x, mid_tp1w, bottom_tp1x, bottom_tp1w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_tp1x, left_tp1w, mid_tp1x, mid_tp1w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_tp1x, right_tp1w, mid_tp1x, mid_tp1w); + + /* + width = 2t - 1 + */ + + QList mid_t2m1x; + QList mid_t2m1w; + QList top_t2m1x; + QList top_t2m1w; + QList bottom_t2m1x; + QList bottom_t2m1w; + QList left_t2m1x; + QList left_t2m1w; + QList right_t2m1x; + QList right_t2m1w; + + pti.w = 2 * t - 1; + pti.h = 2 * t - 1; + pti.wString = QStringLiteral("(2T - 1)"); + pti.hString = QStringLiteral("(2T - 1)"); + + /* + offset = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +* * * *+* * * *+ + + + + +* + *+ + + + + + + + +*+ + + T T T T*T + + + + + + + + + + +* T *T + + + + +* * * *T* * * *T + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 2 tiles + */ + + mid_t2m1x << 7; + mid_t2m1w << 2; + + top_t2m1x << 0; + top_t2m1w << 1; + + bottom_t2m1x << 14; + bottom_t2m1w << 2; + + left_t2m1x << 15; + left_t2m1w << 2; + + right_t2m1x << 14; + right_t2m1w << 2; + + /* + offset = 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * *+* * * *+* + + + + + * + +* + + + + + + + + +*+ + T T T T T*+ + + + + + + + + + + * T T* + + + + + * * *T* * * *T* + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_t2m1x << 7; + mid_t2m1w << 3; + + top_t2m1x << 0; + top_t2m1w << 2; + + bottom_t2m1x << 14; + bottom_t2m1w << 2; + + left_t2m1x << 15; + left_t2m1w << 3; + + right_t2m1x << 14; + right_t2m1w << 3; + + /* + offset = 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * *+* * * *+* * + + + + + * + + * + + + + + + + + + +*+ T T T T T +*+ + + + + + + + + + * T T * + + + + + * *T* * * *T* * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_t2m1x << 7; + mid_t2m1w << 3; + + top_t2m1x << 0; + top_t2m1w << 2; + + bottom_t2m1x << 14; + bottom_t2m1w << 2; + + left_t2m1x << 15; + left_t2m1w << 3; + + right_t2m1x << 14; + right_t2m1w << 3; + + /* + offset = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *+* * * *+* * * + + + + + *+ + * + + + + + + + + + + +*T T T T T + +*+ + + + + + + + + *T T * + + + + + *T* * * *T* * * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_t2m1x << 7; + mid_t2m1w << 3; + + top_t2m1x << 0; + top_t2m1w << 2; + + bottom_t2m1x << 14; + bottom_t2m1w << 2; + + left_t2m1x << 15; + left_t2m1w << 3; + + right_t2m1x << 14; + right_t2m1w << 3; + + /* + offset = 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +* * * *+* * * *+ + + + + +* + *+ + + + + + + + + + + T*T T T T + + +*+ + + + + + + + T* T *+ + + + + T* * * *T* * * *+ + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T + Covers: 2 tiles + */ + + mid_t2m1x << 8; + mid_t2m1w << 2; + + top_t2m1x << 0; + top_t2m1w << 2; + + bottom_t2m1x << 15; + bottom_t2m1w << 1; + + left_t2m1x << 0; + left_t2m1w << 2; + + right_t2m1x << 15; + right_t2m1w << 2; + + pti.zoom = 4.0; + pti.zoomString = QStringLiteral("int zoom"); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_t2m1x, mid_t2m1w, mid_t2m1x, mid_t2m1w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_t2m1x, mid_t2m1w, top_t2m1x, top_t2m1w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_t2m1x, mid_t2m1w, bottom_t2m1x, bottom_t2m1w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_t2m1x, left_t2m1w, mid_t2m1x, mid_t2m1w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_t2m1x, right_t2m1w, mid_t2m1x, mid_t2m1w); + + pti.zoom = 4.5; + pti.zoomString = QStringLiteral("frac zoom"); + pti.w = pti.w * qPow(2.0, 0.5); + pti.h = pti.h * qPow(2.0, 0.5); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_t2m1x, mid_t2m1w, mid_t2m1x, mid_t2m1w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_t2m1x, mid_t2m1w, top_t2m1x, top_t2m1w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_t2m1x, mid_t2m1w, bottom_t2m1x, bottom_t2m1w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_t2m1x, left_t2m1w, mid_t2m1x, mid_t2m1w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_t2m1x, right_t2m1w, mid_t2m1x, mid_t2m1w); + + /* + width = 2t + */ + + + QList mid_t2x; + QList mid_t2w; + QList top_t2x; + QList top_t2w; + QList bottom_t2x; + QList bottom_t2w; + QList left_t2x; + QList left_t2w; + QList right_t2x; + QList right_t2w; + + pti.w = 2 * t; + pti.h = 2 * t; + pti.wString = QStringLiteral("2T"); + pti.hString = QStringLiteral("2T"); + + /* + + offset = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * * * * * + + + + * + * + + + + + + + * + + + T T T T O + + + + + + + + + + * T O + + + + * * * * O * * * O + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 2 + Covers: 4 tiles + */ + + mid_t2x << 6; + mid_t2w << 4; + + top_t2x << 0; + top_t2w << 2; + + bottom_t2x << 13; + bottom_t2w << 3; + + left_t2x << 14; + left_t2w << 4; + + right_t2x << 13; + right_t2w << 4; + + /* + offset = 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * * * * * + + + + + * + * + + + + + + + + * + + T T T T T * + + + + + + + + + + * T T * + + + + + * * * O * * * O * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_t2x << 7; + mid_t2w << 3; + + top_t2x << 0; + top_t2w << 2; + + bottom_t2x << 14; + bottom_t2w << 2; + + left_t2x << 15; + left_t2w << 3; + + right_t2x << 14; + right_t2w << 3; + + /* + offset = 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * * * * * + + + + + * + * + + + + + + + + + * + T T T T T + * + + + + + + + + + * T T * + + + + + * * O * * * O * * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_t2x << 7; + mid_t2w << 3; + + top_t2x << 0; + top_t2w << 2; + + bottom_t2x << 14; + bottom_t2w << 2; + + left_t2x << 15; + left_t2w << 3; + + right_t2x << 14; + right_t2w << 3; + + /* + offset = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * * * * * + + + + + * + * + + + + + + + + + + * T T T T T + + * + + + + + + + + * T T * + + + + + * O * * * O * * * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_t2x << 7; + mid_t2w << 3; + + top_t2x << 0; + top_t2w << 2; + + bottom_t2x << 14; + bottom_t2w << 2; + + left_t2x << 15; + left_t2w << 3; + + right_t2x << 14; + right_t2w << 3; + + /* + offset = 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * * * * * * * * + + + + * * + + + + + + + + + + O T T T T + + + * + + + + + + + O T * + + + + O * * * O * * * * + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 4 tiles + */ + + mid_t2x << 7; + mid_t2w << 4; + + top_t2x << 0; + top_t2w << 3; + + bottom_t2x << 14; + bottom_t2w << 2; + + left_t2x << 15; + left_t2w << 4; + + right_t2x << 14; + right_t2w << 4; + + pti.zoom = 4.0; + pti.zoomString = QStringLiteral("int zoom"); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_t2x, mid_t2w, mid_t2x, mid_t2w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_t2x, mid_t2w, top_t2x, top_t2w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_t2x, mid_t2w, bottom_t2x, bottom_t2w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_t2x, left_t2w, mid_t2x, mid_t2w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_t2x, right_t2w, mid_t2x, mid_t2w); + + pti.zoom = 4.5; + pti.zoomString = QStringLiteral("frac zoom"); + pti.w = pti.w * qPow(2.0, 0.5); + pti.h = pti.h * qPow(2.0, 0.5); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_t2x, mid_t2w, mid_t2x, mid_t2w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_t2x, mid_t2w, top_t2x, top_t2w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_t2x, mid_t2w, bottom_t2x, bottom_t2w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_t2x, left_t2w, mid_t2x, mid_t2w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_t2x, right_t2w, mid_t2x, mid_t2w); + + /* + width = 2t + 1 + */ + + QList mid_t2p1x; + QList mid_t2p1w; + QList top_t2p1x; + QList top_t2p1w; + QList bottom_t2p1x; + QList bottom_t2p1w; + QList left_t2p1x; + QList left_t2p1w; + QList right_t2p1x; + QList right_t2p1w; + + pti.w = 2 * t + 1; + pti.h = 2 * t + 1; + pti.wString = QStringLiteral("(2T + 1)"); + pti.hString = QStringLiteral("(2T + 1)"); + + /* + + offset = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *+* * * *+* * * *+* + + + + *+ + +* + + + + + + +*+ + + + T T T T T*+ + + + + + + + + + *+ T T* + + + + *+* * * *T* * * *T* + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 2 + Covers: 4 tiles + */ + + mid_t2p1x << 6; + mid_t2p1w << 4; + + top_t2p1x << 0; + top_t2p1w << 2; + + bottom_t2p1x << 13; + bottom_t2p1w << 3; + + left_t2p1x << 14; + left_t2p1w << 4; + + right_t2p1x << 13; + right_t2p1w << 4; + + /* + offset = 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +* * * *+* * * *+* * + + + + +* + + * + + + + + + + +*+ + + T T T T T +*+ + + + + + + + + +* T T * + + + + +* * * *T* * * *T* * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_t2p1x << 7; + mid_t2p1w << 3; + + top_t2p1x << 0; + top_t2p1w << 2; + + bottom_t2p1x << 14; + bottom_t2p1w << 2; + + left_t2p1x << 15; + left_t2p1w << 3; + + right_t2p1x << 14; + right_t2p1w << 3; + + /* + offset = 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * * *+* * * *+* * * + + + + + * + + * + + + + + + + + +*+ + T T T T T + +*+ + + + + + + + + * T T * + + + + + * * *T* * * *T* * * + + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_t2p1x << 7; + mid_t2p1w << 3; + + top_t2p1x << 0; + top_t2p1w << 2; + + bottom_t2p1x << 14; + bottom_t2p1w << 2; + + left_t2p1x << 15; + left_t2p1w << 3; + + right_t2p1x << 14; + right_t2p1w << 3; + + /* + offset = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * *+* * * *+* * * *+ + + + + * + + *+ + + + + + + + + +*+ T T T T T + + +*+ + + + + + + + * T T *+ + + + + * *T* * * *T* * * *+ + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 3 tiles + */ + + mid_t2p1x << 7; + mid_t2p1w << 3; + + top_t2p1x << 0; + top_t2p1w << 2; + + bottom_t2p1x << 14; + bottom_t2p1w << 2; + + left_t2p1x << 15; + left_t2p1w << 3; + + right_t2p1x << 14; + right_t2p1w << 3; + + /* + offset = 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *+* * * *+* * * *+* + + + + *+ + +* + + + + + + + + + +*T T T T T + + + +*+ + + + + + + *T T +* + + + + *T* * * *T* * * *+* + + + + T T + + + + + + + + + + + T T T T T + + + + + + + + + + Starts at: T - 1 + Covers: 4 tiles + */ + + mid_t2p1x << 7; + mid_t2p1w << 4; + + top_t2p1x << 0; + top_t2p1w << 3; + + bottom_t2p1x << 14; + bottom_t2p1w << 2; + + left_t2p1x << 15; + left_t2p1w << 4; + + right_t2p1x << 14; + right_t2p1w << 4; + + pti.zoom = 4.0; + pti.zoomString = QStringLiteral("int zoom"); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_t2p1x, mid_t2p1w, mid_t2p1x, mid_t2p1w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_t2p1x, mid_t2p1w, top_t2p1x, top_t2p1w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_t2p1x, mid_t2p1w, bottom_t2p1x, bottom_t2p1w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_t2p1x, left_t2p1w, mid_t2p1x, mid_t2p1w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_t2p1x, right_t2p1w, mid_t2p1x, mid_t2p1w); + + pti.zoom = 4.5; + pti.zoomString = QStringLiteral("frac zoom"); + pti.w = pti.w * qPow(2.0, 0.5); + pti.h = pti.h * qPow(2.0, 0.5); + + pti.x = 0.5; + pti.y = 0.5; + pti.xyString = QStringLiteral("middle"); + + test_group(pti, mid_t2p1x, mid_t2p1w, mid_t2p1x, mid_t2p1w); + + pti.x = 0.5; + pti.y = 0.0; + pti.xyString = QStringLiteral("top"); + + test_group(pti, mid_t2p1x, mid_t2p1w, top_t2p1x, top_t2p1w); + + pti.x = 0.5; + pti.y = 15.0 / 16.0; + pti.xyString = QStringLiteral("bottom"); + + test_group(pti, mid_t2p1x, mid_t2p1w, bottom_t2p1x, bottom_t2p1w); + + pti.x = 0.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("left"); + + test_group(pti, left_t2p1x, left_t2p1w, mid_t2p1x, mid_t2p1w); + + pti.x = 15.0 / 16.0; + pti.y = 0.5; + pti.xyString = QStringLiteral("right"); + + test_group(pti, right_t2p1x, right_t2p1w, mid_t2p1x, mid_t2p1w); +} + +QTEST_GUILESS_MAIN(tst_QGeoCameraTiles) +#include "tst_qgeocameratiles.moc" diff --git a/tests/auto/qgeocircle/qgeocircle.pro b/tests/auto/qgeocircle/qgeocircle.pro new file mode 100644 index 0000000..eb56a7f --- /dev/null +++ b/tests/auto/qgeocircle/qgeocircle.pro @@ -0,0 +1,8 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qgeocircle + +SOURCES += \ + tst_qgeocircle.cpp + +QT += positioning testlib diff --git a/tests/auto/qgeocircle/tst_qgeocircle.cpp b/tests/auto/qgeocircle/tst_qgeocircle.cpp new file mode 100644 index 0000000..01fbed6 --- /dev/null +++ b/tests/auto/qgeocircle/tst_qgeocircle.cpp @@ -0,0 +1,417 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QGeoCircle : public QObject +{ + Q_OBJECT + +private slots: + void defaultConstructor(); + void centerRadiusConstructor(); + void assignment(); + + void comparison(); + void type(); + + void radius(); + void center(); + + void translate_data(); + void translate(); + + void valid_data(); + void valid(); + + void empty_data(); + void empty(); + + void contains_data(); + void contains(); + + void extendShape(); + void extendShape_data(); + + void areaComparison(); + void areaComparison_data(); + + void boxComparison(); + void boxComparison_data(); +}; + +void tst_QGeoCircle::defaultConstructor() +{ + QGeoCircle c; + QVERIFY(!c.center().isValid()); + QCOMPARE(c.radius(), qreal(-1.0)); +} + +void tst_QGeoCircle::centerRadiusConstructor() +{ + QGeoCircle c(QGeoCoordinate(1,1), qreal(50.0)); + QCOMPARE(c.center(), QGeoCoordinate(1,1)); + QCOMPARE(c.radius(), qreal(50.0)); +} + +void tst_QGeoCircle::assignment() +{ + QGeoCircle c1 = QGeoCircle(QGeoCoordinate(10.0, 0.0), 20.0); + QGeoCircle c2 = QGeoCircle(QGeoCoordinate(20.0, 0.0), 30.0); + + QVERIFY(c1 != c2); + + c2 = c1; + QCOMPARE(c2.center(), QGeoCoordinate(10.0, 0.0)); + QCOMPARE(c2.radius(), 20.0); + QCOMPARE(c1, c2); + + c2.setCenter(QGeoCoordinate(30.0, 0.0)); + c2.setRadius(15.0); + QCOMPARE(c1.center(), QGeoCoordinate(10.0, 0.0)); + QCOMPARE(c1.radius(), 20.0); + + // Assign c1 to an area + QGeoShape area = c1; + QCOMPARE(area.type(), c1.type()); + QVERIFY(area == c1); + + // Assign the area back to a bounding circle + QGeoCircle ca = area; + QCOMPARE(ca.center(), c1.center()); + QCOMPARE(ca.radius(), c1.radius()); + + // Check that the copy is not modified when modifying the original. + c1.setCenter(QGeoCoordinate(15.0, 15.0)); + QVERIFY(ca.center() != c1.center()); + QVERIFY(ca != c1); +} + +void tst_QGeoCircle::comparison() +{ + QGeoCircle c1(QGeoCoordinate(1,1), qreal(50.0)); + QGeoCircle c2(QGeoCoordinate(1,1), qreal(50.0)); + QGeoCircle c3(QGeoCoordinate(1,1), qreal(35.0)); + QGeoCircle c4(QGeoCoordinate(1,2), qreal(50.0)); + + QVERIFY(c1 == c2); + QVERIFY(!(c1 != c2)); + + QVERIFY(!(c1 == c3)); + QVERIFY(c1 != c3); + + QVERIFY(!(c1 == c4)); + QVERIFY(c1 != c4); + + QVERIFY(!(c2 == c3)); + QVERIFY(c2 != c3); + + QGeoRectangle b1(QGeoCoordinate(20,20),QGeoCoordinate(10,30)); + QVERIFY(!(c1 == b1)); + QVERIFY(c1 != b1); + + QGeoShape *c2Ptr = &c2; + QVERIFY(c1 == *c2Ptr); + QVERIFY(!(c1 != *c2Ptr)); + + QGeoShape *c3Ptr = &c3; + QVERIFY(!(c1 == *c3Ptr)); + QVERIFY(c1 != *c3Ptr); +} + +void tst_QGeoCircle::type() +{ + QGeoCircle c; + QCOMPARE(c.type(), QGeoShape::CircleType); +} + +void tst_QGeoCircle::radius() +{ + QGeoCircle c; + c.setRadius(1.0); + QCOMPARE(c.radius(), qreal(1.0)); + c.setRadius(5.0); + QCOMPARE(c.radius(), qreal(5.0)); +} + +void tst_QGeoCircle::center() +{ + QGeoCircle c; + c.setCenter(QGeoCoordinate(1,1)); + QCOMPARE(c.center(), QGeoCoordinate(1,1)); + + QGeoShape shape = c; + QCOMPARE(shape.center(), c.center()); + + c.setCenter(QGeoCoordinate(5,10)); + QCOMPARE(c.center(), QGeoCoordinate(5,10)); +} + +void tst_QGeoCircle::translate_data() +{ + QTest::addColumn("center"); + QTest::addColumn("radius"); + QTest::addColumn("lat"); + QTest::addColumn("lon"); + QTest::addColumn("newCenter"); + + QTest::newRow("from 0,0") << QGeoCoordinate(0,0) << qreal(10.0) << + 5.0 << 5.0 << QGeoCoordinate(5.0, 5.0); + QTest::newRow("across 0,0") << QGeoCoordinate(-2, -2) << qreal(20.0) << + 5.0 << 5.0 << QGeoCoordinate(3.0, 3.0); + QTest::newRow("backwards across 0,0") << QGeoCoordinate(5,5) << qreal(50.0) + << -13.0 << 5.0 + << QGeoCoordinate(-8.0, 10.0); +} + +void tst_QGeoCircle::translate() +{ + QFETCH(QGeoCoordinate, center); + QFETCH(qreal, radius); + QFETCH(double, lat); + QFETCH(double, lon); + QFETCH(QGeoCoordinate, newCenter); + + QGeoCircle c(center, radius); + QGeoCircle d = c; + + c.translate(lat, lon); + + QCOMPARE(c.radius(), radius); + QCOMPARE(c.center(), newCenter); + + c = d.translated(lat, lon); + d.setRadius(1.0); + + QCOMPARE(c.radius(), radius); + QCOMPARE(d.center(), center); + QCOMPARE(c.center(), newCenter); +} + +void tst_QGeoCircle::valid_data() +{ + QTest::addColumn("center"); + QTest::addColumn("radius"); + QTest::addColumn("valid"); + + QTest::newRow("default") << QGeoCoordinate() << qreal(-1.0) << false; + QTest::newRow("empty coord") << QGeoCoordinate() << qreal(5.0) << false; + QTest::newRow("NaN coord") << QGeoCoordinate(500, 500) << qreal(5.0) << false; + QTest::newRow("bad radius") << QGeoCoordinate(10, 10) << qreal(-5.0) << false; + QTest::newRow("NaN radius") << QGeoCoordinate(10, 10) << qreal(qQNaN()) << false; + QTest::newRow("zero radius") << QGeoCoordinate(10, 10) << qreal(0.0) << true; + QTest::newRow("good") << QGeoCoordinate(10, 10) << qreal(5.0) << true; +} + +void tst_QGeoCircle::valid() +{ + QFETCH(QGeoCoordinate, center); + QFETCH(qreal, radius); + QFETCH(bool, valid); + + QGeoCircle c(center, radius); + QCOMPARE(c.isValid(), valid); + + QGeoShape area = c; + QCOMPARE(c.isValid(), valid); +} + +void tst_QGeoCircle::empty_data() +{ + QTest::addColumn("center"); + QTest::addColumn("radius"); + QTest::addColumn("empty"); + + QTest::newRow("default") << QGeoCoordinate() << qreal(-1.0) << true; + QTest::newRow("empty coord") << QGeoCoordinate() << qreal(5.0) << true; + QTest::newRow("NaN coord") << QGeoCoordinate(500, 500) << qreal(5.0) << true; + QTest::newRow("bad radius") << QGeoCoordinate(10, 10) << qreal(-5.0) << true; + QTest::newRow("NaN radius") << QGeoCoordinate(10, 10) << qreal(qQNaN()) << true; + QTest::newRow("zero radius") << QGeoCoordinate(10, 10) << qreal(0.0) << true; + QTest::newRow("good") << QGeoCoordinate(10, 10) << qreal(5.0) << false; +} + +void tst_QGeoCircle::empty() +{ + QFETCH(QGeoCoordinate, center); + QFETCH(qreal, radius); + QFETCH(bool, empty); + + QGeoCircle c(center, radius); + QCOMPARE(c.isEmpty(), empty); + + QGeoShape area = c; + QCOMPARE(area.isEmpty(), empty); +} + +void tst_QGeoCircle::contains_data() +{ + QTest::addColumn("center"); + QTest::addColumn("radius"); + QTest::addColumn("probe"); + QTest::addColumn("result"); + + QTest::newRow("own centre") << QGeoCoordinate(1,1) << qreal(100.0) << + QGeoCoordinate(1,1) << true; + QTest::newRow("over the hills") << QGeoCoordinate(1,1) << qreal(100.0) << + QGeoCoordinate(30, 40) << false; + QTest::newRow("at 0.5*radius") << QGeoCoordinate(1,1) << qreal(100.0) << + QGeoCoordinate(1.00015374,1.00015274) << true; + QTest::newRow("at 0.99*radius") << QGeoCoordinate(1,1) << qreal(100.0) << + QGeoCoordinate(1.00077538, 0.99955527) << true; + QTest::newRow("at 1.01*radius") << QGeoCoordinate(1,1) << qreal(100.0) << + QGeoCoordinate(1.00071413, 0.99943423) << false; +} + +void tst_QGeoCircle::contains() +{ + QFETCH(QGeoCoordinate, center); + QFETCH(qreal, radius); + QFETCH(QGeoCoordinate, probe); + QFETCH(bool, result); + + QGeoCircle c(center, radius); + QCOMPARE(c.contains(probe), result); + + QGeoShape area = c; + QCOMPARE(area.contains(probe), result); +} + +void tst_QGeoCircle::extendShape() +{ + QFETCH(QGeoCircle, circle); + QFETCH(QGeoCoordinate, coord); + QFETCH(bool, containsFirst); + QFETCH(bool, containsExtended); + + QCOMPARE(circle.contains(coord), containsFirst); + circle.extendShape(coord); + QCOMPARE(circle.contains(coord), containsExtended); + +} + +void tst_QGeoCircle::extendShape_data() +{ + QTest::addColumn("circle"); + QTest::addColumn("coord"); + QTest::addColumn("containsFirst"); + QTest::addColumn("containsExtended"); + + QGeoCoordinate co1(20.0, 20.0); + + QTest::newRow("own center") + << QGeoCircle(co1, 100) + << QGeoCoordinate(20.0, 20.0) + << true + << true; + QTest::newRow("inside") + << QGeoCircle(co1, 100) + << QGeoCoordinate(20.0001, 20.0001) + << true + << true; + QTest::newRow("far away") + << QGeoCircle(co1, 100) + << QGeoCoordinate(50.0001, 50.0001) + << false + << true; + QTest::newRow("invalid circle") + << QGeoCircle() + << QGeoCoordinate(20.0, 20.0) + << false + << false; + QTest::newRow("invalid coordinate") + << QGeoCircle(co1, 100) + << QGeoCoordinate(99.0, 190.0) + << false + << false; +} + +void tst_QGeoCircle::areaComparison_data() +{ + QTest::addColumn("area"); + QTest::addColumn("circle"); + QTest::addColumn("equal"); + + QGeoCircle c1(QGeoCoordinate(10.0, 0.0), 10.0); + QGeoCircle c2(QGeoCoordinate(20.0, 10.0), 20.0); + QGeoRectangle b(QGeoCoordinate(10.0, 0.0), QGeoCoordinate(0.0, 10.0)); + + QTest::newRow("default constructed") << QGeoShape() << QGeoCircle() << false; + QTest::newRow("c1 c1") << QGeoShape(c1) << c1 << true; + QTest::newRow("c1 c2") << QGeoShape(c1) << c2 << false; + QTest::newRow("c2 c1") << QGeoShape(c2) << c1 << false; + QTest::newRow("c2 c2") << QGeoShape(c2) << c2 << true; + QTest::newRow("b c1") << QGeoShape(b) << c1 << false; +} + +void tst_QGeoCircle::areaComparison() +{ + QFETCH(QGeoShape, area); + QFETCH(QGeoCircle, circle); + QFETCH(bool, equal); + + QCOMPARE((area == circle), equal); + QCOMPARE((area != circle), !equal); + + QCOMPARE((circle == area), equal); + QCOMPARE((circle != area), !equal); +} + +void tst_QGeoCircle::boxComparison_data() +{ + QTest::addColumn("box"); + QTest::addColumn("circle"); + QTest::addColumn("equal"); + + QGeoCircle c(QGeoCoordinate(10.0, 0.0), 10.0); + QGeoRectangle b(QGeoCoordinate(10.0, 0.0), QGeoCoordinate(0.0, 10.0)); + + QTest::newRow("default constructed") << QGeoRectangle() << QGeoCircle() << false; + QTest::newRow("b c") << b << c << false; +} + +void tst_QGeoCircle::boxComparison() +{ + QFETCH(QGeoRectangle, box); + QFETCH(QGeoCircle, circle); + QFETCH(bool, equal); + + QCOMPARE((box == circle), equal); + QCOMPARE((box != circle), !equal); + + QCOMPARE((circle == box), equal); + QCOMPARE((circle != box), !equal); +} + +QTEST_MAIN(tst_QGeoCircle) +#include "tst_qgeocircle.moc" diff --git a/tests/auto/qgeocodereply/qgeocodereply.pro b/tests/auto/qgeocodereply/qgeocodereply.pro new file mode 100644 index 0000000..dea3d4c --- /dev/null +++ b/tests/auto/qgeocodereply/qgeocodereply.pro @@ -0,0 +1,10 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qgeocodereply + +HEADERS += ../utils/qlocationtestutils_p.h \ + tst_qgeocodereply.h +SOURCES += tst_qgeocodereply.cpp \ + ../utils/qlocationtestutils.cpp + +QT += location testlib diff --git a/tests/auto/qgeocodereply/tst_qgeocodereply.cpp b/tests/auto/qgeocodereply/tst_qgeocodereply.cpp new file mode 100644 index 0000000..bd47666 --- /dev/null +++ b/tests/auto/qgeocodereply/tst_qgeocodereply.cpp @@ -0,0 +1,275 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tst_qgeocodereply.h" + +QT_USE_NAMESPACE + +void tst_QGeoCodeReply::initTestCase() +{ + + reply = new SubGeocodeReply(); +} + +void tst_QGeoCodeReply::cleanupTestCase() +{ + + delete reply; + delete qgeolocation; +} + +void tst_QGeoCodeReply::init() +{ + qRegisterMetaType(); + signalerror = new QSignalSpy(reply, SIGNAL(error(QGeoCodeReply::Error,QString))); + signalfinished = new QSignalSpy(reply, SIGNAL(finished())); +} + +void tst_QGeoCodeReply::cleanup() +{ + delete signalerror; + delete signalfinished; +} + +void tst_QGeoCodeReply::constructor() +{ + QVERIFY(!reply->isFinished()); + + QCOMPARE(reply->limit(),-1); + QCOMPARE(reply->offset(),0); + QCOMPARE(reply->error(),QGeoCodeReply::NoError); + + QVERIFY( signalerror->isValid() ); + QVERIFY( signalfinished->isValid() ); + + QCOMPARE(signalerror->count(),0); + QCOMPARE(signalfinished->count(),0); +} + +void tst_QGeoCodeReply::constructor_error() +{ + QFETCH(QGeoCodeReply::Error,error); + QFETCH(QString,msg); + + QVERIFY( signalerror->isValid() ); + QVERIFY( signalfinished->isValid() ); + + QGeoCodeReply *qgeocodereplycopy = new QGeoCodeReply (error,msg,0); + + QCOMPARE(signalerror->count(),0); + QCOMPARE(signalfinished->count(),0); + + QCOMPARE (qgeocodereplycopy->error(),error); + QCOMPARE (qgeocodereplycopy->errorString(),msg); + + delete qgeocodereplycopy; +} + +void tst_QGeoCodeReply::constructor_error_data() +{ + QTest::addColumn("error"); + QTest::addColumn("msg"); + + QTest::newRow("error1") << QGeoCodeReply::NoError << "No error."; + QTest::newRow("error2") << QGeoCodeReply::EngineNotSetError << "Engine Not Set Error."; + QTest::newRow("error3") << QGeoCodeReply::CommunicationError << "Communication Error."; + QTest::newRow("error4") << QGeoCodeReply::ParseError << "Parse Error."; + QTest::newRow("error5") << QGeoCodeReply::UnsupportedOptionError << "Unsupported Option Error."; + QTest::newRow("error6") << QGeoCodeReply::UnknownError << "Unknown Error."; + +} + +void tst_QGeoCodeReply::destructor() +{ + QGeoCodeReply *qgeocodereplycopy; + QFETCH(QGeoCodeReply::Error,error); + QFETCH(QString,msg); + + qgeocodereplycopy = new QGeoCodeReply (error,msg,0); + delete qgeocodereplycopy; +} + +void tst_QGeoCodeReply::destructor_data() +{ + tst_QGeoCodeReply::constructor_error_data(); +} + +void tst_QGeoCodeReply::abort() +{ + QVERIFY( signalerror->isValid() ); + QVERIFY( signalfinished->isValid() ); + + QCOMPARE(signalerror->count(),0); + QCOMPARE (signalfinished->count(),0); + + reply->callSetFinished(true); + reply->abort(); + + QCOMPARE(signalerror->count(),0); + QCOMPARE (signalfinished->count(),1); + + reply->abort(); + reply->callSetFinished(false); + reply->abort(); + + QCOMPARE(signalerror->count(),0); + QCOMPARE (signalfinished->count(),2); +} + +void tst_QGeoCodeReply::error() +{ + QFETCH(QGeoCodeReply::Error,error); + QFETCH(QString,msg); + + QVERIFY( signalerror->isValid() ); + QVERIFY( signalfinished->isValid() ); + QCOMPARE(signalerror->count(),0); + + reply->callSetError(error,msg); + + QCOMPARE(signalerror->count(),1); + QCOMPARE(signalfinished->count(),1); + QCOMPARE(reply->errorString(),msg); + QCOMPARE(reply->error(),error); + + +} + +void tst_QGeoCodeReply::error_data() +{ + QTest::addColumn("error"); + QTest::addColumn("msg"); + + QTest::newRow("error1") << QGeoCodeReply::NoError << "No error."; + QTest::newRow("error2") << QGeoCodeReply::EngineNotSetError << "Engine Not Set Error."; + QTest::newRow("error3") << QGeoCodeReply::CommunicationError << "Communication Error."; + QTest::newRow("error4") << QGeoCodeReply::ParseError << "Parse Error."; + QTest::newRow("error5") << QGeoCodeReply::UnsupportedOptionError << "Unsupported Option Error."; + QTest::newRow("error6") << QGeoCodeReply::UnknownError << "Unknown Error."; +} + +void tst_QGeoCodeReply::finished() +{ + QVERIFY( signalerror->isValid() ); + QVERIFY( signalfinished->isValid() ); + + QCOMPARE(signalerror->count(),0); + QCOMPARE (signalfinished->count(),0); + + reply->callSetFinished(true); + QVERIFY(reply->isFinished()); + QCOMPARE(signalerror->count(),0); + QCOMPARE (signalfinished->count(),1); + + reply->callSetFinished(false); + + QVERIFY(!reply->isFinished()); + QCOMPARE(signalerror->count(),0); + QCOMPARE (signalfinished->count(),1); + + reply->callSetFinished(true); + + QVERIFY(reply->isFinished()); + QCOMPARE(signalerror->count(),0); + QCOMPARE (signalfinished->count(),2); +} + + + +void tst_QGeoCodeReply::limit() +{ + int limit =30; + reply->callSetLimit(limit); + QCOMPARE(reply->limit(),limit); +} + +void tst_QGeoCodeReply::offset() +{ + int offset = 2; + reply->callSetOffset(offset); + QCOMPARE(reply->offset(),offset); +} + +void tst_QGeoCodeReply::locations() +{ + QList geolocations; + geolocations = reply->locations(); + + QCOMPARE(geolocations.size(),0); + + QGeoAddress *qgeoaddress = new QGeoAddress (); + qgeoaddress->setCity("Berlin"); + + QGeoCoordinate *qgeocoordinate = new QGeoCoordinate (12.12 , 54.43); + + qgeolocation = new QGeoLocation (); + qgeolocation->setAddress(*qgeoaddress); + qgeolocation->setCoordinate(*qgeocoordinate); + + reply->callAddLocation(*qgeolocation); + + geolocations = reply->locations(); + QCOMPARE(geolocations.size(),1); + QCOMPARE(geolocations.at(0),*qgeolocation); + + QGeoLocation *qgeolocationcopy = new QGeoLocation (*qgeolocation); + + QList qgeolocations; + qgeolocations.append(*qgeolocation); + qgeolocations.append(*qgeolocationcopy); + + reply->callSetLocations(qgeolocations); + + geolocations = reply->locations(); + + QCOMPARE(geolocations.size(),qgeolocations.size()); + for (int i = 0 ; i < geolocations.size(); i++) + { + QCOMPARE(geolocations.at(i),qgeolocations.at(i)); + } + + delete qgeoaddress; + delete qgeocoordinate; + delete qgeolocationcopy; +} + +void tst_QGeoCodeReply::viewport() +{ + QGeoCoordinate *qgeocoordinate = new QGeoCoordinate (12.12 , 54.43); + + qgeoboundingbox = new QGeoRectangle (*qgeocoordinate, 0.5 , 0.5); + + reply->callSetViewport(*qgeoboundingbox); + + QCOMPARE (reply->viewport(), static_cast(*qgeoboundingbox)); + + delete qgeocoordinate; + delete qgeoboundingbox; +} + +QTEST_MAIN(tst_QGeoCodeReply); diff --git a/tests/auto/qgeocodereply/tst_qgeocodereply.h b/tests/auto/qgeocodereply/tst_qgeocodereply.h new file mode 100644 index 0000000..3db16da --- /dev/null +++ b/tests/auto/qgeocodereply/tst_qgeocodereply.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_QGEOCODEREPLY_H +#define TST_QGEOCODEREPLY_H + +#include +#include +#include +#include +#include + +#include "../utils/qlocationtestutils_p.h" + +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE +class SubGeocodeReply : public QGeoCodeReply +{ + Q_OBJECT +public: + SubGeocodeReply() : QGeoCodeReply() {} + + void callAddLocation ( const QGeoLocation & location ) {addLocation(location);} + void callSetError ( Error error, const QString & errorString ) {setError(error, errorString);} + void callSetFinished ( bool finished ) {setFinished(finished);} + void callSetLimit ( int limit ) {setLimit(limit);} + void callSetOffset ( int offset ) {setOffset(offset);} + void callSetLocations ( const QList & locations ) {setLocations(locations);} + void callSetViewport ( const QGeoShape &viewport ) {setViewport(viewport);} + +}; + +class tst_QGeoCodeReply :public QObject +{ + Q_OBJECT + +public slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + //Start Unit Test for QGeoCodeReply +private slots: + void constructor(); + void constructor_error(); + void constructor_error_data(); + void destructor(); + void destructor_data(); + void abort(); + void error(); + void error_data(); + void finished(); + void limit(); + void offset(); + void locations(); + void viewport(); + + //End Unit Test for QGeoCodeReply + + + +private: + QSignalSpy *signalerror; + QSignalSpy *signalfinished; + SubGeocodeReply* reply; + QGeoLocation *qgeolocation; + QGeoRectangle *qgeoboundingbox; +}; + +Q_DECLARE_METATYPE(QList); +Q_DECLARE_METATYPE(QGeoCodeReply::Error); + +#endif // TST_QGEOCODEREPLY_H + diff --git a/tests/auto/qgeocodingmanager/qgeocodingmanager.pro b/tests/auto/qgeocodingmanager/qgeocodingmanager.pro new file mode 100644 index 0000000..373f1ff --- /dev/null +++ b/tests/auto/qgeocodingmanager/qgeocodingmanager.pro @@ -0,0 +1,12 @@ +CONFIG += testcase +TARGET = tst_qgeocodingmanager + +HEADERS += ../utils/qlocationtestutils_p.h \ + tst_qgeocodingmanager.h + +SOURCES += tst_qgeocodingmanager.cpp \ + ../utils/qlocationtestutils.cpp + +CONFIG -= app_bundle + +QT += location testlib diff --git a/tests/auto/qgeocodingmanager/tst_qgeocodingmanager.cpp b/tests/auto/qgeocodingmanager/tst_qgeocodingmanager.cpp new file mode 100644 index 0000000..581b3c4 --- /dev/null +++ b/tests/auto/qgeocodingmanager/tst_qgeocodingmanager.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include "tst_qgeocodingmanager.h" + +QT_USE_NAMESPACE + + +void tst_QGeoCodingManager::initTestCase() +{ + /* + * Set custom path since CI doesn't install test plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif + tst_QGeoCodingManager::loadGeocodingManager(); +} + +void tst_QGeoCodingManager::cleanupTestCase() +{ + delete qgeoserviceprovider; +} + +void tst_QGeoCodingManager::init() +{ + qRegisterMetaType(); + qRegisterMetaType(); + + signalerror = new QSignalSpy(qgeocodingmanager, SIGNAL(error(QGeoCodeReply*,QGeoCodeReply::Error,QString))); + signalfinished = new QSignalSpy(qgeocodingmanager, SIGNAL(finished(QGeoCodeReply*))); + QVERIFY( signalerror->isValid() ); + QVERIFY( signalfinished->isValid() ); +} + +void tst_QGeoCodingManager::cleanup() +{ + delete signalerror; + delete signalfinished; +} + +void tst_QGeoCodingManager::loadGeocodingManager() +{ + QStringList providers = QGeoServiceProvider::availableServiceProviders(); + QVERIFY(providers.contains("geocode.test.plugin")); + + qgeoserviceprovider = new QGeoServiceProvider("geocode.test.plugin"); + QVERIFY(qgeoserviceprovider); + QCOMPARE(qgeoserviceprovider->error(), QGeoServiceProvider::NotSupportedError); + + qgeoserviceprovider->setAllowExperimental(true); + QCOMPARE(qgeoserviceprovider->error(), QGeoServiceProvider::NoError); + QCOMPARE(qgeoserviceprovider->geocodingFeatures(), + QGeoServiceProvider::OfflineGeocodingFeature + | QGeoServiceProvider::ReverseGeocodingFeature); + + qgeocodingmanager = qgeoserviceprovider->geocodingManager(); + QVERIFY(qgeocodingmanager); +} + +void tst_QGeoCodingManager::locale() +{ + QLocale *german = new QLocale (QLocale::German, QLocale::Germany); + QLocale *english = new QLocale (QLocale::C, QLocale::AnyCountry); + + //Default Locale from the Search Engine + QCOMPARE(qgeocodingmanager->locale(),*german); + + qgeocodingmanager->setLocale(*english); + + QCOMPARE(qgeocodingmanager->locale(),*english); + + QVERIFY(qgeocodingmanager->locale() != *german); + + delete german; + delete english; +} + +void tst_QGeoCodingManager::name() +{ + QString name = "geocode.test.plugin"; + QCOMPARE(qgeocodingmanager->managerName(),name); +} + +void tst_QGeoCodingManager::version() +{ + int version=100; + QCOMPARE(qgeocodingmanager->managerVersion(),version); + +} + +void tst_QGeoCodingManager::search() +{ + QCOMPARE(signalerror->count(),0); + QCOMPARE(signalfinished->count(),0); + + QString search = "Berlin. Invaliendenstrasse"; + int limit = 10; + int offset = 2; + + QGeoCodeReply * reply = qgeocodingmanager->geocode(search, limit,offset); + + QCOMPARE(reply->errorString(),search); + QCOMPARE(signalfinished->count(),1); + QCOMPARE(signalerror->count(),0); + + delete reply; +} + +void tst_QGeoCodingManager::geocode() +{ + QCOMPARE(signalerror->count(),0); + QCOMPARE(signalfinished->count(),0); + + QGeoAddress *address = new QGeoAddress (); + QString city = "Berlin"; + address->setCity(city); + + QGeoCodeReply *reply = qgeocodingmanager->geocode(*address); + + QCOMPARE(reply->errorString(),city); + QCOMPARE(signalfinished->count(),1); + QCOMPARE(signalerror->count(),0); + + delete address; + delete reply; +} + +void tst_QGeoCodingManager::reverseGeocode() +{ + QCOMPARE(signalerror->count(), 0); + QCOMPARE(signalfinished->count(), 0); + + QGeoCoordinate *coordinate = new QGeoCoordinate(34.34, 56.65); + + QGeoCodeReply *reply = qgeocodingmanager->reverseGeocode(*coordinate); + + QCOMPARE(reply->errorString(), coordinate->toString()); + QCOMPARE(signalfinished->count(), 1); + QCOMPARE(signalerror->count(), 0); + + delete coordinate; + delete reply; + + +} + + +QTEST_GUILESS_MAIN(tst_QGeoCodingManager) + diff --git a/tests/auto/qgeocodingmanager/tst_qgeocodingmanager.h b/tests/auto/qgeocodingmanager/tst_qgeocodingmanager.h new file mode 100644 index 0000000..97dba3e --- /dev/null +++ b/tests/auto/qgeocodingmanager/tst_qgeocodingmanager.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#ifndef TST_QGEOCODINGMANAGER_H +#define TST_QGEOCODINGMANAGER_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +QT_USE_NAMESPACE + +class tst_QGeoCodingManager: public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + void locale(); + void name(); + void version(); + void search(); + void geocode(); + void reverseGeocode(); + +private: + QGeoServiceProvider *qgeoserviceprovider; + QGeoCodingManager *qgeocodingmanager; + QSignalSpy *signalerror; + QSignalSpy *signalfinished; + void loadGeocodingManager(); + +}; +Q_DECLARE_METATYPE(QGeoCodeReply*); +Q_DECLARE_METATYPE(QGeoCodeReply::Error); + +#endif + diff --git a/tests/auto/qgeocodingmanagerplugins/geocoding_plugin.json b/tests/auto/qgeocodingmanagerplugins/geocoding_plugin.json new file mode 100644 index 0000000..9ac9353 --- /dev/null +++ b/tests/auto/qgeocodingmanagerplugins/geocoding_plugin.json @@ -0,0 +1,10 @@ +{ + "Keys": ["geocode.test.plugin"], + "Provider": "geocode.test.plugin", + "Version": 100, + "Experimental": true, + "Features": [ + "OfflineGeocodingFeature", + "ReverseGeocodingFeature" + ] +} diff --git a/tests/auto/qgeocodingmanagerplugins/qgeocodingmanagerengine_test.h b/tests/auto/qgeocodingmanagerplugins/qgeocodingmanagerengine_test.h new file mode 100644 index 0000000..6d88764 --- /dev/null +++ b/tests/auto/qgeocodingmanagerplugins/qgeocodingmanagerengine_test.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOCODINGMANAGERENGINE_TEST_H +#define QGEOCODINGMANAGERENGINE_TEST_H + +#include +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class GeocodeReplyTest : public QGeoCodeReply +{ + Q_OBJECT +public: + GeocodeReplyTest(QObject *parent = 0) : QGeoCodeReply(parent) {} + + void callAddLocation ( const QGeoLocation & location ) {addLocation(location);} + void callSetError ( Error error, const QString & errorString ) {setError(error, errorString);} + void callSetFinished ( bool finished ) {setFinished(finished);} + void callSetLimit ( int limit ) {setLimit(limit);} + void callSetOffset ( int offset ) {setOffset(offset);} + void callSetLocations ( const QList & locations ) {setLocations(locations);} + void callSetViewport ( const QGeoShape &viewport ) {setViewport(viewport);} + +}; + +class QGeoCodingManagerEngineTest: public QGeoCodingManagerEngine + +{ +Q_OBJECT +public: + QGeoCodingManagerEngineTest(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) : + QGeoCodingManagerEngine(parameters) + { + Q_UNUSED(error) + Q_UNUSED(errorString) + setLocale(QLocale(QLocale::German, QLocale::Germany)); + } + + QGeoCodeReply* geocode(const QString &searchString, int limit, int offset, const QGeoShape &bounds) + { + GeocodeReplyTest *geocodereply = new GeocodeReplyTest(); + geocodereply->callSetLimit(limit); + geocodereply->callSetOffset(offset); + geocodereply->callSetViewport(bounds); + geocodereply->callSetError(QGeoCodeReply::NoError,searchString); + geocodereply->callSetFinished(true); + emit(this->finished(geocodereply)); + + return static_cast(geocodereply); + } + + QGeoCodeReply* geocode (const QGeoAddress &address, const QGeoShape &bounds) + { + GeocodeReplyTest *geocodereply = new GeocodeReplyTest(); + geocodereply->callSetViewport(bounds); + geocodereply->callSetError(QGeoCodeReply::NoError,address.city()); + geocodereply->callSetFinished(true); + emit(this->finished(geocodereply)); + + return static_cast(geocodereply); + } + + QGeoCodeReply* reverseGeocode(const QGeoCoordinate &coordinate, const QGeoShape &bounds) + { + GeocodeReplyTest *geocodereply = new GeocodeReplyTest(); + geocodereply->callSetViewport(bounds); + geocodereply->callSetError(QGeoCodeReply::NoError,coordinate.toString()); + geocodereply->callSetFinished(true); + emit(this->finished(geocodereply)); + return static_cast(geocodereply); + } +}; + +#endif diff --git a/tests/auto/qgeocodingmanagerplugins/qgeocodingmanagerplugins.pro b/tests/auto/qgeocodingmanagerplugins/qgeocodingmanagerplugins.pro new file mode 100644 index 0000000..650bccb --- /dev/null +++ b/tests/auto/qgeocodingmanagerplugins/qgeocodingmanagerplugins.pro @@ -0,0 +1,15 @@ +TARGET = qtgeoservices_geocodingplugin +QT += location + +PLUGIN_TYPE = geoservices +PLUGIN_CLASS_NAME = GeocodingTestGeoServicePlugin +PLUGIN_EXTENDS = - +load(qt_plugin) + +HEADERS += qgeocodingmanagerengine_test.h \ + qgeoserviceproviderplugin_test.h + +SOURCES += qgeoserviceproviderplugin_test.cpp + +OTHER_FILES += \ + geocoding_plugin.json diff --git a/tests/auto/qgeocodingmanagerplugins/qgeoserviceproviderplugin_test.cpp b/tests/auto/qgeocodingmanagerplugins/qgeoserviceproviderplugin_test.cpp new file mode 100644 index 0000000..c7729c2 --- /dev/null +++ b/tests/auto/qgeocodingmanagerplugins/qgeoserviceproviderplugin_test.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoserviceproviderplugin_test.h" +#include "qgeocodingmanagerengine_test.h" + +#include + +QGeoServiceProviderFactoryTest::QGeoServiceProviderFactoryTest() +{ +} + +QGeoServiceProviderFactoryTest::~QGeoServiceProviderFactoryTest() +{ +} + +QGeoCodingManagerEngine* QGeoServiceProviderFactoryTest::createGeocodingManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, + QString *errorString) const +{ + return new QGeoCodingManagerEngineTest(parameters, error, errorString); +} diff --git a/tests/auto/qgeocodingmanagerplugins/qgeoserviceproviderplugin_test.h b/tests/auto/qgeocodingmanagerplugins/qgeoserviceproviderplugin_test.h new file mode 100644 index 0000000..53d2961 --- /dev/null +++ b/tests/auto/qgeocodingmanagerplugins/qgeoserviceproviderplugin_test.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDER_TEST_H +#define QGEOSERVICEPROVIDER_TEST_H + +#include +#include + +QT_USE_NAMESPACE + +class QGeoServiceProviderFactoryTest: public QObject, public QGeoServiceProviderFactory +{ + Q_OBJECT + Q_INTERFACES(QGeoServiceProviderFactory) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0" + FILE "geocoding_plugin.json") + +public: + QGeoServiceProviderFactoryTest(); + ~QGeoServiceProviderFactoryTest(); + + QGeoCodingManagerEngine* createGeocodingManagerEngine( + const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) const; +}; + +#endif + + diff --git a/tests/auto/qgeocoordinate/qgeocoordinate.pro b/tests/auto/qgeocoordinate/qgeocoordinate.pro new file mode 100644 index 0000000..52795e1 --- /dev/null +++ b/tests/auto/qgeocoordinate/qgeocoordinate.pro @@ -0,0 +1,8 @@ +CONFIG += testcase +TARGET = tst_qgeocoordinate + +HEADERS += ../utils/qlocationtestutils_p.h +SOURCES += tst_qgeocoordinate.cpp \ + ../utils/qlocationtestutils.cpp + +QT += positioning testlib diff --git a/tests/auto/qgeocoordinate/tst_qgeocoordinate.cpp b/tests/auto/qgeocoordinate/tst_qgeocoordinate.cpp new file mode 100644 index 0000000..fa9fd5c --- /dev/null +++ b/tests/auto/qgeocoordinate/tst_qgeocoordinate.cpp @@ -0,0 +1,951 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include "../utils/qlocationtestutils_p.h" + +#include +#include + +#include +#include + +#include + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QGeoCoordinate::CoordinateFormat) +Q_DECLARE_METATYPE(QGeoCoordinate::CoordinateType) + +static const QGeoCoordinate BRISBANE(-27.46758, 153.027892); +static const QGeoCoordinate MELBOURNE(-37.814251, 144.963169); +static const QGeoCoordinate LONDON(51.500152, -0.126236); +static const QGeoCoordinate NEW_YORK(40.71453, -74.00713); +static const QGeoCoordinate NORTH_POLE(90, 0); +static const QGeoCoordinate SOUTH_POLE(-90, 0); + +static const QChar DEGREES_SYMB(0x00B0); + + +QByteArray tst_qgeocoordinate_debug; + +void tst_qgeocoordinate_messageHandler(QtMsgType type, const QMessageLogContext &, const QString &msg) +{ + switch (type) { + case QtDebugMsg : + tst_qgeocoordinate_debug = msg.toLocal8Bit(); + break; + default: + break; + } +} + + +class tst_QGeoCoordinate : public QObject +{ + Q_OBJECT + +private: + enum TestDataType { + Latitude, + Longitude, + Altitude + }; + +private slots: + void initTestcase() + { + qRegisterMetaType(); + QMetaType::registerEqualsComparator(); + } + + void constructor() + { + QGeoCoordinate c; + QVERIFY(!c.isValid()); + QCOMPARE(c, QGeoCoordinate()); + } + + void constructor_lat_long() + { + QFETCH(double, latitude); + QFETCH(double, longitude); + QFETCH(QGeoCoordinate::CoordinateType, type); + + QGeoCoordinate c(latitude, longitude); + QCOMPARE(c.type(), type); + if (type != QGeoCoordinate::InvalidCoordinate) { + QVERIFY(c.isValid()); + QCOMPARE(c.latitude(), latitude); + QCOMPARE(c.longitude(), longitude); + } else { + QVERIFY(!c.isValid()); + QVERIFY(c.latitude() != latitude); + QVERIFY(c.longitude() != longitude); + } + } + + void constructor_lat_long_data() + { + QTest::addColumn("latitude"); + QTest::addColumn("longitude"); + QTest::addColumn("type"); + + QTest::newRow("both zero") << 0.0 << 0.0 << QGeoCoordinate::Coordinate2D; + QTest::newRow("both negative") << -1.0 << -1.0 << QGeoCoordinate::Coordinate2D; + QTest::newRow("both positive") << 1.0 << 1.0 << QGeoCoordinate::Coordinate2D; + QTest::newRow("latitude negative") << -1.0 << 1.0 << QGeoCoordinate::Coordinate2D; + QTest::newRow("longitude negative") << 1.0 << -1.0 << QGeoCoordinate::Coordinate2D; + + QTest::newRow("both too high") << 90.1 << 180.1 << QGeoCoordinate::InvalidCoordinate; + QTest::newRow("latitude too high") << 90.1 << 0.1 << QGeoCoordinate::InvalidCoordinate; + QTest::newRow("longitude too high") << 0.1 << 180.1 << QGeoCoordinate::InvalidCoordinate; + + QTest::newRow("both too low") << -90.1 << -180.1 << QGeoCoordinate::InvalidCoordinate; + QTest::newRow("latitude too low") << -90.1 << 0.1 << QGeoCoordinate::InvalidCoordinate; + QTest::newRow("longitude too low") << 0.1 << -180.1 << QGeoCoordinate::InvalidCoordinate; + + QTest::newRow("both not too high") << 90.0 << 180.0 << QGeoCoordinate::Coordinate2D; + QTest::newRow("both not too low") << -90.0 << -180.0 << QGeoCoordinate::Coordinate2D; + + QTest::newRow("latitude too high and longitude too low") << 90.1 << -180.1 << QGeoCoordinate::InvalidCoordinate; + QTest::newRow("latitude too low and longitude too high") << -90.1 << 180.1 << QGeoCoordinate::InvalidCoordinate; + } + + void constructor_lat_long_alt() + { + QFETCH(double, latitude); + QFETCH(double, longitude); + QFETCH(double, altitude); + QFETCH(QGeoCoordinate::CoordinateType, type); + + QGeoCoordinate c(latitude, longitude, altitude); + QCOMPARE(c.type(), type); + if (type != QGeoCoordinate::InvalidCoordinate) { + QCOMPARE(c.latitude(), latitude); + QCOMPARE(c.longitude(), longitude); + QCOMPARE(c.altitude(), altitude); + } else { + QVERIFY(!c.isValid()); + QVERIFY(c.latitude() != latitude); + QVERIFY(c.longitude() != longitude); + QVERIFY(c.altitude() != altitude); + } + } + + void constructor_lat_long_alt_data() + { + QTest::addColumn("latitude"); + QTest::addColumn("longitude"); + QTest::addColumn("altitude"); + QTest::addColumn("type"); + + QTest::newRow("all zero") << 0.0 << 0.0 << 0.0 << QGeoCoordinate::Coordinate3D; + QTest::newRow("all negative") << -1.0 << -1.0 << -2.0 << QGeoCoordinate::Coordinate3D; + QTest::newRow("all positive") << 1.0 << 1.0 << 4.0 << QGeoCoordinate::Coordinate3D; + + QTest::newRow("latitude negative") << -1.0 << 1.0 << 1.0 << QGeoCoordinate::Coordinate3D; + QTest::newRow("longitude negative") << 1.0 << -1.0 << 1.0 << QGeoCoordinate::Coordinate3D; + QTest::newRow("altitude negative") << 1.0 << 1.0 << -1.0 << QGeoCoordinate::Coordinate3D; + + QTest::newRow("altitude not too high") << 1.0 << 1.0 << DBL_MAX << QGeoCoordinate::Coordinate3D; + QTest::newRow("altitude not too low") << 1.0 << 1.0 << DBL_MIN << QGeoCoordinate::Coordinate3D; + + QTest::newRow("all not too high") << 90.0 << 180.0 << DBL_MAX << QGeoCoordinate::Coordinate3D; + QTest::newRow("all not too low") << -90.0 << -180.0 << DBL_MIN << QGeoCoordinate::Coordinate3D; + + QTest::newRow("all too high") << 90.1 << 180.1 << DBL_MAX << QGeoCoordinate::InvalidCoordinate; + QTest::newRow("all too low") << -90.1 << -180.1 << DBL_MIN << QGeoCoordinate::InvalidCoordinate; + } + + void copy_constructor() + { + QFETCH(QGeoCoordinate, c); + + QGeoCoordinate copy(c); + QCOMPARE(copy.type(), c.type()); + if (c.type() != QGeoCoordinate::InvalidCoordinate) { + QCOMPARE(copy.latitude(), c.latitude()); + QCOMPARE(copy.longitude(), c.longitude()); + if (c.type() == QGeoCoordinate::Coordinate3D) + QCOMPARE(copy.altitude(), c.altitude()); + } + } + + void copy_constructor_data() + { + QTest::addColumn("c"); + + QTest::newRow("no argument") << QGeoCoordinate(); + QTest::newRow("latitude, longitude arguments all zero") << QGeoCoordinate(0.0, 0.0); + + QTest::newRow("latitude, longitude arguments not too high") << QGeoCoordinate(90.0, 180.0); + QTest::newRow("latitude, longitude arguments not too low") << QGeoCoordinate(-90.0, -180.0); + QTest::newRow("latitude, longitude arguments too high") << QGeoCoordinate(90.1, 180.1); + QTest::newRow("latitude, longitude arguments too low") << QGeoCoordinate(-90.1, -180.1); + + QTest::newRow("latitude, longitude, altitude arguments all zero") << QGeoCoordinate(0.0, 0.0, 0.0); + QTest::newRow("latitude, longitude, altitude arguments not too high values") << QGeoCoordinate(90.0, 180.0, DBL_MAX); + QTest::newRow("latitude, longitude, altitude arguments not too low values") << QGeoCoordinate(-90.0, -180.0, DBL_MIN); + + QTest::newRow("latitude, longitude, altitude arguments too high latitude & longitude") << QGeoCoordinate(90.1, 180.1, DBL_MAX); + QTest::newRow("latitude, longitude, altitude arguments too low latitude & longitude") << QGeoCoordinate(-90.1, -180.1, DBL_MAX); + } + + void destructor() + { + QGeoCoordinate *coordinate; + + coordinate = new QGeoCoordinate(); + delete coordinate; + + coordinate = new QGeoCoordinate(0.0, 0.0); + delete coordinate; + + coordinate = new QGeoCoordinate(0.0, 0.0, 0.0); + delete coordinate; + + coordinate = new QGeoCoordinate(90.0, 180.0); + delete coordinate; + + coordinate = new QGeoCoordinate(-90.0, -180.0); + delete coordinate; + + coordinate = new QGeoCoordinate(90.1, 180.1); + delete coordinate; + + coordinate = new QGeoCoordinate(-90.1, -180.1); + delete coordinate; + } + + void destructor2() + { + QFETCH(QGeoCoordinate, c); + QGeoCoordinate *coordinate = new QGeoCoordinate(c); + delete coordinate; + } + + void destructor2_data() + { + copy_constructor_data(); + } + + void assign() + { + QFETCH(QGeoCoordinate, c); + + QGeoCoordinate c1 = c; + QCOMPARE(c.type(), c1.type()); + if (c.isValid()) { + QCOMPARE(c.latitude(), c.latitude()); + QCOMPARE(c.longitude(), c.longitude()); + if (c.type() == QGeoCoordinate::Coordinate3D) + QCOMPARE(c.altitude(), c.altitude()); + } + } + + void assign_data() + { + copy_constructor_data(); + } + + void comparison() + { + QFETCH(QGeoCoordinate, c1); + QFETCH(QGeoCoordinate, c2); + QFETCH(bool, result); + + QCOMPARE(c1 == c2, result); + QVariant v1 = QVariant::fromValue(c1); + QVariant v2 = QVariant::fromValue(c2); + QCOMPARE(v2 == v1, result); + } + + void comparison_data() + { + QTest::addColumn("c1"); + QTest::addColumn("c2"); + QTest::addColumn("result"); + + QTest::newRow("Invalid != BRISBANE") + << QGeoCoordinate(-190,-1000) << BRISBANE << false; + QTest::newRow("BRISBANE != MELBOURNE") + << BRISBANE << MELBOURNE << false; + QTest::newRow("equal") + << BRISBANE << BRISBANE << true; + QTest::newRow("LONDON != uninitialized data") + << LONDON << QGeoCoordinate() << false; + QTest::newRow("uninitialized data == uninitialized data") + << QGeoCoordinate() << QGeoCoordinate() << true; + QTest::newRow("invalid == same invalid") + << QGeoCoordinate(-190,-1000) << QGeoCoordinate(-190,-1000) << true; + QTest::newRow("invalid == different invalid") + << QGeoCoordinate(-190,-1000) << QGeoCoordinate(190,1000) << true; + QTest::newRow("valid != another valid") + << QGeoCoordinate(-90,-180) << QGeoCoordinate(-45,+45) << false; + QTest::newRow("valid == same valid") + << QGeoCoordinate(-90,-180) << QGeoCoordinate(-90,-180) << true; + QTest::newRow("different longitudes at north pole are equal") + << QGeoCoordinate(90, 0) << QGeoCoordinate(90, 45) << true; + QTest::newRow("different longitudes at south pole are equal") + << QGeoCoordinate(-90, 0) << QGeoCoordinate(-90, 45) << true; + } + + void type() + { + QFETCH(QGeoCoordinate, c); + QFETCH(QGeoCoordinate::CoordinateType, type); + + QCOMPARE(c.type(), type); + } + + void type_data() + { + QTest::addColumn("c"); + QTest::addColumn("type"); + + QGeoCoordinate c; + + QTest::newRow("no values set") << c << QGeoCoordinate::InvalidCoordinate; + + c.setAltitude(1.0); + QTest::newRow("only altitude is set") << c << QGeoCoordinate::InvalidCoordinate; + + c.setLongitude(1.0); + QTest::newRow("only latitude and altitude is set") << c << QGeoCoordinate::InvalidCoordinate; + + c.setLatitude(-1.0); + QTest::newRow("all valid: 3D Coordinate") << c << QGeoCoordinate::Coordinate3D; + + c.setLatitude(-90.1); + QTest::newRow("too low latitude and valid longitude") << c << QGeoCoordinate::InvalidCoordinate; + + c.setLongitude(-180.1); + c.setLatitude(90.0); + QTest::newRow("valid latitude and too low longitude") << c << QGeoCoordinate::InvalidCoordinate; + + c.setLatitude(90.1); + c.setLongitude(-180.0); + QTest::newRow("too high latitude and valid longitude") << c << QGeoCoordinate::InvalidCoordinate; + + c.setLatitude(-90.0); + c.setLongitude(180.1); + QTest::newRow("valid latitude and too high longitude") << c << QGeoCoordinate::InvalidCoordinate; + } + + void valid() + { + QFETCH(QGeoCoordinate, c); + QCOMPARE(c.isValid(), c.type() != QGeoCoordinate::InvalidCoordinate); + } + + void valid_data() + { + type_data(); + } + + void addDataValues(TestDataType type) + { + QTest::addColumn("value"); + QTest::addColumn("valid"); + + QTest::newRow("negative") << -1.0 << true; + QTest::newRow("zero") << 0.0 << true; + QTest::newRow("positive") << 1.0 << true; + + switch (type) { + case Latitude: + QTest::newRow("too low") << -90.1 << false; + QTest::newRow("not too low") << -90.0 << true; + QTest::newRow("not too hight") << 90.0 << true; + QTest::newRow("too high") << 90.1; + break; + case Longitude: + QTest::newRow("too low") << -180.1 << false; + QTest::newRow("not too low") << -180.0 << true; + QTest::newRow("not too hight") << 180.0 << true; + QTest::newRow("too high") << 180.1; + break; + case Altitude: + break; + } + } + + void latitude() + { + QFETCH(double, value); + QGeoCoordinate c; + c.setLatitude(value); + QCOMPARE(c.latitude(), value); + + QGeoCoordinate c2 = c; + QCOMPARE(c.latitude(), value); + QCOMPARE(c2, c); + } + void latitude_data() { addDataValues(Latitude); } + + void longitude() + { + QFETCH(double, value); + QGeoCoordinate c; + c.setLongitude(value); + QCOMPARE(c.longitude(), value); + + QGeoCoordinate c2 = c; + QCOMPARE(c.longitude(), value); + QCOMPARE(c2, c); + } + void longitude_data() { addDataValues(Longitude); } + + void altitude() + { + QFETCH(double, value); + QGeoCoordinate c; + c.setAltitude(value); + QCOMPARE(c.altitude(), value); + + QGeoCoordinate c2 = c; + QCOMPARE(c.altitude(), value); + QCOMPARE(c2, c); + } + void altitude_data() { addDataValues(Altitude); } + + void distanceTo() + { + QFETCH(QGeoCoordinate, c1); + QFETCH(QGeoCoordinate, c2); + QFETCH(qreal, distance); + + QCOMPARE(QString::number(c1.distanceTo(c2)), QString::number(distance)); + } + + void distanceTo_data() + { + QTest::addColumn("c1"); + QTest::addColumn("c2"); + QTest::addColumn("distance"); + + QTest::newRow("invalid coord 1") + << QGeoCoordinate() << BRISBANE << qreal(0.0); + QTest::newRow("invalid coord 2") + << BRISBANE << QGeoCoordinate() << qreal(0.0); + QTest::newRow("brisbane -> melbourne") + << BRISBANE << MELBOURNE << qreal(1374820.1618767744); + QTest::newRow("london -> new york") + << LONDON << NEW_YORK << qreal(5570538.4987236429); + QTest::newRow("north pole -> south pole") + << NORTH_POLE << SOUTH_POLE << qreal(20015109.4154876769); + } + + void azimuthTo() + { + QFETCH(QGeoCoordinate, c1); + QFETCH(QGeoCoordinate, c2); + QFETCH(qreal, azimuth); + + qreal result = c1.azimuthTo(c2); + QVERIFY(result >= 0.0); + QVERIFY(result < 360.0); + QCOMPARE(QString::number(result), QString::number(azimuth)); + } + + void azimuthTo_data() + { + QTest::addColumn("c1"); + QTest::addColumn("c2"); + QTest::addColumn("azimuth"); + + QTest::newRow("invalid coord 1") + << QGeoCoordinate() << BRISBANE << qreal(0.0); + QTest::newRow("invalid coord 2") + << BRISBANE << QGeoCoordinate() << qreal(0.0); + QTest::newRow("brisbane -> melbourne") + << BRISBANE << MELBOURNE << qreal(211.1717286649); + QTest::newRow("london -> new york") + << LONDON << NEW_YORK << qreal(288.3388804508); + QTest::newRow("north pole -> south pole") + << NORTH_POLE << SOUTH_POLE << qreal(180.0); + QTest::newRow("Almost 360degrees bearing") + << QGeoCoordinate(0.5,45.0,0.0) << QGeoCoordinate(0.5,-134.9999651,0.0) << qreal(359.998); + } + + void atDistanceAndAzimuth() + { + QFETCH(QGeoCoordinate, origin); + QFETCH(qreal, distance); + QFETCH(qreal, azimuth); + QFETCH(QGeoCoordinate, result); + + QCOMPARE(result, origin.atDistanceAndAzimuth(distance, azimuth)); + } + + void atDistanceAndAzimuth_data() + { + QTest::addColumn("origin"); + QTest::addColumn("distance"); + QTest::addColumn("azimuth"); + QTest::addColumn("result"); + + QTest::newRow("invalid coord") + << QGeoCoordinate() + << qreal(1000.0) + << qreal(10.0) + << QGeoCoordinate(); + if (sizeof(qreal) == sizeof(double)) { + QTest::newRow("brisbane -> melbourne") + << BRISBANE + << qreal(1374820.1618767744) + << qreal(211.1717286649) + << MELBOURNE; + QTest::newRow("london -> new york") + << LONDON + << qreal(5570538.4987236429) + << qreal(288.3388804508) + << NEW_YORK; + QTest::newRow("north pole -> south pole") + << NORTH_POLE + << qreal(20015109.4154876769) + << qreal(180.0) + << SOUTH_POLE; + } else { + QTest::newRow("brisbane -> melbourne") + << BRISBANE + << qreal(1374820.1618767744) + << qreal(211.1717286649) + << QGeoCoordinate(-37.8142515084775, 144.963170622944); + QTest::newRow("london -> new york") + << LONDON + << qreal(5570538.4987236429) + << qreal(288.3388804508) + << QGeoCoordinate(40.7145220608416, -74.0071216045375); + QTest::newRow("north pole -> south pole") + << NORTH_POLE + << qreal(20015109.4154876769) + << qreal(180.0) + << QGeoCoordinate(-89.9999947369857, -90.0); + } + } + + void degreesToString() + { + QFETCH(QGeoCoordinate, coord); + QFETCH(QGeoCoordinate::CoordinateFormat, format); + QFETCH(QString, string); + + QCOMPARE(coord.toString(format), string); + } + + void degreesToString_data() + { + QTest::addColumn("coord"); + QTest::addColumn("format"); + QTest::addColumn("string"); + + QGeoCoordinate northEast(27.46758, 153.027892); + QGeoCoordinate northEastWithAlt(27.46758, 153.027892, 28.23411); + QGeoCoordinate southEast(-27.46758, 153.027892); + QGeoCoordinate southEastWithAlt(-27.46758, 153.027892, 28.23411); + QGeoCoordinate northWest(27.46758, -153.027892); + QGeoCoordinate northWestWithAlt(27.46758, -153.027892, 28.23411); + QGeoCoordinate southWest(-27.46758, -153.027892); + QGeoCoordinate southWestWithAlt(-27.46758, -153.027892, 28.23411); + + QGeoCoordinate empty; + QGeoCoordinate toohigh(90.1, 180.1); + QGeoCoordinate toolow(-90.1, -180.1); + QGeoCoordinate zeroLatLong(0.0, 0.0); + QGeoCoordinate allZero(0.0, 0.0, 0.0); + + QTest::newRow("empty, dd, no hemisphere") + << empty << QGeoCoordinate::Degrees + << QString(); + QTest::newRow("empty, dd, hemisphere") + << empty << QGeoCoordinate::DegreesWithHemisphere + << QString(); + QTest::newRow("empty, dm, no hemisphere") + << empty << QGeoCoordinate::DegreesMinutes + << QString(); + QTest::newRow("empty, dm, hemisphere") + << empty << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString(); + QTest::newRow("empty, dms, no hemisphere") + << empty << QGeoCoordinate::DegreesMinutesSeconds + << QString(); + QTest::newRow("empty, dms, hemisphere") + << empty << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString(); + + QTest::newRow("too low, dd, no hemisphere") + << toolow << QGeoCoordinate::Degrees + << QString(); + QTest::newRow("too low, dd, hemisphere") + << toolow << QGeoCoordinate::DegreesWithHemisphere + << QString(); + QTest::newRow("too low, dm, no hemisphere") + << toolow << QGeoCoordinate::DegreesMinutes + << QString(); + QTest::newRow("too low, dm, hemisphere") + << toolow << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString(); + QTest::newRow("too low, dms, no hemisphere") + << toolow << QGeoCoordinate::DegreesMinutesSeconds + << QString(); + QTest::newRow("too low, dms, hemisphere") + << toolow << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString(); + + QTest::newRow("too high, dd, no hemisphere") + << toohigh << QGeoCoordinate::Degrees + << QString(); + QTest::newRow("too high, dd, hemisphere") + << toohigh << QGeoCoordinate::DegreesWithHemisphere + << QString(); + QTest::newRow("too high, dm, no hemisphere") + << toohigh << QGeoCoordinate::DegreesMinutes + << QString(); + QTest::newRow("too high, dm, hemisphere") + << toohigh << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString(); + QTest::newRow("too high, dms, no hemisphere") + << toohigh << QGeoCoordinate::DegreesMinutesSeconds + << QString(); + QTest::newRow("too high, dms, hemisphere") + << toohigh << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString(); + + QTest::newRow("zeroLatLong, dd, no hemisphere") + << zeroLatLong << QGeoCoordinate::Degrees + << QString("0.00000%1, 0.00000%1").arg(DEGREES_SYMB); + QTest::newRow("zeroLatLong, dd, hemisphere") + << zeroLatLong << QGeoCoordinate::DegreesWithHemisphere + << QString("0.00000%1, 0.00000%1").arg(DEGREES_SYMB); + QTest::newRow("zeroLatLong, dm, no hemisphere") + << zeroLatLong << QGeoCoordinate::DegreesMinutes + << QString("0%1 0.000', 0%1 0.000'").arg(DEGREES_SYMB); + QTest::newRow("zeroLatLong, dm, hemisphere") + << zeroLatLong << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("0%1 0.000', 0%1 0.000'").arg(DEGREES_SYMB); + QTest::newRow("zeroLatLong, dms, no hemisphere") + << zeroLatLong << QGeoCoordinate::DegreesMinutesSeconds + << QString("0%1 0' 0.0\", 0%1 0' 0.0\"").arg(DEGREES_SYMB); + QTest::newRow("zeroLatLong, dms, hemisphere") + << zeroLatLong << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("0%1 0' 0.0\", 0%1 0' 0.0\"").arg(DEGREES_SYMB); + + QTest::newRow("allZero, dd, no hemisphere") + << allZero << QGeoCoordinate::Degrees + << QString("0.00000%1, 0.00000%1, 0m").arg(DEGREES_SYMB); + QTest::newRow("allZero, dd, hemisphere") + << allZero << QGeoCoordinate::DegreesWithHemisphere + << QString("0.00000%1, 0.00000%1, 0m").arg(DEGREES_SYMB); + QTest::newRow("allZero, dm, no hemisphere") + << allZero << QGeoCoordinate::DegreesMinutes + << QString("0%1 0.000', 0%1 0.000', 0m").arg(DEGREES_SYMB); + QTest::newRow("allZero, dm, hemisphere") + << allZero << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("0%1 0.000', 0%1 0.000', 0m").arg(DEGREES_SYMB); + QTest::newRow("allZero, dms, no hemisphere") + << allZero << QGeoCoordinate::DegreesMinutesSeconds + << QString("0%1 0' 0.0\", 0%1 0' 0.0\", 0m").arg(DEGREES_SYMB); + QTest::newRow("allZero, dms, hemisphere") + << allZero << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("0%1 0' 0.0\", 0%1 0' 0.0\", 0m").arg(DEGREES_SYMB); + + QTest::newRow("NE, dd, no hemisphere") + << northEast << QGeoCoordinate::Degrees + << QString("27.46758%1, 153.02789%1").arg(DEGREES_SYMB); + QTest::newRow("NE, dd, hemisphere") + << northEast << QGeoCoordinate::DegreesWithHemisphere + << QString("27.46758%1 N, 153.02789%1 E").arg(DEGREES_SYMB); + QTest::newRow("NE, dm, no hemisphere") + << northEast << QGeoCoordinate::DegreesMinutes + << QString("27%1 28.055', 153%1 1.674'").arg(DEGREES_SYMB); + QTest::newRow("NE, dm, hemisphere") + << northEast << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("27%1 28.055' N, 153%1 1.674' E").arg(DEGREES_SYMB); + QTest::newRow("NE, dms, no hemisphere") + << northEast << QGeoCoordinate::DegreesMinutesSeconds + << QString("27%1 28' 3.3\", 153%1 1' 40.4\"").arg(DEGREES_SYMB); + QTest::newRow("NE, dms, hemisphere") + << northEast << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("27%1 28' 3.3\" N, 153%1 1' 40.4\" E").arg(DEGREES_SYMB); + + QTest::newRow("NE with alt, dd, no hemisphere") + << northEastWithAlt << QGeoCoordinate::Degrees + << QString("27.46758%1, 153.02789%1, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NE with alt, dd, hemisphere") + << northEastWithAlt << QGeoCoordinate::DegreesWithHemisphere + << QString("27.46758%1 N, 153.02789%1 E, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NE with alt, dm, no hemisphere") + << northEastWithAlt << QGeoCoordinate::DegreesMinutes + << QString("27%1 28.055', 153%1 1.674', 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NE with alt, dm, hemisphere") + << northEastWithAlt << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("27%1 28.055' N, 153%1 1.674' E, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NE with alt, dms, no hemisphere") + << northEastWithAlt << QGeoCoordinate::DegreesMinutesSeconds + << QString("27%1 28' 3.3\", 153%1 1' 40.4\", 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NE with alt, dms, hemisphere") + << northEastWithAlt << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("27%1 28' 3.3\" N, 153%1 1' 40.4\" E, 28.2341m").arg(DEGREES_SYMB); + + QTest::newRow("SE, dd, no hemisphere") + << southEast << QGeoCoordinate::Degrees + << QString("-27.46758%1, 153.02789%1").arg(DEGREES_SYMB); + QTest::newRow("SE, dd, hemisphere") + << southEast << QGeoCoordinate::DegreesWithHemisphere + << QString("27.46758%1 S, 153.02789%1 E").arg(DEGREES_SYMB); + QTest::newRow("SE, dm, no hemisphere") + << southEast << QGeoCoordinate::DegreesMinutes + << QString("-27%1 28.055', 153%1 1.674'").arg(DEGREES_SYMB); + QTest::newRow("SE, dm, hemisphere") + << southEast << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("27%1 28.055' S, 153%1 1.674' E").arg(DEGREES_SYMB); + QTest::newRow("SE, dms, no hemisphere") + << southEast << QGeoCoordinate::DegreesMinutesSeconds + << QString("-27%1 28' 3.3\", 153%1 1' 40.4\"").arg(DEGREES_SYMB); + QTest::newRow("SE, dms, hemisphere") + << southEast << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("27%1 28' 3.3\" S, 153%1 1' 40.4\" E").arg(DEGREES_SYMB); + + QTest::newRow("SE with alt, dd, no hemisphere, 28.2341m") + << southEastWithAlt << QGeoCoordinate::Degrees + << QString("-27.46758%1, 153.02789%1, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SE with alt, dd, hemisphere, 28.2341m") + << southEastWithAlt << QGeoCoordinate::DegreesWithHemisphere + << QString("27.46758%1 S, 153.02789%1 E, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SE with alt, dm, no hemisphere, 28.2341m") + << southEastWithAlt << QGeoCoordinate::DegreesMinutes + << QString("-27%1 28.055', 153%1 1.674', 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SE with alt, dm, hemisphere, 28.2341m") + << southEastWithAlt << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("27%1 28.055' S, 153%1 1.674' E, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SE with alt, dms, no hemisphere, 28.2341m") + << southEastWithAlt << QGeoCoordinate::DegreesMinutesSeconds + << QString("-27%1 28' 3.3\", 153%1 1' 40.4\", 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SE with alt, dms, hemisphere, 28.2341m") + << southEastWithAlt << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("27%1 28' 3.3\" S, 153%1 1' 40.4\" E, 28.2341m").arg(DEGREES_SYMB);; + + QTest::newRow("NW, dd, no hemisphere") + << northWest << QGeoCoordinate::Degrees + << QString("27.46758%1, -153.02789%1").arg(DEGREES_SYMB); + QTest::newRow("NW, dd, hemisphere") + << northWest << QGeoCoordinate::DegreesWithHemisphere + << QString("27.46758%1 N, 153.02789%1 W").arg(DEGREES_SYMB); + QTest::newRow("NW, dm, no hemisphere") + << northWest << QGeoCoordinate::DegreesMinutes + << QString("27%1 28.055', -153%1 1.674'").arg(DEGREES_SYMB); + QTest::newRow("NW, dm, hemisphere") + << northWest << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("27%1 28.055' N, 153%1 1.674' W").arg(DEGREES_SYMB); + QTest::newRow("NW, dms, no hemisphere") + << northWest << QGeoCoordinate::DegreesMinutesSeconds + << QString("27%1 28' 3.3\", -153%1 1' 40.4\"").arg(DEGREES_SYMB); + QTest::newRow("NW, dms, hemisphere") + << northWest << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("27%1 28' 3.3\" N, 153%1 1' 40.4\" W").arg(DEGREES_SYMB); + + QTest::newRow("NW with alt, dd, no hemisphere, 28.2341m") + << northWestWithAlt << QGeoCoordinate::Degrees + << QString("27.46758%1, -153.02789%1, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NW with alt, dd, hemisphere, 28.2341m") + << northWestWithAlt << QGeoCoordinate::DegreesWithHemisphere + << QString("27.46758%1 N, 153.02789%1 W, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NW with alt, dm, no hemisphere, 28.2341m") + << northWestWithAlt << QGeoCoordinate::DegreesMinutes + << QString("27%1 28.055', -153%1 1.674', 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NW with alt, dm, hemisphere, 28.2341m") + << northWestWithAlt << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("27%1 28.055' N, 153%1 1.674' W, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NW with alt, dms, no hemisphere, 28.2341m") + << northWestWithAlt << QGeoCoordinate::DegreesMinutesSeconds + << QString("27%1 28' 3.3\", -153%1 1' 40.4\", 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("NW with alt, dms, hemisphere, 28.2341m") + << northWestWithAlt << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("27%1 28' 3.3\" N, 153%1 1' 40.4\" W, 28.2341m").arg(DEGREES_SYMB); + + QTest::newRow("SW, dd, no hemisphere") + << southWest << QGeoCoordinate::Degrees + << QString("-27.46758%1, -153.02789%1").arg(DEGREES_SYMB); + QTest::newRow("SW, dd, hemisphere") + << southWest << QGeoCoordinate::DegreesWithHemisphere + << QString("27.46758%1 S, 153.02789%1 W").arg(DEGREES_SYMB); + QTest::newRow("SW, dm, no hemisphere") + << southWest << QGeoCoordinate::DegreesMinutes + << QString("-27%1 28.055', -153%1 1.674'").arg(DEGREES_SYMB); + QTest::newRow("SW, dm, hemisphere") + << southWest << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("27%1 28.055' S, 153%1 1.674' W").arg(DEGREES_SYMB); + QTest::newRow("SW, dms, no hemisphere") + << southWest << QGeoCoordinate::DegreesMinutesSeconds + << QString("-27%1 28' 3.3\", -153%1 1' 40.4\"").arg(DEGREES_SYMB); + QTest::newRow("SW, dms, hemisphere") + << southWest << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("27%1 28' 3.3\" S, 153%1 1' 40.4\" W").arg(DEGREES_SYMB); + + QTest::newRow("SW with alt, dd, no hemisphere, 28.2341m") + << southWestWithAlt << QGeoCoordinate::Degrees + << QString("-27.46758%1, -153.02789%1, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SW with alt, dd, hemisphere, 28.2341m") + << southWestWithAlt << QGeoCoordinate::DegreesWithHemisphere + << QString("27.46758%1 S, 153.02789%1 W, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SW with alt, dm, no hemisphere, 28.2341m") + << southWestWithAlt << QGeoCoordinate::DegreesMinutes + << QString("-27%1 28.055', -153%1 1.674', 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SW with alt, dm, hemisphere, 28.2341m") + << southWestWithAlt << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString("27%1 28.055' S, 153%1 1.674' W, 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SW with alt, dms, no hemisphere, 28.2341m") + << southWestWithAlt << QGeoCoordinate::DegreesMinutesSeconds + << QString("-27%1 28' 3.3\", -153%1 1' 40.4\", 28.2341m").arg(DEGREES_SYMB); + QTest::newRow("SW with alt, dms, hemisphere, 28.2341m") + << southWestWithAlt << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString("27%1 28' 3.3\" S, 153%1 1' 40.4\" W, 28.2341m").arg(DEGREES_SYMB); + + QTest::newRow("Wrap seconds to Minutes DMSH") + << QGeoCoordinate(1.1333333, 1.1333333) << QGeoCoordinate::DegreesMinutesSecondsWithHemisphere + << QString( "1%1 8' 0.0\" N, 1%1 8' 0.0\" E").arg(DEGREES_SYMB); + QTest::newRow("Wrap seconds to Minutes DMS") + << QGeoCoordinate(1.1333333, 1.1333333) << QGeoCoordinate::DegreesMinutesSeconds + << QString( "1%1 8' 0.0\", 1%1 8' 0.0\"").arg(DEGREES_SYMB); + QTest::newRow("Wrap minutes to Degrees DMH") + << QGeoCoordinate(1.999999, 1.999999) << QGeoCoordinate::DegreesMinutesWithHemisphere + << QString( "2%1 0.000' N, 2%1 0.000' E").arg(DEGREES_SYMB); + QTest::newRow("Wrap minutes to Degrees DM") + << QGeoCoordinate(1.999999, 1.999999) << QGeoCoordinate::DegreesMinutes + << QString( "2%1 0.000', 2%1 0.000'").arg(DEGREES_SYMB); + + QTest::newRow("Wrap seconds to minutes to Degrees DM -> above valid long/lat values") + << QGeoCoordinate(89.9999, 179.9999) << QGeoCoordinate::DegreesMinutesSeconds + << QString( "90%1 0' 0.0\", 180%1 0' 0.0\"").arg(DEGREES_SYMB); + + QTest::newRow("Wrap minutes to Degrees DM ->above valid long/lat values") + << QGeoCoordinate(89.9999, 179.9999) << QGeoCoordinate::DegreesMinutes + << QString( "90%1 0.000', 180%1 0.000'").arg(DEGREES_SYMB); + + } + + void datastream() + { + QFETCH(QGeoCoordinate, c); + + QByteArray ba; + QDataStream out(&ba, QIODevice::WriteOnly); + out << c; + + QDataStream in(&ba, QIODevice::ReadOnly); + QGeoCoordinate inCoord; + in >> inCoord; + QCOMPARE(inCoord, c); + } + + void datastream_data() + { + QTest::addColumn("c"); + + QTest::newRow("invalid") << QGeoCoordinate(); + QTest::newRow("valid lat, long") << BRISBANE; + QTest::newRow("valid lat, long, alt") << QGeoCoordinate(-1, -1, -1); + QTest::newRow("valid lat, long, alt again") << QGeoCoordinate(1, 1, 1); + } + + void debug() + { + QFETCH(QGeoCoordinate, c); + QFETCH(int, nextValue); + QFETCH(QByteArray, debugString); + + qInstallMessageHandler(tst_qgeocoordinate_messageHandler); + qDebug() << c << nextValue; + qInstallMessageHandler(0); + QCOMPARE(tst_qgeocoordinate_debug, debugString); + } + + void debug_data() + { + QTest::addColumn("c"); + QTest::addColumn("nextValue"); + QTest::addColumn("debugString"); + + + QTest::newRow("uninitialized") << QGeoCoordinate() << 45 + << QByteArray("QGeoCoordinate(?, ?) 45"); + QTest::newRow("initialized without altitude") << BRISBANE << 45 + << (QString("QGeoCoordinate(%1, %2) 45").arg(BRISBANE.latitude()) + .arg(BRISBANE.longitude())).toLatin1(); + QTest::newRow("invalid initialization") << QGeoCoordinate(-100,-200) << 45 + << QByteArray("QGeoCoordinate(?, ?) 45"); + QTest::newRow("initialized with altitude") << QGeoCoordinate(1,2,3) << 45 + << QByteArray("QGeoCoordinate(1, 2, 3) 45"); + } + + void hash() + { + uint s1 = qHash(QGeoCoordinate(1, 1, 2)); + uint s2 = qHash(QGeoCoordinate(2, 1, 1)); + uint s3 = qHash(QGeoCoordinate(1, 2, 1)); + uint s10 = qHash(QGeoCoordinate(0, 0, 2)); + uint s20 = qHash(QGeoCoordinate(2, 0, 0)); + uint s30 = qHash(QGeoCoordinate(0, 2, 0)); + uint s30NoAlt = qHash(QGeoCoordinate(0, 2)); + uint s30WithSeed = qHash(QGeoCoordinate(0, 2, 0), 1); + uint nullCoordinate = qHash(QGeoCoordinate()); + + uint north1 = qHash(QGeoCoordinate(90.0, 34.7, 0)); + uint north2 = qHash(QGeoCoordinate(90.0, 180, 0)); + + uint south1 = qHash(QGeoCoordinate(90.0, 67.7, 34.0)); + uint south2 = qHash(QGeoCoordinate(90.0, 111, 34.0)); + + QVERIFY(s1 != s2); + QVERIFY(s2 != s3); + QVERIFY(s1 != s3); + QVERIFY(s10 != s20); + QVERIFY(s20 != s30); + QVERIFY(s10 != s30); + QVERIFY(s30NoAlt != s30); + QVERIFY(s30WithSeed != s30); + + QVERIFY(nullCoordinate != s1); + QVERIFY(nullCoordinate != s2); + QVERIFY(nullCoordinate != s3); + QVERIFY(nullCoordinate != s10); + QVERIFY(nullCoordinate != s20); + QVERIFY(nullCoordinate != s30); + QVERIFY(nullCoordinate != s30NoAlt); + QVERIFY(nullCoordinate != s30WithSeed); + + QVERIFY(north1 == north2); + QVERIFY(south1 == south2); + } +}; + +QTEST_GUILESS_MAIN(tst_QGeoCoordinate) +#include "tst_qgeocoordinate.moc" diff --git a/tests/auto/qgeolocation/qgeolocation.pro b/tests/auto/qgeolocation/qgeolocation.pro new file mode 100644 index 0000000..3b5b3a3 --- /dev/null +++ b/tests/auto/qgeolocation/qgeolocation.pro @@ -0,0 +1,9 @@ +CONFIG += testcase +TARGET = tst_qgeolocation + +HEADERS += ../utils/qlocationtestutils_p.h \ + tst_qgeolocation.h +SOURCES += tst_qgeolocation.cpp \ + ../utils/qlocationtestutils.cpp + +QT += positioning testlib diff --git a/tests/auto/qgeolocation/tst_qgeolocation.cpp b/tests/auto/qgeolocation/tst_qgeolocation.cpp new file mode 100644 index 0000000..9e40b0b --- /dev/null +++ b/tests/auto/qgeolocation/tst_qgeolocation.cpp @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tst_qgeolocation.h" + +QT_USE_NAMESPACE + +tst_QGeoLocation::tst_QGeoLocation() +{ +} + +void tst_QGeoLocation::initTestCase() +{ + +} + +void tst_QGeoLocation::cleanupTestCase() +{ + +} + +void tst_QGeoLocation::init() +{ +} + +void tst_QGeoLocation::cleanup() +{ +} + +void tst_QGeoLocation::constructor() +{ + QCOMPARE(m_location.address(), m_address); + QCOMPARE(m_location.coordinate(), m_coordinate); + QCOMPARE(m_location.boundingBox(), m_viewport); +} + +void tst_QGeoLocation::copy_constructor() +{ + QGeoLocation *qgeolocationcopy = new QGeoLocation (m_location); + QCOMPARE(m_location, *qgeolocationcopy); + delete qgeolocationcopy; +} + +void tst_QGeoLocation::destructor() +{ + QGeoLocation *qgeolocationcopy; + + qgeolocationcopy = new QGeoLocation(); + delete qgeolocationcopy; + + qgeolocationcopy = new QGeoLocation(m_location); + delete qgeolocationcopy; +} + +void tst_QGeoLocation::address() +{ + m_address.setCity("Berlin"); + m_address.setCountry("Germany"); + m_address.setCountryCode("DEU"); + m_address.setDistrict("Mitte"); + m_address.setPostalCode("10115"); + m_address.setStreet("Invalidenstrasse"); + + m_location.setAddress(m_address); + + QCOMPARE(m_location.address(),m_address); + + m_address.setPostalCode("10125"); + QVERIFY(m_location.address() != m_address); +} + +void tst_QGeoLocation::coordinate() +{ + m_coordinate.setLatitude(13.3851); + m_coordinate.setLongitude(52.5312); + m_coordinate.setAltitude(134.23); + + m_location.setCoordinate(m_coordinate); + + QCOMPARE(m_location.coordinate(), m_coordinate); + + m_coordinate.setAltitude(0); + QVERIFY(m_location.coordinate() != m_coordinate); +} + +void tst_QGeoLocation::viewport() +{ + m_coordinate.setLatitude(13.3851); + m_coordinate.setLongitude(52.5312); + + QGeoRectangle qgeoboundingboxcopy(m_coordinate, 0.4, 0.4); + m_location.setBoundingBox(qgeoboundingboxcopy); + + QCOMPARE(m_location.boundingBox(),qgeoboundingboxcopy); + + qgeoboundingboxcopy.setHeight(1); + + QVERIFY(m_location.boundingBox() != qgeoboundingboxcopy); +} + +void tst_QGeoLocation::operators() +{ + QGeoAddress qgeoaddresscopy; + qgeoaddresscopy.setCity("Berlin"); + qgeoaddresscopy.setCountry("Germany"); + qgeoaddresscopy.setCountryCode("DEU"); + + QGeoCoordinate qgeocoordinatecopy (32.324 , 41.324 , 24.55); + + m_address.setCity("Madrid"); + m_address.setCountry("Spain"); + m_address.setCountryCode("SPA"); + + m_coordinate.setLatitude(21.3434); + m_coordinate.setLongitude(38.43443); + m_coordinate.setAltitude(634.21); + + m_location.setAddress(m_address); + m_location.setCoordinate(m_coordinate); + + //Create a copy and see that they are the same + QGeoLocation qgeolocationcopy(m_location); + QVERIFY(m_location == qgeolocationcopy); + QVERIFY(!(m_location != qgeolocationcopy)); + + //Modify one and test if they are different + qgeolocationcopy.setAddress(qgeoaddresscopy); + QVERIFY(!(m_location == qgeolocationcopy)); + QVERIFY(m_location != qgeolocationcopy); + qgeolocationcopy.setCoordinate(qgeocoordinatecopy); + QVERIFY(!(m_location == qgeolocationcopy)); + QVERIFY(m_location != qgeolocationcopy); + + //delete qgeolocationcopy; + //Asign and test that they are the same + qgeolocationcopy = m_location; + QVERIFY(m_location ==qgeolocationcopy); + QVERIFY(!(m_location != qgeolocationcopy)); +} + +void tst_QGeoLocation::comparison() +{ + QFETCH(QString, dataField); + + QGeoLocation location; + + //set address + QGeoAddress address; + address.setStreet("21 jump st"); + address.setCountry("USA"); + location.setAddress(address); + + //set coordinate + location.setCoordinate(QGeoCoordinate(5,10)); + + //set viewport + location.setBoundingBox(QGeoRectangle(QGeoCoordinate(5,5),0.4,0.4)); + + QGeoLocation otherLocation(location); + + if (dataField == "no change") { + QCOMPARE(location, otherLocation); + } else { + if (dataField == "address") { + QGeoAddress otherAddress; + otherAddress.setStreet("42 evergreen tce"); + otherAddress.setCountry("USA"); + otherLocation.setAddress(otherAddress); + } else if (dataField == "coordinate") { + otherLocation.setCoordinate(QGeoCoordinate(12,13)); + } else if (dataField == "viewport"){ + otherLocation.setBoundingBox(QGeoRectangle(QGeoCoordinate(1,2), 0.5,0.5)); + } else { + qFatal("Unknown data field to test"); + } + + QVERIFY(location != otherLocation); + } +} + +void tst_QGeoLocation::comparison_data() +{ + QTest::addColumn ("dataField"); + QTest::newRow("no change") << "no change"; + QTest::newRow("address") << "address"; + QTest::newRow("coordinate") << "coordinate"; +} + +void tst_QGeoLocation::isEmpty() +{ + QGeoAddress address; + address.setCity(QStringLiteral("Braunschweig")); + QVERIFY(!address.isEmpty()); + + QGeoRectangle boundingBox; + boundingBox.setTopLeft(QGeoCoordinate(1, -1)); + boundingBox.setBottomRight(QGeoCoordinate(-1, 1)); + QVERIFY(!boundingBox.isEmpty()); + + QGeoLocation location; + + QVERIFY(location.isEmpty()); + + // address + location.setAddress(address); + QVERIFY(!location.isEmpty()); + location.setAddress(QGeoAddress()); + QVERIFY(location.isEmpty()); + + // coordinate + location.setCoordinate(QGeoCoordinate(1, 2)); + QVERIFY(!location.isEmpty()); + location.setCoordinate(QGeoCoordinate()); + QVERIFY(location.isEmpty()); + + // bounding box + location.setBoundingBox(boundingBox); + QVERIFY(!location.isEmpty()); + location.setBoundingBox(QGeoRectangle()); + QVERIFY(location.isEmpty()); +} + +QTEST_APPLESS_MAIN(tst_QGeoLocation); + diff --git a/tests/auto/qgeolocation/tst_qgeolocation.h b/tests/auto/qgeolocation/tst_qgeolocation.h new file mode 100644 index 0000000..182cad2 --- /dev/null +++ b/tests/auto/qgeolocation/tst_qgeolocation.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_QGEOLOCATION_H +#define TST_QGEOLOCATION_H + +#include +#include +#include +#include + +#include "../utils/qlocationtestutils_p.h" +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QGeoLocation : public QObject +{ + Q_OBJECT + +public: + tst_QGeoLocation(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + //Start Unit Tests for qgeolocation.h + void constructor(); + void copy_constructor(); + void destructor(); + void address(); + void coordinate(); + void viewport(); + void operators(); + void comparison(); + void comparison_data(); + void isEmpty(); + //End Unit Tests for qgeolocation.h + +private: + QGeoLocation m_location; + + QGeoAddress m_address; + QGeoCoordinate m_coordinate; + QGeoRectangle m_viewport; +}; + +Q_DECLARE_METATYPE( QGeoCoordinate::CoordinateFormat); +Q_DECLARE_METATYPE( QGeoCoordinate::CoordinateType); +Q_DECLARE_METATYPE( QList); + +#endif + diff --git a/tests/auto/qgeomaneuver/qgeomaneuver.pro b/tests/auto/qgeomaneuver/qgeomaneuver.pro new file mode 100644 index 0000000..670d0cd --- /dev/null +++ b/tests/auto/qgeomaneuver/qgeomaneuver.pro @@ -0,0 +1,11 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeomaneuver + +# Input +HEADERS += ../utils/qlocationtestutils_p.h \ + tst_qgeomaneuver.h +SOURCES += tst_qgeomaneuver.cpp \ + ../utils/qlocationtestutils.cpp + +QT += location testlib diff --git a/tests/auto/qgeomaneuver/tst_qgeomaneuver.cpp b/tests/auto/qgeomaneuver/tst_qgeomaneuver.cpp new file mode 100644 index 0000000..25d5bf7 --- /dev/null +++ b/tests/auto/qgeomaneuver/tst_qgeomaneuver.cpp @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tst_qgeomaneuver.h" + + +tst_QGeoManeuver::tst_QGeoManeuver() +{ +} + +void tst_QGeoManeuver::initTestCase() +{ + +} + +void tst_QGeoManeuver::cleanupTestCase() +{ + +} + +void tst_QGeoManeuver::init() +{ + + qgeomaneuver = new QGeoManeuver(); +} + +void tst_QGeoManeuver::cleanup() +{ + delete qgeomaneuver; +} + +void tst_QGeoManeuver::constructor() +{ + QString empty =""; + + QVERIFY(!qgeomaneuver->isValid()); + QCOMPARE(qgeomaneuver->direction(),QGeoManeuver::NoDirection); + QCOMPARE(qgeomaneuver->distanceToNextInstruction(), qreal(0.0)); + QCOMPARE(qgeomaneuver->instructionText(),empty); + QCOMPARE(qgeomaneuver->timeToNextInstruction(),0); +} + +void tst_QGeoManeuver::copy_constructor() +{ + QGeoManeuver *qgeomaneuvercopy = new QGeoManeuver (*qgeomaneuver); + + QCOMPARE(*qgeomaneuver,*qgeomaneuvercopy); + + delete qgeomaneuvercopy; +} + +void tst_QGeoManeuver::destructor() +{ + QGeoManeuver *qgeomaneuvercopy; + + qgeomaneuvercopy = new QGeoManeuver(); + delete qgeomaneuvercopy; + + qgeomaneuvercopy = new QGeoManeuver(*qgeomaneuver); + delete qgeomaneuvercopy; +} + +void tst_QGeoManeuver::direction() +{ + QFETCH(QGeoManeuver::InstructionDirection,direction); + + qgeomaneuver->setDirection(direction); + + QCOMPARE(qgeomaneuver->direction(),direction); +} +void tst_QGeoManeuver::direction_data() +{ + QTest::addColumn("direction"); + + QTest::newRow("instruction1") << QGeoManeuver::NoDirection; + QTest::newRow("instruction2") << QGeoManeuver::DirectionForward; + QTest::newRow("instruction3") << QGeoManeuver::DirectionBearRight; + QTest::newRow("instruction4") << QGeoManeuver::DirectionLightRight; + QTest::newRow("instruction5") << QGeoManeuver::DirectionRight; + QTest::newRow("instruction6") << QGeoManeuver::DirectionHardRight; + QTest::newRow("instruction7") << QGeoManeuver::DirectionUTurnRight; + QTest::newRow("instruction8") << QGeoManeuver::DirectionUTurnLeft; + QTest::newRow("instruction9") << QGeoManeuver::DirectionHardLeft; + QTest::newRow("instruction10") << QGeoManeuver::DirectionLeft; + QTest::newRow("instruction11") << QGeoManeuver::DirectionLightLeft; + QTest::newRow("instruction12") << QGeoManeuver::DirectionBearLeft; +} + +void tst_QGeoManeuver::distanceToNextInstruction() +{ + qreal distance = 0.0; + qgeomaneuver->setDistanceToNextInstruction(distance); + + QCOMPARE (qgeomaneuver->distanceToNextInstruction(), distance); + + distance = -3423.4324; + + QVERIFY (qgeomaneuver->distanceToNextInstruction() != distance); + + qgeomaneuver->setDistanceToNextInstruction(distance); + QCOMPARE (qgeomaneuver->distanceToNextInstruction(),distance); +} + +void tst_QGeoManeuver::instructionText() +{ + QString text = "After 50m turn left"; + + qgeomaneuver->setInstructionText(text); + + QCOMPARE (qgeomaneuver->instructionText(),text); + + text="After 40m, turn left"; + QVERIFY (qgeomaneuver->instructionText() != text); + +} + +void tst_QGeoManeuver::position() +{ + QFETCH(double, latitude); + QFETCH(double, longitude); + + qgeocoordinate = new QGeoCoordinate (latitude,longitude); + + qgeomaneuver->setPosition(*qgeocoordinate); + + QCOMPARE(qgeomaneuver->position(),*qgeocoordinate); + + delete qgeocoordinate; +} + +void tst_QGeoManeuver::position_data() +{ + QTest::addColumn("latitude"); + QTest::addColumn("longitude"); + + QTest::newRow("invalid0") << -12220.0 << 0.0; + QTest::newRow("invalid1") << 0.0 << 181.0; + + QTest::newRow("correct0") << 0.0 << 0.0; + QTest::newRow("correct1") << 90.0 << 0.0; + QTest::newRow("correct2") << 0.0 << 180.0; + QTest::newRow("correct3") << -90.0 << 0.0; + QTest::newRow("correct4") << 0.0 << -180.0; + QTest::newRow("correct5") << 45.0 << 90.0; +} + +void tst_QGeoManeuver::timeToNextInstruction() +{ + int time = 0; + qgeomaneuver->setTimeToNextInstruction(time); + + QCOMPARE (qgeomaneuver->timeToNextInstruction(),time); + + time = 35; + + QVERIFY (qgeomaneuver->timeToNextInstruction() != time); + + qgeomaneuver->setTimeToNextInstruction(time); + QCOMPARE (qgeomaneuver->timeToNextInstruction(),time); +} + +void tst_QGeoManeuver::waypoint() +{ + QFETCH(double, latitude); + QFETCH(double, longitude); + + qgeocoordinate = new QGeoCoordinate (latitude,longitude); + + qgeomaneuver->setWaypoint(*qgeocoordinate); + + QCOMPARE(qgeomaneuver->waypoint(),*qgeocoordinate); + + qgeocoordinate->setLatitude(30.3); + QVERIFY(qgeomaneuver->waypoint() != *qgeocoordinate); + + + delete qgeocoordinate; +} +void tst_QGeoManeuver::waypoint_data() +{ + QTest::addColumn("latitude"); + QTest::addColumn("longitude"); + + QTest::newRow("invalid0") << -12220.0 << 0.0; + QTest::newRow("invalid1") << 0.0 << 181.0; + + QTest::newRow("correct0") << 0.0 << 0.0; + QTest::newRow("correct1") << 90.0 << 0.0; + QTest::newRow("correct2") << 0.0 << 180.0; + QTest::newRow("correct3") << -90.0 << 0.0; + QTest::newRow("correct4") << 0.0 << -180.0; + QTest::newRow("correct5") << 45.0 << 90.0; +} + +void tst_QGeoManeuver::isValid() +{ + QVERIFY(!qgeomaneuver->isValid()); + qgeomaneuver->setDirection(QGeoManeuver::DirectionBearLeft); + QVERIFY(qgeomaneuver->isValid()); +} + +void tst_QGeoManeuver::operators(){ + + QGeoManeuver *qgeomaneuvercopy = new QGeoManeuver(*qgeomaneuver); + + QVERIFY(qgeomaneuver->operator ==(*qgeomaneuvercopy)); + QVERIFY(!qgeomaneuver->operator !=(*qgeomaneuvercopy)); + + qgeomaneuver->setDirection(QGeoManeuver::DirectionBearLeft); + qgeomaneuver->setInstructionText("Turn left in 50m"); + qgeomaneuver->setTimeToNextInstruction(60); + qgeomaneuver->setDistanceToNextInstruction(560.45); + + qgeomaneuvercopy->setDirection(QGeoManeuver::DirectionForward); + qgeomaneuvercopy->setInstructionText("Turn left in 80m"); + qgeomaneuvercopy->setTimeToNextInstruction(70); + qgeomaneuvercopy->setDistanceToNextInstruction(56065.45); + +// Not passing + QVERIFY(!(qgeomaneuver->operator ==(*qgeomaneuvercopy))); +// Not passing + QVERIFY(qgeomaneuver->operator !=(*qgeomaneuvercopy)); + + *qgeomaneuvercopy = qgeomaneuvercopy->operator =(*qgeomaneuver); + QVERIFY(qgeomaneuver->operator ==(*qgeomaneuvercopy)); + QVERIFY(!qgeomaneuver->operator !=(*qgeomaneuvercopy)); + + delete qgeomaneuvercopy; +} + + +QTEST_APPLESS_MAIN(tst_QGeoManeuver); diff --git a/tests/auto/qgeomaneuver/tst_qgeomaneuver.h b/tests/auto/qgeomaneuver/tst_qgeomaneuver.h new file mode 100644 index 0000000..aad28c8 --- /dev/null +++ b/tests/auto/qgeomaneuver/tst_qgeomaneuver.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_QGEOMANEUVER_H +#define TST_QGEOMANEUVER_H + + +#include +#include +#include +#include +#include + +#include "../utils/qlocationtestutils_p.h" +#include +#include + + +class tst_QGeoManeuver : public QObject +{ + Q_OBJECT + +public: + tst_QGeoManeuver(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + //Start unit test for QGeoRouteManeuver + void constructor(); + void copy_constructor(); + void destructor(); + void direction(); + void direction_data(); + void distanceToNextInstruction(); + void instructionText(); + void position(); + void position_data(); + void timeToNextInstruction(); + void waypoint(); + void waypoint_data(); + void isValid(); + void operators(); + //End Unit Test for QGeoRouteManeuver + +private: + QGeoManeuver *qgeomaneuver; + QGeoCoordinate *qgeocoordinate; + +}; + +Q_DECLARE_METATYPE( QList); +Q_DECLARE_METATYPE (QGeoManeuver::InstructionDirection); + +#endif // TST_QGEOMANEUVER_H + diff --git a/tests/auto/qgeopositioninfo/qgeopositioninfo.pro b/tests/auto/qgeopositioninfo/qgeopositioninfo.pro new file mode 100644 index 0000000..f928c8b --- /dev/null +++ b/tests/auto/qgeopositioninfo/qgeopositioninfo.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeopositioninfo + +SOURCES += tst_qgeopositioninfo.cpp + +QT += positioning testlib diff --git a/tests/auto/qgeopositioninfo/tst_qgeopositioninfo.cpp b/tests/auto/qgeopositioninfo/tst_qgeopositioninfo.cpp new file mode 100644 index 0000000..72904e1 --- /dev/null +++ b/tests/auto/qgeopositioninfo/tst_qgeopositioninfo.cpp @@ -0,0 +1,386 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include + +#include +#include +#include +#include +#include + +#include + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QGeoPositionInfo) +Q_DECLARE_METATYPE(QGeoPositionInfo::Attribute) + +QByteArray tst_qgeopositioninfo_debug; + +void tst_qgeopositioninfo_messageHandler(QtMsgType type, const QMessageLogContext&, const QString &msg) +{ + switch (type) { + case QtDebugMsg : + tst_qgeopositioninfo_debug = msg.toLocal8Bit(); + break; + default: + break; + } +} + +QList tst_qgeopositioninfo_qrealTestValues() +{ + QList values; + + if (qreal(DBL_MIN) == DBL_MIN) + values << DBL_MIN; + + values << FLT_MIN; + values << -1.0 << 0.0 << 1.0; + values << FLT_MAX; + + if (qreal(DBL_MAX) == DBL_MAX) + values << DBL_MAX; + + return values; +} + +QList tst_qgeopositioninfo_getAttributes() +{ + QList attributes; + attributes << QGeoPositionInfo::Direction + << QGeoPositionInfo::GroundSpeed + << QGeoPositionInfo::VerticalSpeed + << QGeoPositionInfo::MagneticVariation + << QGeoPositionInfo::HorizontalAccuracy + << QGeoPositionInfo::VerticalAccuracy; + return attributes; +} + + +class tst_QGeoPositionInfo : public QObject +{ + Q_OBJECT + +private: + QGeoPositionInfo infoWithAttribute(QGeoPositionInfo::Attribute attribute, qreal value) + { + QGeoPositionInfo info; + info.setAttribute(attribute, value); + return info; + } + + void addTestData_info() + { + QTest::addColumn("info"); + + QTest::newRow("invalid") << QGeoPositionInfo(); + + QTest::newRow("coord") << QGeoPositionInfo(QGeoCoordinate(-27.3422,150.2342), QDateTime()); + QTest::newRow("datetime") << QGeoPositionInfo(QGeoCoordinate(), QDateTime::currentDateTime()); + + QList attributes = tst_qgeopositioninfo_getAttributes(); + QList values = tst_qgeopositioninfo_qrealTestValues(); + for (int i=0; i("coord"); + QTest::addColumn("dateTime"); + QTest::addColumn("valid"); + + QTest::newRow("both null") << QGeoCoordinate() << QDateTime() << false; + QTest::newRow("both valid") << QGeoCoordinate(1,1) << QDateTime::currentDateTime() << true; + QTest::newRow("valid coord") << QGeoCoordinate(1,1) << QDateTime() << false; + QTest::newRow("valid datetime") << QGeoCoordinate() << QDateTime::currentDateTime() << false; + QTest::newRow("valid time but not date == invalid") + << QGeoCoordinate() << QDateTime(QDate(), QTime::currentTime()) << false; + QTest::newRow("valid date but not time == valid due to QDateTime constructor") + << QGeoCoordinate() << QDateTime(QDate::currentDate(), QTime()) << false; + } + + void constructor_copy() + { + QFETCH(QGeoPositionInfo, info); + + QCOMPARE(QGeoPositionInfo(info), info); + } + + void constructor_copy_data() + { + addTestData_info(); + } + + void operator_assign() + { + QFETCH(QGeoPositionInfo, info); + + QGeoPositionInfo info2 = info; + QCOMPARE(info2, info); + } + + void operator_assign_data() + { + addTestData_info(); + } + + void operator_equals() + { + QFETCH(QGeoPositionInfo, info); + + QVERIFY(info == info); + if (info.isValid()) + QCOMPARE(info == QGeoPositionInfo(), false); + } + + void operator_equals_data() + { + addTestData_info(); + } + + void operator_notEquals() + { + QFETCH(QGeoPositionInfo, info); + + QCOMPARE(info != info, false); + if (info.isValid()) + QCOMPARE(info != QGeoPositionInfo(), true); + } + + void operator_notEquals_data() + { + addTestData_info(); + } + + void setDateTime() + { + QFETCH(QDateTime, dateTime); + + QGeoPositionInfo info; + info.setTimestamp(dateTime); + QCOMPARE(info.timestamp(), dateTime); + } + + void setDateTime_data() + { + QTest::addColumn("dateTime"); + QTest::newRow("invalid") << QDateTime(); + QTest::newRow("now") << QDateTime::currentDateTime(); + } + + void dateTime() + { + QGeoPositionInfo info; + QVERIFY(info.timestamp().isNull()); + } + + void setCoordinate() + { + + QFETCH(QGeoCoordinate, coord); + + QGeoPositionInfo info; + info.setCoordinate(coord); + QCOMPARE(info.coordinate(), coord); + } + + void setCoordinate_data() + { + QTest::addColumn("coord"); + + QTest::newRow("invalid") << QGeoCoordinate(); + QTest::newRow("valid") << QGeoCoordinate(30,30); + } + + void attribute() + { + QFETCH(QGeoPositionInfo::Attribute, attribute); + QFETCH(qreal, value); + + QGeoPositionInfo info; + QVERIFY(qIsNaN(info.attribute(attribute))); + + info.setAttribute(attribute, value); + QCOMPARE(info.attribute(attribute), value); + + info.removeAttribute(attribute); + QVERIFY(qIsNaN(info.attribute(attribute))); + } + + void attribute_data() + { + QTest::addColumn("attribute"); + QTest::addColumn("value"); + + QList attributes = tst_qgeopositioninfo_getAttributes(); + QList values = tst_qgeopositioninfo_qrealTestValues(); + for (int i=0; i> inInfo; + QCOMPARE(inInfo, info); + } + + void datastream_data() + { + addTestData_info(); + } + + void debug() + { + QFETCH(QGeoPositionInfo, info); + QFETCH(int, nextValue); + QFETCH(QByteArray, debugStringEnd); + + qInstallMessageHandler(tst_qgeopositioninfo_messageHandler); + qDebug() << info << nextValue; + qInstallMessageHandler(0); + + // use endsWith() so we don't depend on QDateTime's debug() implementation + QVERIFY2(tst_qgeopositioninfo_debug.endsWith(debugStringEnd), + qPrintable(QString::fromLatin1("'%1' does not end with '%2'"). + arg(QLatin1String(tst_qgeopositioninfo_debug), + QLatin1String(debugStringEnd)))); + } + + void debug_data() + { + QTest::addColumn("info"); + QTest::addColumn("nextValue"); + QTest::addColumn("debugStringEnd"); + + QTest::newRow("no values") << QGeoPositionInfo() << 40 + << QString("QGeoCoordinate(?, ?)) 40").toLatin1(); + + QGeoCoordinate coord(1, 1); + QTest::newRow("coord, time") << QGeoPositionInfo(coord, QDateTime::currentDateTime()) + << 40 << QByteArray("QGeoCoordinate(1, 1)) 40"); + + QGeoPositionInfo info; + info.setAttribute(QGeoPositionInfo::Direction, 1.1); + info.setAttribute(QGeoPositionInfo::GroundSpeed, 2.1); + info.setAttribute(QGeoPositionInfo::VerticalSpeed, 3.1); + info.setAttribute(QGeoPositionInfo::MagneticVariation, 4.1); + info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, 5.1); + info.setAttribute(QGeoPositionInfo::VerticalAccuracy, 6.1); + QTest::newRow("all attributes") << info << 40 + << QByteArray("QGeoCoordinate(?, ?), Direction=1.1, GroundSpeed=2.1, VerticalSpeed=3.1, MagneticVariation=4.1, HorizontalAccuracy=5.1, VerticalAccuracy=6.1) 40"); + } +}; + + +QTEST_APPLESS_MAIN(tst_QGeoPositionInfo) +#include "tst_qgeopositioninfo.moc" diff --git a/tests/auto/qgeopositioninfosource/qgeopositioninfosource.pro b/tests/auto/qgeopositioninfosource/qgeopositioninfosource.pro new file mode 100644 index 0000000..ee49425 --- /dev/null +++ b/tests/auto/qgeopositioninfosource/qgeopositioninfosource.pro @@ -0,0 +1,15 @@ +TEMPLATE = app +CONFIG+=testcase +testcase.timeout = 400 # this test is slow +TARGET=tst_qgeopositioninfosource + +HEADERS += ../utils/qlocationtestutils_p.h \ + testqgeopositioninfosource_p.h + +SOURCES += ../utils/qlocationtestutils.cpp \ + testqgeopositioninfosource.cpp \ + tst_qgeopositioninfosource.cpp + +CONFIG -= app_bundle + +QT += positioning testlib diff --git a/tests/auto/qgeopositioninfosource/testqgeopositioninfosource.cpp b/tests/auto/qgeopositioninfosource/testqgeopositioninfosource.cpp new file mode 100644 index 0000000..31ab86f --- /dev/null +++ b/tests/auto/qgeopositioninfosource/testqgeopositioninfosource.cpp @@ -0,0 +1,777 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "testqgeopositioninfosource_p.h" +#include "../utils/qlocationtestutils_p.h" + +Q_DECLARE_METATYPE(QGeoPositionInfoSource::PositioningMethod) +Q_DECLARE_METATYPE(QGeoPositionInfoSource::PositioningMethods) +Q_DECLARE_METATYPE(QGeoPositionInfo) + +#define MAX_WAITING_TIME 50000 + +// Must provide a valid source, unless testing the source +// returned by QGeoPositionInfoSource::createDefaultSource() on a system +// that has no default source +#define CHECK_SOURCE_VALID { \ + if (!m_source) { \ + if (m_testingDefaultSource && QGeoPositionInfoSource::createDefaultSource(0) == 0) \ + QSKIP("No default position source on this system"); \ + else \ + QFAIL("createTestSource() must return a valid source!"); \ + } \ + } + +class MyPositionSource : public QGeoPositionInfoSource +{ + Q_OBJECT +public: + MyPositionSource(QObject *parent = 0) + : QGeoPositionInfoSource(parent) { + } + + QGeoPositionInfo lastKnownPosition(bool /*fromSatellitePositioningMethodsOnly = false*/) const { + return QGeoPositionInfo(); + } + + void setSupportedPositioningMethods(PositioningMethods methods) { + m_methods = methods; + } + + virtual PositioningMethods supportedPositioningMethods() const { + return m_methods; + } + virtual int minimumUpdateInterval() const { + return 0; + } + + virtual void startUpdates() {} + virtual void stopUpdates() {} + + virtual void requestUpdate(int) {} + + Error error() const { return QGeoPositionInfoSource::NoError; } + +private: + PositioningMethods m_methods; +}; + +class DefaultSourceTest : public TestQGeoPositionInfoSource +{ + Q_OBJECT +protected: + QGeoPositionInfoSource *createTestSource() { + return QGeoPositionInfoSource::createSource(QStringLiteral("test.source"), 0); + } +}; + + +TestQGeoPositionInfoSource::TestQGeoPositionInfoSource(QObject *parent) + : QObject(parent) +{ + m_testingDefaultSource = false; + /* + * Set custom path since CI doesn't install test plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif +} + +TestQGeoPositionInfoSource *TestQGeoPositionInfoSource::createDefaultSourceTest() +{ + DefaultSourceTest *test = new DefaultSourceTest; + test->m_testingDefaultSource = true; + return test; +} + +void TestQGeoPositionInfoSource::test_slot1() +{ +} + +void TestQGeoPositionInfoSource::test_slot2() +{ + m_testSlot2Called = true; +} + +void TestQGeoPositionInfoSource::base_initTestCase() +{ + qRegisterMetaType(); +} + +void TestQGeoPositionInfoSource::base_init() +{ + m_source = createTestSource(); + m_testSlot2Called = false; +} + +void TestQGeoPositionInfoSource::base_cleanup() +{ + delete m_source; + m_source = 0; +} + +void TestQGeoPositionInfoSource::base_cleanupTestCase() +{ +} + +void TestQGeoPositionInfoSource::initTestCase() +{ + base_initTestCase(); +} + +void TestQGeoPositionInfoSource::init() +{ + base_init(); +} + +void TestQGeoPositionInfoSource::cleanup() +{ + base_cleanup(); +} + +void TestQGeoPositionInfoSource::cleanupTestCase() +{ + base_cleanupTestCase(); +} + +// TC_ID_3_x_1 +void TestQGeoPositionInfoSource::constructor_withParent() +{ + QObject *parent = new QObject(); + new MyPositionSource(parent); + delete parent; +} + +// TC_ID_3_x_2 +void TestQGeoPositionInfoSource::constructor_noParent() +{ + MyPositionSource *obj = new MyPositionSource(); + delete obj; +} + +void TestQGeoPositionInfoSource::updateInterval() +{ + MyPositionSource s; + QCOMPARE(s.updateInterval(), 0); +} + +void TestQGeoPositionInfoSource::setPreferredPositioningMethods() +{ + QFETCH(QGeoPositionInfoSource::PositioningMethod, supported); + QFETCH(QGeoPositionInfoSource::PositioningMethod, preferred); + QFETCH(QGeoPositionInfoSource::PositioningMethod, resulting); + + MyPositionSource s; + s.setSupportedPositioningMethods(supported); + s.setPreferredPositioningMethods(preferred); + QCOMPARE(s.preferredPositioningMethods(), resulting); +} + +void TestQGeoPositionInfoSource::setPreferredPositioningMethods_data() +{ + QTest::addColumn("supported"); + QTest::addColumn("preferred"); + QTest::addColumn("resulting"); + + QTest::newRow("Sat supported, Sat preferred") + << QGeoPositionInfoSource::SatellitePositioningMethods + << QGeoPositionInfoSource::SatellitePositioningMethods + << QGeoPositionInfoSource::SatellitePositioningMethods; + QTest::newRow("Sat supported, Non-Sat preferred") + << QGeoPositionInfoSource::SatellitePositioningMethods + << QGeoPositionInfoSource::NonSatellitePositioningMethods + << QGeoPositionInfoSource::SatellitePositioningMethods; + QTest::newRow("Sat supported, All preferred") + << QGeoPositionInfoSource::SatellitePositioningMethods + << QGeoPositionInfoSource::AllPositioningMethods + << QGeoPositionInfoSource::SatellitePositioningMethods; + + QTest::newRow("Non-Sat supported, Sat preferred") + << QGeoPositionInfoSource::NonSatellitePositioningMethods + << QGeoPositionInfoSource::SatellitePositioningMethods + << QGeoPositionInfoSource::NonSatellitePositioningMethods; + QTest::newRow("Non-Sat supported, Non-Sat preferred") + << QGeoPositionInfoSource::NonSatellitePositioningMethods + << QGeoPositionInfoSource::NonSatellitePositioningMethods + << QGeoPositionInfoSource::NonSatellitePositioningMethods; + QTest::newRow("Non-Sat supported, All preferred") + << QGeoPositionInfoSource::NonSatellitePositioningMethods + << QGeoPositionInfoSource::AllPositioningMethods + << QGeoPositionInfoSource::NonSatellitePositioningMethods; + + QTest::newRow("All supported, Sat preferred") + << QGeoPositionInfoSource::AllPositioningMethods + << QGeoPositionInfoSource::SatellitePositioningMethods + << QGeoPositionInfoSource::SatellitePositioningMethods; + QTest::newRow("All supported, Non-Sat preferred") + << QGeoPositionInfoSource::AllPositioningMethods + << QGeoPositionInfoSource::NonSatellitePositioningMethods + << QGeoPositionInfoSource::NonSatellitePositioningMethods; + QTest::newRow("All supported, All preferred") + << QGeoPositionInfoSource::AllPositioningMethods + << QGeoPositionInfoSource::AllPositioningMethods + << QGeoPositionInfoSource::AllPositioningMethods; +} + +void TestQGeoPositionInfoSource::preferredPositioningMethods() +{ + MyPositionSource s; + QCOMPARE(s.preferredPositioningMethods(), 0); +} + +//TC_ID_3_x_1 : Create a position source with the given parent that reads from the system's default +// sources of location data +void TestQGeoPositionInfoSource::createDefaultSource() +{ + QObject *parent = new QObject; + + QGeoPositionInfoSource *source = QGeoPositionInfoSource::createDefaultSource(parent); + // now all platforms have the dummy plugin at least + QVERIFY(source != 0); + delete parent; +} + +void TestQGeoPositionInfoSource::setUpdateInterval() +{ + CHECK_SOURCE_VALID; + + QFETCH(int, interval); + QFETCH(int, expectedInterval); + + m_source->setUpdateInterval(interval); + QCOMPARE(m_source->updateInterval(), expectedInterval); +} + +void TestQGeoPositionInfoSource::setUpdateInterval_data() +{ + QTest::addColumn("interval"); + QTest::addColumn("expectedInterval"); + QGeoPositionInfoSource *source = createTestSource(); + int minUpdateInterval = source ? source->minimumUpdateInterval() : -1; + if (source) + delete source; + + QTest::newRow("0") << 0 << 0; + + if (minUpdateInterval > -1) { + QTest::newRow("INT_MIN") << INT_MIN << minUpdateInterval; + QTest::newRow("-1") << -1 << minUpdateInterval; + } + + if (minUpdateInterval > 0) { + QTest::newRow("more than minInterval") << minUpdateInterval + 1 << minUpdateInterval + 1; + QTest::newRow("equal to minInterval") << minUpdateInterval << minUpdateInterval; + } + + if (minUpdateInterval > 1) { + QTest::newRow("less then minInterval") << minUpdateInterval - 1 << minUpdateInterval; + QTest::newRow("in btw zero and minInterval") << 1 << minUpdateInterval; + } +} + +void TestQGeoPositionInfoSource::lastKnownPosition() +{ + CHECK_SOURCE_VALID; + QFETCH(QGeoPositionInfoSource::PositioningMethod, positioningMethod); + QFETCH(bool, lastKnownPositionArgument); + + if ((m_source->supportedPositioningMethods() & positioningMethod) == 0) + QSKIP("Not a supported positioning method for this position source"); + + m_source->setPreferredPositioningMethods(positioningMethod); + + QSignalSpy spy(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy timeout(m_source, SIGNAL(updateTimeout())); + int time_out = 7000; + m_source->setUpdateInterval(time_out); + m_source->startUpdates(); + + // Use QEventLoop instead of qWait() to ensure we stop as soon as a + // position is emitted (otherwise the lastKnownPosition() may have + // changed by the time it is checked) + QEventLoop loop; + QTimer timer; + //simulated CI tests will quickly return -> real GPS tests take 2 minutes for satellite systems + //use a 5 min timeout + timer.setInterval(300000); + connect(m_source, SIGNAL(positionUpdated(QGeoPositionInfo)), + &loop, SLOT(quit())); + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.start(); + loop.exec(); + + QVERIFY((spy.count() > 0) && (timeout.count() == 0)); + + QList list = spy.takeFirst(); + QGeoPositionInfo info = list.at(0).value(); + QGeoPositionInfo lastPositioninfo = m_source->lastKnownPosition(lastKnownPositionArgument); + + // lastPositioninfo is only gauranteed to be valid in all cases when only using satelite + // positioning methods or when lastKnownPositionArgument is false + if (!lastKnownPositionArgument || + positioningMethod == QGeoPositionInfoSource::SatellitePositioningMethods) { + QVERIFY(lastPositioninfo.isValid()); + } + + if (lastPositioninfo.isValid()) { + QCOMPARE(info.coordinate(), lastPositioninfo.coordinate()); + // On some CI machines the above evenloop code is not sufficient as positionUpdated + // still fires causing last know position and last update to be out of sync. + // To accommodate we check that the time stamps are no more than 1s apart + // ideally they should be the same + // doesn't work: QCOMPARE(info.timestamp(), lastPositioninfo.timestamp()); + const qint64 diff = qAbs(info.timestamp().msecsTo(lastPositioninfo.timestamp())); + QCOMPARE(diff < 1000, true); + + QCOMPARE(info.hasAttribute(QGeoPositionInfo::HorizontalAccuracy), + lastPositioninfo.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)); + + if (info.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) { + bool isNaN1 = qIsNaN(info.attribute(QGeoPositionInfo::HorizontalAccuracy)); + bool isNaN2 = qIsNaN(lastPositioninfo.attribute(QGeoPositionInfo::HorizontalAccuracy)); + QCOMPARE(isNaN1, isNaN2); + if (!isNaN1) { + QCOMPARE(qFuzzyCompare(info.attribute(QGeoPositionInfo::HorizontalAccuracy), + lastPositioninfo.attribute(QGeoPositionInfo::HorizontalAccuracy)), true); + } + } + + QCOMPARE(info.hasAttribute(QGeoPositionInfo::VerticalAccuracy), + lastPositioninfo.hasAttribute(QGeoPositionInfo::VerticalAccuracy)); + + if (info.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) { + bool isNaN1 = qIsNaN(info.attribute(QGeoPositionInfo::VerticalAccuracy)); + bool isNaN2 = qIsNaN(lastPositioninfo.attribute(QGeoPositionInfo::VerticalAccuracy)); + QCOMPARE(isNaN1, isNaN2); + if (!isNaN1) { + QCOMPARE(qFuzzyCompare(info.attribute(QGeoPositionInfo::VerticalAccuracy), + lastPositioninfo.attribute(QGeoPositionInfo::VerticalAccuracy)), true); + } + } + } + + m_source->stopUpdates(); +} + +void TestQGeoPositionInfoSource::lastKnownPosition_data() +{ + QTest::addColumn("positioningMethod"); + QTest::addColumn("lastKnownPositionArgument"); + + // no good way to determine on MeeGo what are supported. If we ask for all or non-satellites, we + // typically get geoclue-example provider, which is not suitable for this test. + QTest::newRow("all - false") << QGeoPositionInfoSource::AllPositioningMethods << false; + QTest::newRow("all - true") << QGeoPositionInfoSource::AllPositioningMethods << true; + QTest::newRow("satellite - false") << QGeoPositionInfoSource::SatellitePositioningMethods << false; + QTest::newRow("satellite - true") << QGeoPositionInfoSource::SatellitePositioningMethods << true; +} + +void TestQGeoPositionInfoSource::minimumUpdateInterval() +{ + CHECK_SOURCE_VALID; + + QVERIFY(m_source->minimumUpdateInterval() > 0); +} + +//TC_ID_3_x_1 +void TestQGeoPositionInfoSource::startUpdates_testIntervals() +{ + CHECK_SOURCE_VALID; + QSignalSpy spy(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy timeout(m_source, SIGNAL(updateTimeout())); + m_source->setUpdateInterval(7000); + int interval = m_source->updateInterval(); + + m_source->startUpdates(); + + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 9500); + for (int i = 0; i < 6; i++) { + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 1) && (timeout.count() == 0), (interval*2)); + spy.clear(); + } + + m_source->stopUpdates(); +} + + +void TestQGeoPositionInfoSource::startUpdates_testIntervalChangesWhileRunning() +{ + // There are two ways of dealing with an interval change, and we have left it system dependent. + // The interval can be changed will running or after the next update. + // WinCE uses the first method, S60 uses the second method. + + // The minimum interval on the symbian emulator is 5000 msecs, which is why the times in + // this test are as high as they are. + + CHECK_SOURCE_VALID; + + QSignalSpy spy(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy timeout(m_source, SIGNAL(updateTimeout())); + m_source->setUpdateInterval(0); + m_source->startUpdates(); + m_source->setUpdateInterval(0); + + QTRY_VERIFY_WITH_TIMEOUT(spy.count() > 0, 7000); + QCOMPARE(timeout.count(), 0); + spy.clear(); + + m_source->setUpdateInterval(5000); + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 2) && (timeout.count() == 0), 15000); + spy.clear(); + + m_source->setUpdateInterval(10000); + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 2) && (timeout.count() == 0), 30000); + spy.clear(); + + m_source->setUpdateInterval(5000); + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 2) && (timeout.count() == 0), 15000); + spy.clear(); + + m_source->setUpdateInterval(5000); + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 2) && (timeout.count() == 0), 15000); + spy.clear(); + + m_source->setUpdateInterval(0); + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 1) && (timeout.count() == 0), 7000); + spy.clear(); + + m_source->setUpdateInterval(0); + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 1) && (timeout.count() == 0), 7000); + spy.clear(); + + m_source->stopUpdates(); +} + +//TC_ID_3_x_2 +void TestQGeoPositionInfoSource::startUpdates_testDefaultInterval() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spy(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy timeout(m_source, SIGNAL(updateTimeout())); + m_source->startUpdates(); + for (int i = 0; i < 3; i++) { + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() > 0) && (timeout.count() == 0), 7000); + spy.clear(); + } + m_source->stopUpdates(); +} + +//TC_ID_3_x_3 +void TestQGeoPositionInfoSource::startUpdates_testZeroInterval() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spy(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy timeout(m_source, SIGNAL(updateTimeout())); + m_source->setUpdateInterval(0); + m_source->startUpdates(); + for (int i = 0; i < 3; i++) { + QTRY_VERIFY_WITH_TIMEOUT((spy.count() > 0) && (timeout.count() == 0), 7000); + spy.clear(); + } + m_source->stopUpdates(); +} + +void TestQGeoPositionInfoSource::startUpdates_moreThanOnce() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spy(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy timeout(m_source, SIGNAL(updateTimeout())); + m_source->setUpdateInterval(0); + m_source->startUpdates(); + + m_source->startUpdates(); // check there is no crash + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() > 0) && (timeout.count() == 0), 7000); + + m_source->startUpdates(); // check there is no crash + + m_source->stopUpdates(); +} + +//TC_ID_3_x_1 +void TestQGeoPositionInfoSource::stopUpdates() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spy(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy timeout(m_source, SIGNAL(updateTimeout())); + m_source->setUpdateInterval(7000); + m_source->startUpdates(); + for (int i = 0; i < 2; i++) { + QTRY_VERIFY_WITH_TIMEOUT((spy.count() > 0) && (timeout.count() == 0), 9500); + spy.clear(); + } + m_source->stopUpdates(); + QTest::qWait(9500); + QCOMPARE(spy.count(), 0); + spy.clear(); + + m_source->setUpdateInterval(0); + m_source->startUpdates(); + m_source->stopUpdates(); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 0, 9500); +} + +//TC_ID_3_x_2 +void TestQGeoPositionInfoSource::stopUpdates_withoutStart() +{ + CHECK_SOURCE_VALID; + m_source->stopUpdates(); // check there is no crash +} + +void TestQGeoPositionInfoSource::requestUpdate() +{ + CHECK_SOURCE_VALID; + QFETCH(int, timeout); + QSignalSpy spy(m_source, SIGNAL(updateTimeout())); + m_source->requestUpdate(timeout); + QTRY_COMPARE(spy.count(), 1); +} + +void TestQGeoPositionInfoSource::requestUpdate_data() +{ + QTest::addColumn("timeout"); + QTest::newRow("less than zero") << -1; //TC_ID_3_x_7 +} + +// TC_ID_3_x_1 : Create position source and call requestUpdate with valid timeout value +void TestQGeoPositionInfoSource::requestUpdate_validTimeout() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyUpdate(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(m_source, SIGNAL(updateTimeout())); + + m_source->requestUpdate(7000); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() > 0) && (spyTimeout.count() == 0), 7000); +} + +void TestQGeoPositionInfoSource::requestUpdate_defaultTimeout() +{ + CHECK_SOURCE_VALID; + QSignalSpy spyUpdate(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(m_source, SIGNAL(updateTimeout())); + + m_source->requestUpdate(0); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() > 0) && (spyTimeout.count() == 0), 7000); +} + +// TC_ID_3_x_2 : Create position source and call requestUpdate with a timeout less than +// minimumupdateInterval +void TestQGeoPositionInfoSource::requestUpdate_timeoutLessThanMinimumInterval() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyTimeout(m_source, SIGNAL(updateTimeout())); + m_source->requestUpdate(1); + + QTRY_COMPARE_WITH_TIMEOUT(spyTimeout.count(), 1, 1000); +} + +// TC_ID_3_x_3 : Call requestUpdate() with same value repeatedly +void TestQGeoPositionInfoSource::requestUpdate_repeatedCalls() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyUpdate(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(m_source, SIGNAL(updateTimeout())); + + m_source->requestUpdate(7000); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() > 0) && (spyTimeout.count() == 0), 7000); + spyUpdate.clear(); + m_source->requestUpdate(7000); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() > 0) && (spyTimeout.count() == 0), 7000); +} + +void TestQGeoPositionInfoSource::requestUpdate_overlappingCalls() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyUpdate(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(m_source, SIGNAL(updateTimeout())); + + m_source->requestUpdate(7000); + m_source->requestUpdate(7000); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() > 0) && (spyTimeout.count() == 0), 7000); +} + +//TC_ID_3_x_4 +void TestQGeoPositionInfoSource::requestUpdateAfterStartUpdates_ZeroInterval() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyUpdate(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(m_source, SIGNAL(updateTimeout())); + + m_source->setUpdateInterval(0); + m_source->startUpdates(); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() > 0) && (spyTimeout.count() == 0), 7000); + spyUpdate.clear(); + + m_source->requestUpdate(7000); + QTest::qWait(7000); + + QVERIFY((spyUpdate.count() > 0) && (spyTimeout.count() == 0)); + spyUpdate.clear(); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() > 0) && (spyTimeout.count() == 0), MAX_WAITING_TIME); + + m_source->stopUpdates(); +} + +void TestQGeoPositionInfoSource::requestUpdateAfterStartUpdates_SmallInterval() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyUpdate(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(m_source, SIGNAL(updateTimeout())); + + m_source->setUpdateInterval(10000); + m_source->startUpdates(); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() == 1) && (spyTimeout.count() == 0), 20000); + spyUpdate.clear(); + + m_source->requestUpdate(7000); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() == 1) && (spyTimeout.count() == 0), 7000); + spyUpdate.clear(); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() == 1) && (spyTimeout.count() == 0), 20000); + + m_source->stopUpdates(); +} + +void TestQGeoPositionInfoSource::requestUpdateBeforeStartUpdates_ZeroInterval() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyUpdate(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(m_source, SIGNAL(updateTimeout())); + + m_source->requestUpdate(7000); + + m_source->setUpdateInterval(0); + m_source->startUpdates(); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() >= 2) && (spyTimeout.count() == 0), 14000); + spyUpdate.clear(); + + QTest::qWait(7000); + + QCOMPARE(spyTimeout.count(), 0); + + m_source->stopUpdates(); +} + +void TestQGeoPositionInfoSource::requestUpdateBeforeStartUpdates_SmallInterval() +{ + CHECK_SOURCE_VALID; + QSignalSpy spyUpdate(m_source, SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(m_source, SIGNAL(updateTimeout())); + + m_source->requestUpdate(7000); + + m_source->setUpdateInterval(10000); + m_source->startUpdates(); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() > 0) && (spyTimeout.count() == 0), 7000); + spyUpdate.clear(); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() > 0) && (spyTimeout.count() == 0), 20000); + + m_source->stopUpdates(); +} + +void TestQGeoPositionInfoSource::removeSlotForRequestTimeout() +{ + CHECK_SOURCE_VALID; + + bool i = connect(m_source, SIGNAL(updateTimeout()), this, SLOT(test_slot1())); + QVERIFY(i == true); + i = connect(m_source, SIGNAL(updateTimeout()), this, SLOT(test_slot2())); + QVERIFY(i == true); + i = disconnect(m_source, SIGNAL(updateTimeout()), this, SLOT(test_slot1())); + QVERIFY(i == true); + + m_source->requestUpdate(-1); + QTRY_VERIFY_WITH_TIMEOUT((m_testSlot2Called == true), 1000); +} + +void TestQGeoPositionInfoSource::removeSlotForPositionUpdated() +{ + CHECK_SOURCE_VALID; + + bool i = connect(m_source, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(test_slot1())); + QVERIFY(i == true); + i = connect(m_source, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(test_slot2())); + QVERIFY(i == true); + i = disconnect(m_source, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(test_slot1())); + QVERIFY(i == true); + + m_source->requestUpdate(7000); + + QTRY_VERIFY_WITH_TIMEOUT((m_testSlot2Called == true), 7000); +} + +#include "testqgeopositioninfosource.moc" diff --git a/tests/auto/qgeopositioninfosource/testqgeopositioninfosource_p.h b/tests/auto/qgeopositioninfosource/testqgeopositioninfosource_p.h new file mode 100644 index 0000000..7ee23f8 --- /dev/null +++ b/tests/auto/qgeopositioninfosource/testqgeopositioninfosource_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TESTQGEOPOSITIONINFOSOURCE_P_H +#define TESTQGEOPOSITIONINFOSOURCE_P_H + +#include + +#ifdef TST_GEOCLUEMOCK_ENABLED +#include "geocluemock.h" +#include +#endif + +#include +#include + +QT_BEGIN_NAMESPACE +class QGeoPositionInfoSource; +QT_END_NAMESPACE + +class TestQGeoPositionInfoSource : public QObject +{ + Q_OBJECT + +public: + TestQGeoPositionInfoSource(QObject *parent = 0); + + static TestQGeoPositionInfoSource *createDefaultSourceTest(); + +public slots: + void test_slot1(); + void test_slot2(); + +protected: + virtual QGeoPositionInfoSource *createTestSource() = 0; + + // MUST be called by subclasses if they override respective test slots + void base_initTestCase(); + void base_init(); + void base_cleanup(); + void base_cleanupTestCase(); + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void cleanupTestCase(); + + void constructor_withParent(); + + void constructor_noParent(); + + void updateInterval(); + + void setPreferredPositioningMethods(); + void setPreferredPositioningMethods_data(); + + void preferredPositioningMethods(); + + void createDefaultSource(); + + void setUpdateInterval(); + void setUpdateInterval_data(); + + void lastKnownPosition(); + void lastKnownPosition_data(); + + void minimumUpdateInterval(); + + void startUpdates_testIntervals(); + void startUpdates_testIntervalChangesWhileRunning(); + void startUpdates_testDefaultInterval(); + void startUpdates_testZeroInterval(); + void startUpdates_moreThanOnce(); + + void stopUpdates(); + void stopUpdates_withoutStart(); + + void requestUpdate(); + void requestUpdate_data(); + + void requestUpdate_validTimeout(); + void requestUpdate_defaultTimeout(); + void requestUpdate_timeoutLessThanMinimumInterval(); + void requestUpdate_repeatedCalls(); + void requestUpdate_overlappingCalls(); + + void requestUpdateAfterStartUpdates_ZeroInterval(); + void requestUpdateAfterStartUpdates_SmallInterval(); + void requestUpdateBeforeStartUpdates_ZeroInterval(); + void requestUpdateBeforeStartUpdates_SmallInterval(); + + void removeSlotForRequestTimeout(); + void removeSlotForPositionUpdated(); + +private: + QGeoPositionInfoSource *m_source; + bool m_testingDefaultSource; + bool m_testSlot2Called; +}; + +#endif diff --git a/tests/auto/qgeopositioninfosource/tst_qgeopositioninfosource.cpp b/tests/auto/qgeopositioninfosource/tst_qgeopositioninfosource.cpp new file mode 100644 index 0000000..105778e --- /dev/null +++ b/tests/auto/qgeopositioninfosource/tst_qgeopositioninfosource.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "testqgeopositioninfosource_p.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + TestQGeoPositionInfoSource *test = TestQGeoPositionInfoSource::createDefaultSourceTest(); + int ret = QTest::qExec(test, argc, argv); + delete test; // keep valgrind happy + return ret; +} diff --git a/tests/auto/qgeorectangle/qgeorectangle.pro b/tests/auto/qgeorectangle/qgeorectangle.pro new file mode 100644 index 0000000..9cb635e --- /dev/null +++ b/tests/auto/qgeorectangle/qgeorectangle.pro @@ -0,0 +1,8 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qgeorectangle + +SOURCES += \ + tst_qgeorectangle.cpp + +QT += positioning testlib diff --git a/tests/auto/qgeorectangle/tst_qgeorectangle.cpp b/tests/auto/qgeorectangle/tst_qgeorectangle.cpp new file mode 100644 index 0000000..71a3765 --- /dev/null +++ b/tests/auto/qgeorectangle/tst_qgeorectangle.cpp @@ -0,0 +1,2351 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QGeoRectangle : public QObject +{ + Q_OBJECT + +private slots: + void default_constructor(); + void center_constructor(); + void corner_constructor(); + void list_constructor(); + void copy_constructor(); + void assignment(); + void destructor(); + + void equality(); + void equality_data(); + + void isValid(); + void isValid_data(); + + void isEmpty(); + void isEmpty_data(); + + void corners(); + void corners_data(); + + void setCorners(); + + void width(); + void width_data(); + + void height(); + void height_data(); + + void center(); + void center_data(); + + void containsCoord(); + void containsCoord_data(); + + void containsBoxAndIntersects(); + void containsBoxAndIntersects_data(); + + void translate(); + void translate_data(); + + void unite(); + void unite_data(); + + void extendShape(); + void extendShape_data(); + + void areaComparison(); + void areaComparison_data(); + + void circleComparison(); + void circleComparison_data(); +}; + +void tst_QGeoRectangle::default_constructor() +{ + QGeoRectangle box; + QCOMPARE(box.topLeft().isValid(), false); + QCOMPARE(box.bottomRight().isValid(), false); +} + +void tst_QGeoRectangle::center_constructor() +{ + QGeoRectangle b1 = QGeoRectangle(QGeoCoordinate(5.0, 5.0), 10.0, 10.0); + + QCOMPARE(b1.topLeft(), QGeoCoordinate(10.0, 0.0)); + QCOMPARE(b1.bottomRight(), QGeoCoordinate(0.0, 10.0)); +} + +void tst_QGeoRectangle::corner_constructor() +{ + QGeoRectangle b1 = QGeoRectangle(QGeoCoordinate(10.0, 0.0), + QGeoCoordinate(0.0, 10.0)); + + QCOMPARE(b1.topLeft(), QGeoCoordinate(10.0, 0.0)); + QCOMPARE(b1.bottomRight(), QGeoCoordinate(0.0, 10.0)); +} + +void tst_QGeoRectangle::list_constructor() +{ + QList coordinates; + QGeoRectangle b1 = QGeoRectangle(coordinates); + QCOMPARE(b1.isValid(), false); + + coordinates << QGeoCoordinate(10.0, 0.0); + b1 = QGeoRectangle(coordinates); + QCOMPARE(b1.isValid(), true); + QCOMPARE(b1.isEmpty(), true); + + coordinates << QGeoCoordinate(0.0, 10.0) << QGeoCoordinate(0.0, 5.0); + b1 = QGeoRectangle(coordinates); + QCOMPARE(b1.topLeft(), QGeoCoordinate(10.0,0.0)); + QCOMPARE(b1.bottomRight(), QGeoCoordinate(0.0, 10.0)); +} + +void tst_QGeoRectangle::copy_constructor() +{ + QGeoRectangle b1 = QGeoRectangle(QGeoCoordinate(10.0, 0.0), + QGeoCoordinate(0.0, 10.0)); + QGeoRectangle b2 = QGeoRectangle(b1); + + QCOMPARE(b2.topLeft(), QGeoCoordinate(10.0, 0.0)); + QCOMPARE(b2.bottomRight(), QGeoCoordinate(0.0, 10.0)); + + b2.setTopLeft(QGeoCoordinate(30.0, 0.0)); + b2.setBottomRight(QGeoCoordinate(0.0, 30.0)); + QCOMPARE(b1.topLeft(), QGeoCoordinate(10.0, 0.0)); + QCOMPARE(b1.bottomRight(), QGeoCoordinate(0.0, 10.0)); + + QGeoShape area; + QGeoRectangle areaBox(area); + QVERIFY(!areaBox.isValid()); + QVERIFY(areaBox.isEmpty()); + + QGeoCircle circle; + QGeoRectangle circleBox(circle); + QVERIFY(!circleBox.isValid()); + QVERIFY(circleBox.isEmpty()); +} + +void tst_QGeoRectangle::destructor() +{ + QGeoRectangle *box = new QGeoRectangle(); + delete box; + // checking for a crash +} + +void tst_QGeoRectangle::assignment() +{ + QGeoRectangle b1 = QGeoRectangle(QGeoCoordinate(10.0, 0.0), + QGeoCoordinate(0.0, 10.0)); + QGeoRectangle b2 = QGeoRectangle(QGeoCoordinate(20.0, 0.0), + QGeoCoordinate(0.0, 20.0)); + + QVERIFY(b1 != b2); + + b2 = b1; + QCOMPARE(b2.topLeft(), QGeoCoordinate(10.0, 0.0)); + QCOMPARE(b2.bottomRight(), QGeoCoordinate(0.0, 10.0)); + QCOMPARE(b1, b2); + + b2.setTopLeft(QGeoCoordinate(30.0, 0.0)); + b2.setBottomRight(QGeoCoordinate(0.0, 30.0)); + QCOMPARE(b1.topLeft(), QGeoCoordinate(10.0, 0.0)); + QCOMPARE(b1.bottomRight(), QGeoCoordinate(0.0, 10.0)); + + // Assign b1 to an area + QGeoShape area = b1; + QCOMPARE(area.type(), b1.type()); + QVERIFY(area == b1); + + // Assign the area back to a bounding box + QGeoRectangle ba = area; + QCOMPARE(ba.topLeft(), b1.topLeft()); + QCOMPARE(ba.bottomRight(), b1.bottomRight()); + + // Check that the copy is not modified when modifying the original. + b1.setTopLeft(QGeoCoordinate(80, 30)); + QVERIFY(ba.topLeft() != b1.topLeft()); + QVERIFY(ba != b1); +} + +void tst_QGeoRectangle::equality() +{ + QFETCH(QGeoRectangle, box1); + QFETCH(QGeoRectangle, box2); + QFETCH(QGeoShape, area1); + QFETCH(QGeoShape, area2); + QFETCH(bool, equal); + + // compare boxes + QCOMPARE((box1 == box2), equal); + QCOMPARE((box1 != box2), !equal); + + // compare areas + QCOMPARE((area1 == area2), equal); + QCOMPARE((area1 != area2), !equal); + + // compare area to box + QCOMPARE((area1 == box2), equal); + QCOMPARE((area1 != box2), !equal); + + // compare box to area + QCOMPARE((box1 == area2), equal); + QCOMPARE((box1 != area2), !equal); +} + +void tst_QGeoRectangle::equality_data() +{ + QTest::addColumn("box1"); + QTest::addColumn("box2"); + QTest::addColumn("area1"); + QTest::addColumn("area2"); + QTest::addColumn("equal"); + + QGeoCoordinate c1(10, 5); + QGeoCoordinate c2(5, 10); + QGeoCoordinate c3(20, 15); + QGeoCoordinate c4(15, 20); + + QGeoRectangle b1(c1, c2); + QGeoRectangle b2(c3, c4); + QGeoRectangle b3(c3, c2); + QGeoRectangle b4(c1, c3); + QGeoRectangle b5(c1, c2); + + QGeoShape a1(b1); + QGeoShape a2(b2); + QGeoShape a3(b3); + QGeoShape a4(b4); + QGeoShape a5(b5); + + QTest::newRow("all unequal") + << b1 << b2 << a1 << a2 << false; + QTest::newRow("top left unequal") + << b1 << b3 << a1 << a3 << false; + QTest::newRow("bottom right unequal") + << b1 << b4 << a1 << a4 << false; + QTest::newRow("equal") + << b1 << b5 << a1 << a5 << true; +} + +void tst_QGeoRectangle::isValid() +{ + QFETCH(QGeoRectangle, input); + QFETCH(bool, valid); + + QCOMPARE(input.isValid(), valid); + + QGeoShape area = input; + QCOMPARE(area.isValid(), valid); +} + +void tst_QGeoRectangle::isValid_data() +{ + QTest::addColumn("input"); + QTest::addColumn("valid"); + + QGeoCoordinate c0; + QGeoCoordinate c1(10, 5); + QGeoCoordinate c2(5, 10); + + QTest::newRow("both corners invalid") + << QGeoRectangle(c0, c0) << false; + QTest::newRow("top left corner invalid") + << QGeoRectangle(c0, c2) << false; + QTest::newRow("bottom right corner invalid") + << QGeoRectangle(c1, c0) << false; + QTest::newRow("height in wrong order") + << QGeoRectangle(c2, c1) << false; + QTest::newRow("both corners valid") + << QGeoRectangle(c1, c2) << true; +} + +void tst_QGeoRectangle::isEmpty() +{ + QFETCH(QGeoRectangle, input); + QFETCH(bool, empty); + + QCOMPARE(input.isEmpty(), empty); + + QGeoShape area = input; + QCOMPARE(area.isEmpty(), empty); +} + +void tst_QGeoRectangle::isEmpty_data() +{ + QTest::addColumn("input"); + QTest::addColumn("empty"); + + QGeoCoordinate c0; + QGeoCoordinate c1(10, 5); + QGeoCoordinate c2(5, 10); + QGeoCoordinate c3(10, 10); + + QTest::newRow("both corners invalid") + << QGeoRectangle(c0, c0) << true; + QTest::newRow("top left corner invalid") + << QGeoRectangle(c0, c2) << true; + QTest::newRow("bottom right corner invalid") + << QGeoRectangle(c1, c0) << true; + QTest::newRow("zero width") + << QGeoRectangle(c1, c3) << true; + QTest::newRow("zero height") + << QGeoRectangle(c3, c2) << true; + QTest::newRow("zero width and height") + << QGeoRectangle(c1, c1) << true; + QTest::newRow("non-zero width and height") + << QGeoRectangle(c1, c2) << false; +} + +void tst_QGeoRectangle::corners() +{ + QFETCH(QGeoRectangle, box); + QFETCH(QGeoCoordinate, topLeft); + QFETCH(QGeoCoordinate, topRight); + QFETCH(QGeoCoordinate, bottomLeft); + QFETCH(QGeoCoordinate, bottomRight); + + QCOMPARE(box.topLeft(), topLeft); + QCOMPARE(box.topRight(), topRight); + QCOMPARE(box.bottomLeft(), bottomLeft); + QCOMPARE(box.bottomRight(), bottomRight); +} + +void tst_QGeoRectangle::corners_data() +{ + QTest::addColumn("box"); + QTest::addColumn("topLeft"); + QTest::addColumn("topRight"); + QTest::addColumn("bottomLeft"); + QTest::addColumn("bottomRight"); + + QGeoCoordinate c0; + QGeoCoordinate tl(10, 5); + QGeoCoordinate br(5, 10); + QGeoCoordinate tr(10, 10); + QGeoCoordinate bl(5, 5); + + QTest::newRow("both invalid") + << QGeoRectangle(c0, c0) + << c0 + << c0 + << c0 + << c0; + QTest::newRow("top left invalid") + << QGeoRectangle(c0, br) + << c0 + << c0 + << c0 + << br; + QTest::newRow("bottom right invalid") + << QGeoRectangle(tl, c0) + << tl + << c0 + << c0 + << c0; + QTest::newRow("both valid") + << QGeoRectangle(tl, br) + << tl + << tr + << bl + << br; +} + +void tst_QGeoRectangle::setCorners() +{ + QGeoRectangle box(QGeoCoordinate(10.0, 0.0), + QGeoCoordinate(0.0, 10.0)); + + box.setTopLeft(QGeoCoordinate(20.0, -10.0)); + + QCOMPARE(box.topLeft(), QGeoCoordinate(20.0, -10.0)); + QCOMPARE(box.topRight(), QGeoCoordinate(20.0, 10.0)); + QCOMPARE(box.bottomLeft(), QGeoCoordinate(0.0, -10.0)); + QCOMPARE(box.bottomRight(), QGeoCoordinate(0.0, 10.0)); + + box.setTopRight(QGeoCoordinate(30.0, 20.0)); + + QCOMPARE(box.topLeft(), QGeoCoordinate(30.0, -10.0)); + QCOMPARE(box.topRight(), QGeoCoordinate(30.0, 20.0)); + QCOMPARE(box.bottomLeft(), QGeoCoordinate(0.0, -10.0)); + QCOMPARE(box.bottomRight(), QGeoCoordinate(0.0, 20.0)); + + box.setBottomRight(QGeoCoordinate(-10.0, 30.0)); + + QCOMPARE(box.topLeft(), QGeoCoordinate(30.0, -10.0)); + QCOMPARE(box.topRight(), QGeoCoordinate(30.0, 30.0)); + QCOMPARE(box.bottomLeft(), QGeoCoordinate(-10.0, -10.0)); + QCOMPARE(box.bottomRight(), QGeoCoordinate(-10.0, 30.0)); + + box.setBottomLeft(QGeoCoordinate(-20.0, -20.0)); + + QCOMPARE(box.topLeft(), QGeoCoordinate(30.0, -20.0)); + QCOMPARE(box.topRight(), QGeoCoordinate(30.0, 30.0)); + QCOMPARE(box.bottomLeft(), QGeoCoordinate(-20.0, -20.0)); + QCOMPARE(box.bottomRight(), QGeoCoordinate(-20.0, 30.0)); + + +} + +void tst_QGeoRectangle::width() +{ + QFETCH(QGeoRectangle, box); + QFETCH(double, oldWidth); + QFETCH(double, newWidth); + QFETCH(QGeoRectangle, newBox); + + if (qIsNaN(oldWidth)) + QVERIFY(qIsNaN(box.width())); + else + QCOMPARE(box.width(), oldWidth); + + box.setWidth(newWidth); + + QCOMPARE(box, newBox); +} + +void tst_QGeoRectangle::width_data() +{ + QTest::addColumn("box"); + QTest::addColumn("oldWidth"); + QTest::addColumn("newWidth"); + QTest::addColumn("newBox"); + + QTest::newRow("invalid box") + << QGeoRectangle() + << qQNaN() + << 100.0 + << QGeoRectangle(); + + QTest::newRow("0 width -> negative width") + << QGeoRectangle(QGeoCoordinate(10.0, 90.0), + QGeoCoordinate(5.0, 90.0)) + << 0.0 + << -1.0 + << QGeoRectangle(QGeoCoordinate(10.0, 90.0), + QGeoCoordinate(5.0, 90.0)); + + QTest::newRow("0 width -> 0 width") + << QGeoRectangle(QGeoCoordinate(10.0, 90.0), + QGeoCoordinate(5.0, 90.0)) + << 0.0 + << 0.0 + << QGeoRectangle(QGeoCoordinate(10.0, 90.0), + QGeoCoordinate(5.0, 90.0)); + + QTest::newRow("0 width -> non wrapping width") + << QGeoRectangle(QGeoCoordinate(10.0, 90.0), + QGeoCoordinate(5.0, 90.0)) + << 0.0 + << 10.0 + << QGeoRectangle(QGeoCoordinate(10.0, 85.0), + QGeoCoordinate(5.0, 95.0)); + + QTest::newRow("0 width -> wrapping width positive") + << QGeoRectangle(QGeoCoordinate(10.0, 90.0), + QGeoCoordinate(5.0, 90.0)) + << 0.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(10.0, -5.0), + QGeoCoordinate(5.0, -175.0)); + + QTest::newRow("0 width -> wrapping width negative") + << QGeoRectangle(QGeoCoordinate(10.0, -90.0), + QGeoCoordinate(5.0, -90.0)) + << 0.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(10.0, 175.0), + QGeoCoordinate(5.0, 5.0)); + + QTest::newRow("0 width -> 360 width") + << QGeoRectangle(QGeoCoordinate(10.0, 90.0), + QGeoCoordinate(5.0, 90.0)) + << 0.0 + << 360.0 + << QGeoRectangle(QGeoCoordinate(10.0, -180.0), + QGeoCoordinate(5.0, 180.0)); + + QTest::newRow("0 width -> 360+ width") + << QGeoRectangle(QGeoCoordinate(10.0, 90.0), + QGeoCoordinate(5.0, 90.0)) + << 0.0 + << 370.0 + << QGeoRectangle(QGeoCoordinate(10.0, -180.0), + QGeoCoordinate(5.0, 180.0)); + + QTest::newRow("non wrapping width -> negative width") + << QGeoRectangle(QGeoCoordinate(10.0, 85.0), + QGeoCoordinate(5.0, 95.0)) + << 10.0 + << -1.0 + << QGeoRectangle(QGeoCoordinate(10.0, 85.0), + QGeoCoordinate(5.0, 95.0)); + + QTest::newRow("non wrapping width -> 0 width") + << QGeoRectangle(QGeoCoordinate(10.0, 85.0), + QGeoCoordinate(5.0, 95.0)) + << 10.0 + << 0.0 + << QGeoRectangle(QGeoCoordinate(10.0, 90.0), + QGeoCoordinate(5.0, 90.0)); + + QTest::newRow("non wrapping width -> non wrapping width") + << QGeoRectangle(QGeoCoordinate(10.0, 85.0), + QGeoCoordinate(5.0, 95.0)) + << 10.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(10.0, 80.0), + QGeoCoordinate(5.0, 100.0)); + + QTest::newRow("non wrapping width -> wrapping width positive") + << QGeoRectangle(QGeoCoordinate(10.0, 85.0), + QGeoCoordinate(5.0, 95.0)) + << 10.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(10.0, -5.0), + QGeoCoordinate(5.0, -175.0)); + + QTest::newRow("non wrapping width -> wrapping width negative") + << QGeoRectangle(QGeoCoordinate(10.0, -95.0), + QGeoCoordinate(5.0, -85.0)) + << 10.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(10.0, 175.0), + QGeoCoordinate(5.0, 5.0)); + + QTest::newRow("non wrapping width -> 360 width") + << QGeoRectangle(QGeoCoordinate(10.0, 85.0), + QGeoCoordinate(5.0, 95.0)) + << 10.0 + << 360.0 + << QGeoRectangle(QGeoCoordinate(10.0, -180.0), + QGeoCoordinate(5.0, 180.0)); + + QTest::newRow("non wrapping width width -> 360+ width") + << QGeoRectangle(QGeoCoordinate(10.0, 85.0), + QGeoCoordinate(5.0, 95.0)) + << 10.0 + << 370.0 + << QGeoRectangle(QGeoCoordinate(10.0, -180.0), + QGeoCoordinate(5.0, 180.0)); + + QTest::newRow("wrapping width -> negative width") + << QGeoRectangle(QGeoCoordinate(10.0, 175.0), + QGeoCoordinate(5.0, -85.0)) + << 100.0 + << -1.0 + << QGeoRectangle(QGeoCoordinate(10.0, 175.0), + QGeoCoordinate(5.0, -85.0)); + + QTest::newRow("wrapping width -> 0 width") + << QGeoRectangle(QGeoCoordinate(10.0, 175.0), + QGeoCoordinate(5.0, -85.0)) + << 100.0 + << 0.0 + << QGeoRectangle(QGeoCoordinate(10.0, -135.0), + QGeoCoordinate(5.0, -135.0)); + + QTest::newRow("wrapping width -> non wrapping width") + << QGeoRectangle(QGeoCoordinate(10.0, 175.0), + QGeoCoordinate(5.0, -85.0)) + << 100.0 + << 80.0 + << QGeoRectangle(QGeoCoordinate(10.0, -175.0), + QGeoCoordinate(5.0, -95.0)); + + QTest::newRow("wrapping width -> wrapping width") + << QGeoRectangle(QGeoCoordinate(10.0, 175.0), + QGeoCoordinate(5.0, -85.0)) + << 100.0 + << 120.0 + << QGeoRectangle(QGeoCoordinate(10.0, 165.0), + QGeoCoordinate(5.0, -75.0)); + + QTest::newRow("wrapping width -> 360 width") + << QGeoRectangle(QGeoCoordinate(10.0, 175.0), + QGeoCoordinate(5.0, -85.0)) + << 100.0 + << 360.0 + << QGeoRectangle(QGeoCoordinate(10.0, -180.0), + QGeoCoordinate(5.0, 180.0)); + + QTest::newRow("wrapping width width -> 360+ width") + << QGeoRectangle(QGeoCoordinate(10.0, 175.0), + QGeoCoordinate(5.0, -85.0)) + << 100.0 + << 370.0 + << QGeoRectangle(QGeoCoordinate(10.0, -180.0), + QGeoCoordinate(5.0, 180.0)); +} + +void tst_QGeoRectangle::height() +{ + QFETCH(QGeoRectangle, box); + QFETCH(double, oldHeight); + QFETCH(double, newHeight); + QFETCH(QGeoRectangle, newBox); + + if (qIsNaN(oldHeight)) + QVERIFY(qIsNaN(box.height())); + else + QCOMPARE(box.height(), oldHeight); + + box.setHeight(newHeight); + QCOMPARE(box, newBox); +} + +void tst_QGeoRectangle::height_data() +{ + QTest::addColumn("box"); + QTest::addColumn("oldHeight"); + QTest::addColumn("newHeight"); + QTest::addColumn("newBox"); + + QTest::newRow("invalid box") + << QGeoRectangle() + << qQNaN() + << 100.0 + << QGeoRectangle(); + + QTest::newRow("0 height -> negative height") + << QGeoRectangle(QGeoCoordinate(10.0, 5.0), + QGeoCoordinate(10.0, 10.0)) + << 0.0 + << -1.0 + << QGeoRectangle(QGeoCoordinate(10.0, 5.0), + QGeoCoordinate(10.0, 10.0)); + + QTest::newRow("0 height -> 0 height") + << QGeoRectangle(QGeoCoordinate(10.0, 5.0), + QGeoCoordinate(10.0, 10.0)) + << 0.0 + << 0.0 + << QGeoRectangle(QGeoCoordinate(10.0, 5.0), + QGeoCoordinate(10.0, 10.0)); + + QTest::newRow("0 height -> non zero height") + << QGeoRectangle(QGeoCoordinate(10.0, 5.0), + QGeoCoordinate(10.0, 10.0)) + << 0.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(20.0, 5.0), + QGeoCoordinate(0.0, 10.0)); + + QTest::newRow("0 height -> squash top") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(70.0, 70.0)) + << 0.0 + << 60.0 + << QGeoRectangle(QGeoCoordinate(90.0, 30.0), + QGeoCoordinate(50.0, 70.0)); + + QTest::newRow("0 height -> squash bottom") + << QGeoRectangle(QGeoCoordinate(-70.0, 30.0), + QGeoCoordinate(-70.0, 70.0)) + << 0.0 + << 60.0 + << QGeoRectangle(QGeoCoordinate(-50.0, 30.0), + QGeoCoordinate(-90.0, 70.0)); + + QTest::newRow("0 height -> 180") + << QGeoRectangle(QGeoCoordinate(0.0, 5.0), + QGeoCoordinate(0.0, 10.0)) + << 0.0 + << 180.0 + << QGeoRectangle(QGeoCoordinate(90.0, 5.0), + QGeoCoordinate(-90.0, 10.0)); + + QTest::newRow("0 height -> 180 squash top") + << QGeoRectangle(QGeoCoordinate(20.0, 5.0), + QGeoCoordinate(20.0, 10.0)) + << 0.0 + << 180.0 + << QGeoRectangle(QGeoCoordinate(90.0, 5.0), + QGeoCoordinate(-50.0, 10.0)); + + QTest::newRow("0 height -> 180 squash bottom") + << QGeoRectangle(QGeoCoordinate(-20.0, 5.0), + QGeoCoordinate(-20.0, 10.0)) + << 0.0 + << 180.0 + << QGeoRectangle(QGeoCoordinate(50.0, 5.0), + QGeoCoordinate(-90.0, 10.0)); + + QTest::newRow("0 height -> 180+") + << QGeoRectangle(QGeoCoordinate(0.0, 5.0), + QGeoCoordinate(0.0, 10.0)) + << 0.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(90.0, 5.0), + QGeoCoordinate(-90.0, 10.0)); + + QTest::newRow("0 height -> 180+ squash top") + << QGeoRectangle(QGeoCoordinate(20.0, 5.0), + QGeoCoordinate(20.0, 10.0)) + << 0.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(90.0, 5.0), + QGeoCoordinate(-50.0, 10.0)); + + QTest::newRow("0 height -> 180+ squash bottom") + << QGeoRectangle(QGeoCoordinate(-20.0, 5.0), + QGeoCoordinate(-20.0, 10.0)) + << 0.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(50.0, 5.0), + QGeoCoordinate(-90.0, 10.0)); + + QTest::newRow("non zero height -> negative height") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << 40.0 + << -1.0 + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)); + + QTest::newRow("non zero height -> 0 height") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << 40.0 + << 0.0 + << QGeoRectangle(QGeoCoordinate(50.0, 30.0), + QGeoCoordinate(50.0, 70.0)); + + QTest::newRow("non zero height -> non zero height") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << 40.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(60.0, 30.0), + QGeoCoordinate(40.0, 70.0)); + + QTest::newRow("non zero height -> squash top") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << 40.0 + << 100.0 + << QGeoRectangle(QGeoCoordinate(90.0, 30.0), + QGeoCoordinate(10.0, 70.0)); + + QTest::newRow("non zero height -> squash bottom") + << QGeoRectangle(QGeoCoordinate(-30.0, 30.0), + QGeoCoordinate(-70.0, 70.0)) + << 40.0 + << 100.0 + << QGeoRectangle(QGeoCoordinate(-10.0, 30.0), + QGeoCoordinate(-90.0, 70.0)); + + QTest::newRow("non zero height -> 180") + << QGeoRectangle(QGeoCoordinate(20.0, 30.0), + QGeoCoordinate(-20.0, 70.0)) + << 40.0 + << 180.0 + << QGeoRectangle(QGeoCoordinate(90.0, 30.0), + QGeoCoordinate(-90.0, 70.0)); + + QTest::newRow("non zero height -> 180 squash top") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << 40.0 + << 180.0 + << QGeoRectangle(QGeoCoordinate(90.0, 30.0), + QGeoCoordinate(10.0, 70.0)); + + QTest::newRow("non zero height -> 180 squash bottom") + << QGeoRectangle(QGeoCoordinate(-30.0, 30.0), + QGeoCoordinate(-70.0, 70.0)) + << 40.0 + << 180.0 + << QGeoRectangle(QGeoCoordinate(-10.0, 30.0), + QGeoCoordinate(-90.0, 70.0)); + + QTest::newRow("non zero height -> 180+") + << QGeoRectangle(QGeoCoordinate(20.0, 30.0), + QGeoCoordinate(-20.0, 70.0)) + << 40.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(90.0, 30.0), + QGeoCoordinate(-90.0, 70.0)); + + QTest::newRow("non zero height -> 180+ squash top") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << 40.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(90.0, 30.0), + QGeoCoordinate(10.0, 70.0)); + + QTest::newRow("non zero height -> 180+ squash bottom") + << QGeoRectangle(QGeoCoordinate(-30.0, 30.0), + QGeoCoordinate(-70.0, 70.0)) + << 40.0 + << 190.0 + << QGeoRectangle(QGeoCoordinate(-10.0, 30.0), + QGeoCoordinate(-90.0, 70.0)); +} + +void tst_QGeoRectangle::center() +{ + QFETCH(QGeoRectangle, box); + QFETCH(QGeoCoordinate, oldCenter); + QFETCH(QGeoCoordinate, newCenter); + QFETCH(QGeoRectangle, newBox); + + QGeoShape shape = box; + QCOMPARE(box.center(), oldCenter); + QCOMPARE(shape.center(), oldCenter); + box.setCenter(newCenter); + QCOMPARE(box, newBox); +} + +void tst_QGeoRectangle::center_data() +{ + QTest::addColumn("box"); + QTest::addColumn("oldCenter"); + QTest::addColumn("newCenter"); + QTest::addColumn("newBox"); + + QTest::newRow("invalid") + << QGeoRectangle() + << QGeoCoordinate() + << QGeoCoordinate(0.0, 0.0) + << QGeoRectangle(QGeoCoordinate(0.0, 0.0), 0.0, 0.0); + + QTest::newRow("zero width") + << QGeoRectangle(QGeoCoordinate(10.0, 5.0), + QGeoCoordinate(5.0, 5.0)) + << QGeoCoordinate(7.5, 5.0) + << QGeoCoordinate(20.0, 20.0) + << QGeoRectangle(QGeoCoordinate(22.5, 20.0), + QGeoCoordinate(17.5, 20.0)); + + QTest::newRow("360 width") + << QGeoRectangle(QGeoCoordinate(10.0, -180.0), + QGeoCoordinate(5.0, 180.0)) + << QGeoCoordinate(7.5, 0.0) + << QGeoCoordinate(20.0, 20.0) + << QGeoRectangle(QGeoCoordinate(22.5, -180.0), + QGeoCoordinate(17.5, 180.0)); + + QTest::newRow("zero height") + << QGeoRectangle(QGeoCoordinate(5.0, 5.0), + QGeoCoordinate(5.0, 10.0)) + << QGeoCoordinate(5.0, 7.5) + << QGeoCoordinate(20.0, 20.0) + << QGeoRectangle(QGeoCoordinate(20.0, 17.5), + QGeoCoordinate(20.0, 22.5)); + + QTest::newRow("180 height -> move") + << QGeoRectangle(QGeoCoordinate(90.0, 5.0), + QGeoCoordinate(-90.0, 10.0)) + << QGeoCoordinate(0.0, 7.5) + << QGeoCoordinate(0.0, 20.0) + << QGeoRectangle(QGeoCoordinate(90.0, 17.5), + QGeoCoordinate(-90.0, 22.5)); + + QTest::newRow("180 height -> squash top") + << QGeoRectangle(QGeoCoordinate(90.0, 5.0), + QGeoCoordinate(-90.0, 10.0)) + << QGeoCoordinate(0.0, 7.5) + << QGeoCoordinate(-20.0, 20.0) + << QGeoRectangle(QGeoCoordinate(50.0, 17.5), + QGeoCoordinate(-90.0, 22.5)); + + QTest::newRow("180 height -> squash bottom") + << QGeoRectangle(QGeoCoordinate(90.0, 5.0), + QGeoCoordinate(-90.0, 10.0)) + << QGeoCoordinate(0.0, 7.5) + << QGeoCoordinate(20.0, 20.0) + << QGeoRectangle(QGeoCoordinate(90.0, 17.5), + QGeoCoordinate(-50.0, 22.5)); + + QTest::newRow("non wrapping -> non wrapping") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << QGeoCoordinate(50.0, 50.0) + << QGeoCoordinate(10.0, 10.0) + << QGeoRectangle(QGeoCoordinate(30.0, -10.0), + QGeoCoordinate(-10.0, 30.0)); + + QTest::newRow("non wrapping -> wrapping") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << QGeoCoordinate(50.0, 50.0) + << QGeoCoordinate(10.0, 170.0) + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-10.0, -170.0)); + + QTest::newRow("non wrapping -> squash top") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << QGeoCoordinate(50.0, 50.0) + << QGeoCoordinate(80.0, 50.0) + << QGeoRectangle(QGeoCoordinate(90.0, 30.0), + QGeoCoordinate(70.0, 70.0)); + + QTest::newRow("non wrapping -> squash bottom") + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)) + << QGeoCoordinate(50.0, 50.0) + << QGeoCoordinate(-80.0, 50.0) + << QGeoRectangle(QGeoCoordinate(-70.0, 30.0), + QGeoCoordinate(-90.0, 70.0)); + + QTest::newRow("wrapping -> non wrapping") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-10.0, -170.0)) + << QGeoCoordinate(10.0, 170.0) + << QGeoCoordinate(50.0, 50.0) + << QGeoRectangle(QGeoCoordinate(70.0, 30.0), + QGeoCoordinate(30.0, 70.0)); + + QTest::newRow("wrapping -> wrapping") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-10.0, -170.0)) + << QGeoCoordinate(10.0, 170.0) + << QGeoCoordinate(10.0, -170.0) + << QGeoRectangle(QGeoCoordinate(30.0, 170.0), + QGeoCoordinate(-10.0, -150.0)); + + QTest::newRow("wrapping -> squash top") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-10.0, -170.0)) + << QGeoCoordinate(10.0, 170.0) + << QGeoCoordinate(80.0, 170.0) + << QGeoRectangle(QGeoCoordinate(90.0, 150.0), + QGeoCoordinate(70.0, -170.0)); + + QTest::newRow("wrapping -> squash bottom") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-10.0, -170.0)) + << QGeoCoordinate(10.0, 170.0) + << QGeoCoordinate(-80.0, 170.0) + << QGeoRectangle(QGeoCoordinate(-70.0, 150.0), + QGeoCoordinate(-90.0, -170.0)); +} + + +void tst_QGeoRectangle::containsCoord() +{ + QFETCH(QGeoRectangle, box); + QFETCH(QGeoCoordinate, coord); + QFETCH(bool, contains); + + QCOMPARE(box.contains(coord), contains); + + QGeoShape area = box; + QCOMPARE(area.contains(coord), contains); +} + +void tst_QGeoRectangle::containsCoord_data() +{ + QTest::addColumn("box"); + QTest::addColumn("coord"); + QTest::addColumn("contains"); + + QGeoRectangle b1(QGeoCoordinate(70, 30), QGeoCoordinate(30, 70)); + + double lonLO1 = 20.0; + double lonL1 = 30.0; + double lonLI1 = 40.0; + double lonC1 = 50.0; + double lonRI1 = 60.0; + double lonR1 = 70.0; + double lonRO1 = 80.0; + + double latTO1 = 80.0; + double latT1 = 70.0; + double latTI1 = 60.0; + double latC1 = 50.0; + double latBI1 = 40.0; + double latB1 = 30.0; + double latBO1 = 20.0; + + QTest::newRow("non wrapped - in center") + << b1 << QGeoCoordinate(latC1, lonC1) << true; + QTest::newRow("non wrapped - left edge - inside") + << b1 << QGeoCoordinate(latC1, lonLI1) << true; + QTest::newRow("non wrapped - left edge") + << b1 << QGeoCoordinate(latC1, lonL1) << true; + QTest::newRow("non wrapped - left edge - outside") + << b1 << QGeoCoordinate(latC1, lonLO1) << false; + QTest::newRow("non wrapped - right edge - inside") + << b1 << QGeoCoordinate(latC1, lonRI1) << true; + QTest::newRow("non wrapped - right edge") + << b1 << QGeoCoordinate(latC1, lonR1) << true; + QTest::newRow("non wrapped - right edge - outside") + << b1 << QGeoCoordinate(latC1, lonRO1) << false; + QTest::newRow("non wrapped - top edge - inside") + << b1 << QGeoCoordinate(latTI1, lonC1) << true; + QTest::newRow("non wrapped - top edge") + << b1 << QGeoCoordinate(latT1, lonC1) << true; + QTest::newRow("non wrapped - top edge - outside") + << b1 << QGeoCoordinate(latTO1, lonC1) << false; + QTest::newRow("non wrapped - bottom edge - inside") + << b1 << QGeoCoordinate(latBI1, lonC1) << true; + QTest::newRow("non wrapped - bottom edge") + << b1 << QGeoCoordinate(latB1, lonC1) << true; + QTest::newRow("non wrapped - bottom edge - outside") + << b1 << QGeoCoordinate(latBO1, lonC1) << false; + QTest::newRow("non wrapped - top left - inside") + << b1 << QGeoCoordinate(latTI1, lonLI1) << true; + QTest::newRow("non wrapped - top left") + << b1 << QGeoCoordinate(latT1, lonL1) << true; + QTest::newRow("non wrapped - top left - outside") + << b1 << QGeoCoordinate(latTO1, lonLO1) << false; + QTest::newRow("non wrapped - top right - inside") + << b1 << QGeoCoordinate(latTI1, lonRI1) << true; + QTest::newRow("non wrapped - top right") + << b1 << QGeoCoordinate(latT1, lonR1) << true; + QTest::newRow("non wrapped - top right - outside") + << b1 << QGeoCoordinate(latTO1, lonRO1) << false; + QTest::newRow("non wrapped - bottom left - inside") + << b1 << QGeoCoordinate(latBI1, lonLI1) << true; + QTest::newRow("non wrapped - bottom left") + << b1 << QGeoCoordinate(latB1, lonL1) << true; + QTest::newRow("non wrapped - bottom left - outside") + << b1 << QGeoCoordinate(latBO1, lonLO1) << false; + QTest::newRow("non wrapped - bottom right - inside") + << b1 << QGeoCoordinate(latBI1, lonRI1) << true; + QTest::newRow("non wrapped - bottom right") + << b1 << QGeoCoordinate(latB1, lonR1) << true; + QTest::newRow("non wrapped - bottom right - outside") + << b1 << QGeoCoordinate(latBO1, lonRO1) << false; + + QGeoRectangle b2(QGeoCoordinate(70, 150), QGeoCoordinate(30, -170)); + + double lonLO2 = 140.0; + double lonL2 = 150.0; + double lonLI2 = 160.0; + double lonC2 = 170.0; + double lonRI2 = 180.0; + double lonR2 = -170.0; + double lonRO2 = -160.0; + + double latTO2 = 80.0; + double latT2 = 70.0; + double latTI2 = 60.0; + double latC2 = 50.0; + double latBI2 = 40.0; + double latB2 = 30.0; + double latBO2 = 20.0; + + QTest::newRow("wrapped - in center") + << b2 << QGeoCoordinate(latC2, lonC2) << true; + QTest::newRow("wrapped - left edge - inside") + << b2 << QGeoCoordinate(latC2, lonLI2) << true; + QTest::newRow("wrapped - left edge") + << b2 << QGeoCoordinate(latC2, lonL2) << true; + QTest::newRow("wrapped - left edge - outside") + << b2 << QGeoCoordinate(latC2, lonLO2) << false; + QTest::newRow("wrapped - right edge - inside") + << b2 << QGeoCoordinate(latC2, lonRI2) << true; + QTest::newRow("wrapped - right edge") + << b2 << QGeoCoordinate(latC2, lonR2) << true; + QTest::newRow("wrapped - right edge - outside") + << b2 << QGeoCoordinate(latC2, lonRO2) << false; + QTest::newRow("wrapped - top edge - inside") + << b2 << QGeoCoordinate(latTI2, lonC2) << true; + QTest::newRow("wrapped - top edge") + << b2 << QGeoCoordinate(latT2, lonC2) << true; + QTest::newRow("wrapped - top edge - outside") + << b2 << QGeoCoordinate(latTO2, lonC2) << false; + QTest::newRow("wrapped - bottom edge - inside") + << b2 << QGeoCoordinate(latBI2, lonC2) << true; + QTest::newRow("wrapped - bottom edge") + << b2 << QGeoCoordinate(latB2, lonC2) << true; + QTest::newRow("wrapped - bottom edge - outside") + << b2 << QGeoCoordinate(latBO2, lonC2) << false; + QTest::newRow("wrapped - top left - inside") + << b2 << QGeoCoordinate(latTI2, lonLI2) << true; + QTest::newRow("wrapped - top left") + << b2 << QGeoCoordinate(latT2, lonL2) << true; + QTest::newRow("wrapped - top left - outside") + << b2 << QGeoCoordinate(latTO2, lonLO2) << false; + QTest::newRow("wrapped - top right - inside") + << b2 << QGeoCoordinate(latTI2, lonRI2) << true; + QTest::newRow("wrapped - top right") + << b2 << QGeoCoordinate(latT2, lonR2) << true; + QTest::newRow("wrapped - top right - outside") + << b2 << QGeoCoordinate(latTO2, lonRO2) << false; + QTest::newRow("wrapped - bottom left - inside") + << b2 << QGeoCoordinate(latBI2, lonLI2) << true; + QTest::newRow("wrapped - bottom left") + << b2 << QGeoCoordinate(latB2, lonL2) << true; + QTest::newRow("wrapped - bottom left - outside") + << b2 << QGeoCoordinate(latBO2, lonLO2) << false; + QTest::newRow("wrapped - bottom right - inside") + << b2 << QGeoCoordinate(latBI2, lonRI2) << true; + QTest::newRow("wrapped - bottom right") + << b2 << QGeoCoordinate(latB2, lonR2) << true; + QTest::newRow("wrapped - bottom right - outside") + << b2 << QGeoCoordinate(latBO2, lonRO2) << false; + + QGeoRectangle b3(QGeoCoordinate(90, 30), QGeoCoordinate(50, 70)); + + double lonLO3 = 20.0; + double lonL3 = 30.0; + double lonLI3 = 40.0; + double lonC3 = 50.0; + double lonRI3 = 60.0; + double lonR3 = 70.0; + double lonRO3 = 80.0; + + double latT3 = 90.0; + double latTI3 = 80.0; + double latC3 = 70.0; + /* current unused: + double latBI3 = 60.0; + double latB3 = 50.0; + double latBO3 = 40.0; + */ + + QTest::newRow("north pole - in center") + << b3 << QGeoCoordinate(latC3, lonC3) << true; + QTest::newRow("north pole - left edge - inside") + << b3 << QGeoCoordinate(latC3, lonLI3) << true; + QTest::newRow("north pole - left edge") + << b3 << QGeoCoordinate(latC3, lonL3) << true; + QTest::newRow("north pole - left edge - outside") + << b3 << QGeoCoordinate(latC3, lonLO3) << false; + QTest::newRow("north pole - right edge - inside") + << b3 << QGeoCoordinate(latC3, lonRI3) << true; + QTest::newRow("north pole - right edge") + << b3 << QGeoCoordinate(latC3, lonR3) << true; + QTest::newRow("north pole - right edge - outside") + << b3 << QGeoCoordinate(latC3, lonRO3) << false; + QTest::newRow("north pole - top edge - inside") + << b3 << QGeoCoordinate(latTI3, lonC3) << true; + QTest::newRow("north pole - top edge") + << b3 << QGeoCoordinate(latT3, lonC3) << true; + QTest::newRow("north pole - top left - inside") + << b3 << QGeoCoordinate(latTI3, lonLI3) << true; + QTest::newRow("north pole - top left") + << b3 << QGeoCoordinate(latT3, lonL3) << true; + QTest::newRow("north pole - top left - outside") + << b3 << QGeoCoordinate(latT3, lonLO3) << true; + QTest::newRow("north pole - top right - inside") + << b3 << QGeoCoordinate(latTI3, lonRI3) << true; + QTest::newRow("north pole - top right") + << b3 << QGeoCoordinate(latT3, lonR3) << true; + QTest::newRow("north pole - top right - outside") + << b3 << QGeoCoordinate(latT3, lonRO3) << true; + + QGeoRectangle b4(QGeoCoordinate(-50, 30), QGeoCoordinate(-90, 70)); + + double lonLO4 = 20.0; + double lonL4 = 30.0; + double lonLI4 = 40.0; + double lonC4 = 50.0; + double lonRI4 = 60.0; + double lonR4 = 70.0; + double lonRO4 = 80.0; + + /* currently unused: + double latTO4 = -40.0; + double latT4 = -50.0; + double latTI4 = -60.0; + */ + double latC4 = -70.0; + double latBI4 = -80.0; + double latB4 = -90.0; + + QTest::newRow("south pole - in center") + << b4 << QGeoCoordinate(latC4, lonC4) << true; + QTest::newRow("south pole - left edge - inside") + << b4 << QGeoCoordinate(latC4, lonLI4) << true; + QTest::newRow("south pole - left edge") + << b4 << QGeoCoordinate(latC4, lonL4) << true; + QTest::newRow("south pole - left edge - outside") + << b4 << QGeoCoordinate(latC4, lonLO4) << false; + QTest::newRow("south pole - right edge - inside") + << b4 << QGeoCoordinate(latC4, lonRI4) << true; + QTest::newRow("south pole - right edge") + << b4 << QGeoCoordinate(latC4, lonR4) << true; + QTest::newRow("south pole - right edge - outside") + << b4 << QGeoCoordinate(latC4, lonRO4) << false; + QTest::newRow("south pole - bottom edge - inside") + << b4 << QGeoCoordinate(latBI4, lonC4) << true; + QTest::newRow("south pole - bottom edge") + << b4 << QGeoCoordinate(latB4, lonC4) << true; + QTest::newRow("south pole - bottom left - inside") + << b4 << QGeoCoordinate(latBI4, lonLI4) << true; + QTest::newRow("south pole - bottom left") + << b4 << QGeoCoordinate(latB4, lonL4) << true; + QTest::newRow("south pole - bottom left - outside") + << b4 << QGeoCoordinate(latB4, lonLO4) << true; + QTest::newRow("south pole - bottom right - inside") + << b4 << QGeoCoordinate(latBI4, lonRI4) << true; + QTest::newRow("south pole - bottom right") + << b4 << QGeoCoordinate(latB4, lonR4) << true; + QTest::newRow("south pole - bottom right - outside") + << b4 << QGeoCoordinate(latB4, lonRO4) << true; +} + +void tst_QGeoRectangle::containsBoxAndIntersects() +{ + QFETCH(QGeoRectangle, box1); + QFETCH(QGeoRectangle, box2); + QFETCH(bool, contains); + QFETCH(bool, intersects); + + QCOMPARE(box1.contains(box2), contains); + QCOMPARE(box1.intersects(box2), intersects); +} + +void tst_QGeoRectangle::containsBoxAndIntersects_data() +{ + QTest::addColumn("box1"); + QTest::addColumn("box2"); + QTest::addColumn("contains"); + QTest::addColumn("intersects"); + + QGeoRectangle b1(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)); + + QTest::newRow("non wrapped same") + << b1 + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << true << true; + + QTest::newRow("non wrapped smaller") + << b1 + << QGeoRectangle(QGeoCoordinate(20.0, -20.0), + QGeoCoordinate(-20.0, 20.0)) + << true << true; + + QTest::newRow("non wrapped larger") + << b1 + << QGeoRectangle(QGeoCoordinate(40.0, -40.0), + QGeoCoordinate(-40.0, 40.0)) + << false << true; + + QTest::newRow("non wrapped outside top") + << b1 + << QGeoRectangle(QGeoCoordinate(80.0, -30.0), + QGeoCoordinate(50.0, 30.0)) + << false << false; + + QTest::newRow("non wrapped outside bottom") + << b1 + << QGeoRectangle(QGeoCoordinate(-50.0, -30.0), + QGeoCoordinate(-80.0, 30.0)) + << false << false; + + QTest::newRow("non wrapped outside left") + << b1 + << QGeoRectangle(QGeoCoordinate(30.0, -80.0), + QGeoCoordinate(-30.0, -50.0)) + << false << false; + + QTest::newRow("non wrapped outside wrapped") + << b1 + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << false << false; + + QTest::newRow("non wrapped outside right") + << b1 + << QGeoRectangle(QGeoCoordinate(30.0, 50.0), + QGeoCoordinate(-30.0, 80.0)) + << false << false; + + QTest::newRow("non wrapped top left cross") + << b1 + << QGeoRectangle(QGeoCoordinate(40.0, -40.0), + QGeoCoordinate(20.0, -20.0)) + << false << true; + + QTest::newRow("non wrapped top cross") + << b1 + << QGeoRectangle(QGeoCoordinate(40.0, -10.0), + QGeoCoordinate(20.0, 10.0)) + << false << true; + + QTest::newRow("non wrapped top right cross") + << b1 + << QGeoRectangle(QGeoCoordinate(40.0, 20.0), + QGeoCoordinate(20.0, 40.0)) + << false << true; + + QTest::newRow("non wrapped left cross") + << b1 + << QGeoRectangle(QGeoCoordinate(10.0, -40.0), + QGeoCoordinate(-10.0, -20.0)) + << false << true; + + QTest::newRow("non wrapped right cross") + << b1 + << QGeoRectangle(QGeoCoordinate(10.0, 20.0), + QGeoCoordinate(-10.0, 40.0)) + << false << true; + + QTest::newRow("non wrapped bottom left cross") + << b1 + << QGeoRectangle(QGeoCoordinate(-20.0, -40.0), + QGeoCoordinate(-40.0, -20.0)) + << false << true; + + QTest::newRow("non wrapped bottom cross") + << b1 + << QGeoRectangle(QGeoCoordinate(-20.0, -10.0), + QGeoCoordinate(-40.0, 10.0)) + << false << true; + + QTest::newRow("non wrapped bottom right cross") + << b1 + << QGeoRectangle(QGeoCoordinate(-20.0, 20.0), + QGeoCoordinate(-40.0, 40.0)) + << false << true; + + QTest::newRow("non wrapped top left touch outside") + << b1 + << QGeoRectangle(QGeoCoordinate(50.0, -50.0), + QGeoCoordinate(30.0, -30.0)) + << false << true; + + QTest::newRow("non wrapped top touch outside") + << b1 + << QGeoRectangle(QGeoCoordinate(50.0, -10.0), + QGeoCoordinate(30.0, 10.0)) + << false << true; + + QTest::newRow("non wrapped top right touch outside") + << b1 + << QGeoRectangle(QGeoCoordinate(50.0, 30.0), + QGeoCoordinate(30.0, 50.0)) + << false << true; + + QTest::newRow("non wrapped left touch outside") + << b1 + << QGeoRectangle(QGeoCoordinate(10.0, -50.0), + QGeoCoordinate(-10.0, -30.0)) + << false << true; + + QTest::newRow("non wrapped right touch outside") + << b1 + << QGeoRectangle(QGeoCoordinate(10.0, 30.0), + QGeoCoordinate(-10.0, 50.0)) + << false << true; + + QTest::newRow("non wrapped bottom left touch outside") + << b1 + << QGeoRectangle(QGeoCoordinate(-30.0, -30.0), + QGeoCoordinate(-50.0, -50.0)) + << false << true; + + QTest::newRow("non wrapped bottom touch outside") + << b1 + << QGeoRectangle(QGeoCoordinate(-30.0, -10.0), + QGeoCoordinate(-50.0, 10.0)) + << false << true; + + QTest::newRow("non wrapped bottom right touch outside") + << b1 + << QGeoRectangle(QGeoCoordinate(-30.0, 30.0), + QGeoCoordinate(-50.0, 50.0)) + << false << true; + + QTest::newRow("non wrapped top left touch inside") + << b1 + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(10.0, -10.0)) + << true << true; + + QTest::newRow("non wrapped top touch inside") + << b1 + << QGeoRectangle(QGeoCoordinate(30.0, -10.0), + QGeoCoordinate(10.0, 10.0)) + << true << true; + + QTest::newRow("non wrapped top right touch inside") + << b1 + << QGeoRectangle(QGeoCoordinate(30.0, 10.0), + QGeoCoordinate(10.0, 30.0)) + << true << true; + + QTest::newRow("non wrapped left touch inside") + << b1 + << QGeoRectangle(QGeoCoordinate(10.0, -30.0), + QGeoCoordinate(-10.0, -10.0)) + << true << true; + + QTest::newRow("non wrapped right touch inside") + << b1 + << QGeoRectangle(QGeoCoordinate(10.0, 10.0), + QGeoCoordinate(-10.0, 30.0)) + << true << true; + + QTest::newRow("non wrapped bottom left touch inside") + << b1 + << QGeoRectangle(QGeoCoordinate(-10.0, -30.0), + QGeoCoordinate(-30.0, -10.0)) + << true << true; + + QTest::newRow("non wrapped bottom touch inside") + << b1 + << QGeoRectangle(QGeoCoordinate(-10.0, -10.0), + QGeoCoordinate(-30.0, 10.0)) + << true << true; + + QTest::newRow("non wrapped bottom right touch inside") + << b1 + << QGeoRectangle(QGeoCoordinate(-10.0, 10.0), + QGeoCoordinate(-30.0, 30.0)) + << true << true; + + QTest::newRow("non wrapped top lon strip") + << b1 + << QGeoRectangle(QGeoCoordinate(40.0, -40.0), + QGeoCoordinate(20.0, 40.0)) + << false << true; + + QTest::newRow("non wrapped center lon strip") + << b1 + << QGeoRectangle(QGeoCoordinate(10.0, -40.0), + QGeoCoordinate(-10.0, 40.0)) + << false << true; + + QTest::newRow("non wrapped bottom lon strip") + << b1 + << QGeoRectangle(QGeoCoordinate(-20.0, -40.0), + QGeoCoordinate(-40.0, 40.0)) + << false << true; + + QTest::newRow("non wrapped left lat strip") + << b1 + << QGeoRectangle(QGeoCoordinate(40.0, -40.0), + QGeoCoordinate(-40.0, -20.0)) + << false << true; + + QTest::newRow("non wrapped center lat strip") + << b1 + << QGeoRectangle(QGeoCoordinate(40.0, -10.0), + QGeoCoordinate(-40.0, 10.0)) + << false << true; + + QTest::newRow("non wrapped right lat strip") + << b1 + << QGeoRectangle(QGeoCoordinate(40.0, 20.0), + QGeoCoordinate(-40.0, 40.0)) + << false << true; + + QGeoRectangle b2(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)); + + QTest::newRow("wrapped same") + << b2 + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << true << true; + + QTest::newRow("wrapped smaller") + << b2 + << QGeoRectangle(QGeoCoordinate(20.0, 160.0), + QGeoCoordinate(-20.0, -160.0)) + << true << true; + + QTest::newRow("wrapped larger") + << b2 + << QGeoRectangle(QGeoCoordinate(40.0, 140.0), + QGeoCoordinate(-40.0, -140.0)) + << false << true; + + QTest::newRow("wrapped outside top") + << b2 + << QGeoRectangle(QGeoCoordinate(80.0, 150.0), + QGeoCoordinate(50.0, -150.0)) + << false << false; + + QTest::newRow("wrapped outside bottom") + << b2 + << QGeoRectangle(QGeoCoordinate(-50.0, 150.0), + QGeoCoordinate(-80.0, -150.0)) + << false << false; + + QTest::newRow("wrapped outside left") + << b2 + << QGeoRectangle(QGeoCoordinate(30.0, 70.0), + QGeoCoordinate(-30.0, 130.0)) + << false << false; + + QTest::newRow("wrapped outside right") + << b2 + << QGeoRectangle(QGeoCoordinate(30.0, -130.0), + QGeoCoordinate(-30.0, -70.0)) + << false << false; + + QTest::newRow("wrapped top left cross") + << b2 + << QGeoRectangle(QGeoCoordinate(40.0, 140.0), + QGeoCoordinate(20.0, 160.0)) + << false << true; + + QTest::newRow("wrapped top cross") + << b2 + << QGeoRectangle(QGeoCoordinate(40.0, 170.0), + QGeoCoordinate(20.0, -170.0)) + << false << true; + + QTest::newRow("wrapped top right cross") + << b2 + << QGeoRectangle(QGeoCoordinate(40.0, -160.0), + QGeoCoordinate(20.0, -140.0)) + << false << true; + + QTest::newRow("wrapped left cross") + << b2 + << QGeoRectangle(QGeoCoordinate(10.0, 140.0), + QGeoCoordinate(-10.0, 160.0)) + << false << true; + + QTest::newRow("wrapped right cross") + << b2 + << QGeoRectangle(QGeoCoordinate(10.0, -160.0), + QGeoCoordinate(-10.0, -140.0)) + << false << true; + + QTest::newRow("wrapped bottom left cross") + << b2 + << QGeoRectangle(QGeoCoordinate(-20.0, 140.0), + QGeoCoordinate(-40.0, 160.0)) + << false << true; + + QTest::newRow("wrapped bottom cross") + << b2 + << QGeoRectangle(QGeoCoordinate(-20.0, 170.0), + QGeoCoordinate(-40.0, -170.0)) + << false << true; + + QTest::newRow("wrapped bottom right cross") + << b2 + << QGeoRectangle(QGeoCoordinate(-20.0, -160.0), + QGeoCoordinate(-40.0, -140.0)) + << false << true; + + QTest::newRow("wrapped top left touch outside") + << b2 + << QGeoRectangle(QGeoCoordinate(50.0, 130.0), + QGeoCoordinate(30.0, 150.0)) + << false << true; + + QTest::newRow("wrapped top touch outside") + << b2 + << QGeoRectangle(QGeoCoordinate(50.0, 170.0), + QGeoCoordinate(30.0, -170.0)) + << false << true; + + QTest::newRow("wrapped top right touch outside") + << b2 + << QGeoRectangle(QGeoCoordinate(50.0, -150.0), + QGeoCoordinate(30.0, -130.0)) + << false << true; + + QTest::newRow("wrapped left touch outside") + << b2 + << QGeoRectangle(QGeoCoordinate(10.0, 130.0), + QGeoCoordinate(-10.0, 150.0)) + << false << true; + + QTest::newRow("wrapped right touch outside") + << b2 + << QGeoRectangle(QGeoCoordinate(10.0, -150.0), + QGeoCoordinate(-10.0, -130.0)) + << false << true; + + QTest::newRow("wrapped bottom left touch outside") + << b2 + << QGeoRectangle(QGeoCoordinate(-30.0, 150.0), + QGeoCoordinate(-50.0, 130.0)) + << false << true; + + QTest::newRow("wrapped bottom touch outside") + << b2 + << QGeoRectangle(QGeoCoordinate(-30.0, 170.0), + QGeoCoordinate(-50.0, -170.0)) + << false << true; + + QTest::newRow("wrapped bottom right touch outside") + << b2 + << QGeoRectangle(QGeoCoordinate(-30.0, -150.0), + QGeoCoordinate(-50.0, -130.0)) + << false << true; + + QTest::newRow("wrapped top left touch inside") + << b2 + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(10.0, 170.0)) + << true << true; + + QTest::newRow("wrapped top touch inside") + << b2 + << QGeoRectangle(QGeoCoordinate(30.0, 170.0), + QGeoCoordinate(10.0, -170.0)) + << true << true; + + QTest::newRow("wrapped top right touch inside") + << b2 + << QGeoRectangle(QGeoCoordinate(30.0, -170.0), + QGeoCoordinate(10.0, -150.0)) + << true << true; + + QTest::newRow("wrapped left touch inside") + << b2 + << QGeoRectangle(QGeoCoordinate(10.0, 150.0), + QGeoCoordinate(-10.0, 170.0)) + << true << true; + + QTest::newRow("wrapped right touch inside") + << b2 + << QGeoRectangle(QGeoCoordinate(10.0, -170.0), + QGeoCoordinate(-10.0, -150.0)) + << true << true; + + QTest::newRow("wrapped bottom left touch inside") + << b2 + << QGeoRectangle(QGeoCoordinate(-10.0, 150.0), + QGeoCoordinate(-30.0, 170.0)) + << true << true; + + QTest::newRow("wrapped bottom touch inside") + << b2 + << QGeoRectangle(QGeoCoordinate(-10.0, 170.0), + QGeoCoordinate(-30.0, -170.0)) + << true << true; + + QTest::newRow("wrapped bottom right touch inside") + << b2 + << QGeoRectangle(QGeoCoordinate(-10.0, -170.0), + QGeoCoordinate(-30.0, -150.0)) + << true << true; + + QTest::newRow("wrapped top lon strip") + << b2 + << QGeoRectangle(QGeoCoordinate(40.0, 140.0), + QGeoCoordinate(20.0, -140.0)) + << false << true; + + QTest::newRow("wrapped center lon strip") + << b2 + << QGeoRectangle(QGeoCoordinate(10.0, 140.0), + QGeoCoordinate(-10.0, -140.0)) + << false << true; + + QTest::newRow("wrapped bottom lon strip") + << b2 + << QGeoRectangle(QGeoCoordinate(-20.0, 140.0), + QGeoCoordinate(-40.0, -140.0)) + << false << true; + + QTest::newRow("wrapped left lat strip") + << b2 + << QGeoRectangle(QGeoCoordinate(40.0, 140.0), + QGeoCoordinate(-40.0, 160.0)) + << false << true; + + QTest::newRow("wrapped center lat strip") + << b2 + << QGeoRectangle(QGeoCoordinate(40.0, 170.0), + QGeoCoordinate(-40.0, -170.0)) + << false << true; + + QTest::newRow("wrapped right lat strip") + << b2 + << QGeoRectangle(QGeoCoordinate(40.0, -160.0), + QGeoCoordinate(-40.0, -140.0)) + << false << true; + + QTest::newRow("north pole touching") + << QGeoRectangle(QGeoCoordinate(90.0, 20.0), + QGeoCoordinate(40.0, 40.0)) + << QGeoRectangle(QGeoCoordinate(90.0, 60.0), + QGeoCoordinate(30.0, 80.0)) + << false << true; + + QTest::newRow("south pole touching") + << QGeoRectangle(QGeoCoordinate(40.0, 20.0), + QGeoCoordinate(-90.0, 40.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 60.0), + QGeoCoordinate(-90.0, 80.0)) + << false << true; +} + +void tst_QGeoRectangle::translate() +{ + QFETCH(QGeoRectangle, box); + QFETCH(double, degreesLatitude); + QFETCH(double, degreesLongitude); + QFETCH(QGeoRectangle, newBox); + + QGeoRectangle test = box.translated(degreesLatitude, degreesLongitude); + QCOMPARE(test, newBox); + box.translate(degreesLatitude, degreesLongitude); + QCOMPARE(box, newBox); + +} + +void tst_QGeoRectangle::translate_data() +{ + QTest::addColumn("box"); + QTest::addColumn("degreesLatitude"); + QTest::addColumn("degreesLongitude"); + QTest::addColumn("newBox"); + + QTest::newRow("invalid") + << QGeoRectangle() + << 20.0 + << 20.0 + << QGeoRectangle(); + + QTest::newRow("360 width") + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)) + << 20.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(50.0, -180.0), + QGeoCoordinate(-10.0, 180.0)); + + QTest::newRow("180 height") + << QGeoRectangle(QGeoCoordinate(90.0, -30.0), + QGeoCoordinate(-90.0, 30.0)) + << 20.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(90.0, -10.0), + QGeoCoordinate(-90.0, 50.0)); + + QTest::newRow("non wrapping -> non wrapping") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << 20.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(50.0, -10.0), + QGeoCoordinate(-10.0, 50.0)); + + QTest::newRow("non wrapping -> wrapping") + << QGeoRectangle(QGeoCoordinate(30.0, 110.0), + QGeoCoordinate(-30.0, 170.0)) + << 20.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(50.0, 130.0), + QGeoCoordinate(-10.0, -170.0)); + + QTest::newRow("non wrapping -> north clip") + << QGeoRectangle(QGeoCoordinate(80.0, -30.0), + QGeoCoordinate(20.0, 30.0)) + << 20.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(90.0, -10.0), + QGeoCoordinate(40.0, 50.0)); + + QTest::newRow("non wrapping -> south clip") + << QGeoRectangle(QGeoCoordinate(-20.0, -30.0), + QGeoCoordinate(-80.0, 30.0)) + << -20.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(-40.0, -10.0), + QGeoCoordinate(-90.0, 50.0)); + + QTest::newRow("wrapping -> non wrapping") + << QGeoRectangle(QGeoCoordinate(30.0, 130.0), + QGeoCoordinate(-30.0, -170.0)) + << 20.0 + << -20.0 + << QGeoRectangle(QGeoCoordinate(50.0, 110.0), + QGeoCoordinate(-10.0, 170.0)); + + QTest::newRow("wrapping -> wrapping") + << QGeoRectangle(QGeoCoordinate(30.0, 130.0), + QGeoCoordinate(-30.0, -170.0)) + << 20.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(50.0, 150.0), + QGeoCoordinate(-10.0, -150.0)); + + QTest::newRow("wrapping -> north clip") + << QGeoRectangle(QGeoCoordinate(80.0, 130.0), + QGeoCoordinate(20.0, -170.0)) + << 20.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(90.0, 150.0), + QGeoCoordinate(40.0, -150.0)); + + QTest::newRow("wrapping -> south clip") + << QGeoRectangle(QGeoCoordinate(-20.0, 130.0), + QGeoCoordinate(-80.0, -170.0)) + << -20.0 + << 20.0 + << QGeoRectangle(QGeoCoordinate(-40.0, 150.0), + QGeoCoordinate(-90.0, -150.0)); +} + +void tst_QGeoRectangle::unite() +{ + QFETCH(QGeoRectangle, in1); + QFETCH(QGeoRectangle, in2); + QFETCH(QGeoRectangle, out); + + QCOMPARE(in1.united(in2), out); + QCOMPARE(in2.united(in1), out); + + QCOMPARE(in1 | in2, out); + QCOMPARE(in2 | in1, out); + + QGeoRectangle united1 = QGeoRectangle(in1); + united1 |= in2; + QCOMPARE(united1, out); + + QGeoRectangle united2 = QGeoRectangle(in2); + united2 |= in1; + QCOMPARE(united2, out); +} + +void tst_QGeoRectangle::unite_data() +{ + QTest::addColumn("in1"); + QTest::addColumn("in2"); + QTest::addColumn("out"); + + QTest::newRow("central and taller") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(50.0, -30.0), + QGeoCoordinate(-50.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(50.0, -30.0), + QGeoCoordinate(-50.0, 30.0)); + + QTest::newRow("central and 180 high") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(90.0, -30.0), + QGeoCoordinate(-90.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(90.0, -30.0), + QGeoCoordinate(-90.0, 30.0)); + + QTest::newRow("central and non overlapping higher") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(60.0, -30.0), + QGeoCoordinate(50.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(60.0, -30.0), + QGeoCoordinate(-30.0, 30.0)); + + QTest::newRow("central and overlapping higher") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(60.0, -30.0), + QGeoCoordinate(0.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(60.0, -30.0), + QGeoCoordinate(-30.0, 30.0)); + + QTest::newRow("central and touching higher") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(60.0, -30.0), + QGeoCoordinate(30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(60.0, -30.0), + QGeoCoordinate(-30.0, 30.0)); + + QTest::newRow("central and non overlapping lower") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(-50.0, -30.0), + QGeoCoordinate(-60.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-60.0, 30.0)); + + QTest::newRow("central and overlapping lower") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(0.0, -30.0), + QGeoCoordinate(-60.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-60.0, 30.0)); + + QTest::newRow("central and touching lower") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(-30.0, -30.0), + QGeoCoordinate(-60.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-60.0, 30.0)); + + QTest::newRow("non wrapping central and wider") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -50.0), + QGeoCoordinate(-30.0, 50.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -50.0), + QGeoCoordinate(-30.0, 50.0)); + + QTest::newRow("non wrapping central and 360 width") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)); + + QTest::newRow("non wrapping central and non overlapping non wrapping left") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -110.0), + QGeoCoordinate(-30.0, -50.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -110.0), + QGeoCoordinate(-30.0, 30.0)); + + QTest::newRow("non wrapping central and overlapping non wrapping left") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -80.0), + QGeoCoordinate(-30.0, -20.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -80.0), + QGeoCoordinate(-30.0, 30.0)); + + QTest::newRow("non wrapping central and touching non wrapping left") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -90.0), + QGeoCoordinate(-30.0, -30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -90.0), + QGeoCoordinate(-30.0, 30.0)); + + QTest::newRow("non wrapping central and non overlapping non wrapping right") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 50.0), + QGeoCoordinate(-30.0, 110.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 110.0)); + + QTest::newRow("non wrapping central and overlapping non wrapping right") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 20.0), + QGeoCoordinate(-30.0, 80.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 80.0)); + + QTest::newRow("non wrapping central and touching non wrapping right") + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 30.0), + QGeoCoordinate(-30.0, 90.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 90.0)); + + QTest::newRow("wrapping and wider") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 130.0), + QGeoCoordinate(-30.0, -130.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 130.0), + QGeoCoordinate(-30.0, -130.0)); + + QTest::newRow("wrapping and 360 width") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)); + + QTest::newRow("wrapping and non overlapping right") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -130.0), + QGeoCoordinate(-30.0, -70.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -70.0)); + + QTest::newRow("wrapping and overlapping right") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -160.0), + QGeoCoordinate(-30.0, -70.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -70.0)); + + QTest::newRow("wrapping and touching right") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -150.0), + QGeoCoordinate(-30.0, -90.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -90.0)); + + QTest::newRow("wrapping and non overlapping left") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 70.0), + QGeoCoordinate(-30.0, 130.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 70.0), + QGeoCoordinate(-30.0, -150.0)); + + QTest::newRow("wrapping and overlapping left") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 100.0), + QGeoCoordinate(-30.0, 160.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 100.0), + QGeoCoordinate(-30.0, -150.0)); + + QTest::newRow("wrapping and touching left") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 90.0), + QGeoCoordinate(-30.0, 150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 90.0), + QGeoCoordinate(-30.0, -150.0)); + + QTest::newRow("wrapping and non overlapping center") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -30.0), + QGeoCoordinate(-30.0, 30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)); + + QTest::newRow("wrapping and overlapping center") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -160.0), + QGeoCoordinate(-30.0, 160.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)); + + QTest::newRow("wrapping and touching center") + << QGeoRectangle(QGeoCoordinate(30.0, 150.0), + QGeoCoordinate(-30.0, -150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -150.0), + QGeoCoordinate(-30.0, 150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)); + + QTest::newRow("small gap over zero line") + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, -10.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 10.0), + QGeoCoordinate(-30.0, 20.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, 20.0)); + + QTest::newRow("small gap before zero line") + << QGeoRectangle(QGeoCoordinate(30.0, -40.0), + QGeoCoordinate(-30.0, -30.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, -10.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -40.0), + QGeoCoordinate(-30.0, -10.0)); + + QTest::newRow("small gap after zero line") + << QGeoRectangle(QGeoCoordinate(30.0, 10.0), + QGeoCoordinate(-30.0, 20.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 30.0), + QGeoCoordinate(-30.0, 40.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 10.0), + QGeoCoordinate(-30.0, 40.0)); + + QTest::newRow("small gap over dateline") + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, 170.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -170.0), + QGeoCoordinate(-30.0, -160.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, -160.0)); + + QTest::newRow("small gap before dateline") + << QGeoRectangle(QGeoCoordinate(30.0, 140.0), + QGeoCoordinate(-30.0, 150.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, 170.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 140.0), + QGeoCoordinate(-30.0, 170.0)); + + QTest::newRow("small gap after dateline") + << QGeoRectangle(QGeoCoordinate(30.0, -170.0), + QGeoCoordinate(-30.0, -160.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -150.0), + QGeoCoordinate(-30.0, -140.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -170.0), + QGeoCoordinate(-30.0, -140.0)); + + QTest::newRow("90-degree inner gap over zero line") + << QGeoRectangle(QGeoCoordinate(30.0, -55.0), + QGeoCoordinate(-30.0, -45.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 45.0), + QGeoCoordinate(-30.0, 55.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -55.0), + QGeoCoordinate(-30.0, 55.0)); + + QTest::newRow("90-degree inner gap before zero line") + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, -10.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -65.0), + QGeoCoordinate(-30.0, -55.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -65.0), + QGeoCoordinate(-30.0, -10.0)); + + QTest::newRow("90-degree inner gap after zero line") + << QGeoRectangle(QGeoCoordinate(30.0, 65.0), + QGeoCoordinate(-30.0, 75.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 10.0), + QGeoCoordinate(-30.0, 20.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 10.0), + QGeoCoordinate(-30.0, 75.0)); + + QTest::newRow("90-degree inner gap over dateline") + << QGeoRectangle(QGeoCoordinate(30.0, 125.0), + QGeoCoordinate(-30.0, 135.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -135.0), + QGeoCoordinate(-30.0, -125.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 125.0), + QGeoCoordinate(-30.0, -125.0)); + + QTest::newRow("90-degree inner gap before dateline") + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, 170.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 50.0), + QGeoCoordinate(-30.0, 60.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 50.0), + QGeoCoordinate(-30.0, 170.0)); + + QTest::newRow("90-degree inner gap after dateline") + << QGeoRectangle(QGeoCoordinate(30.0, -170.0), + QGeoCoordinate(-30.0, -160.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -60.0), + QGeoCoordinate(-30.0, -50.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -170.0), + QGeoCoordinate(-30.0, -50.0)); + + QTest::newRow("180-degree inner gap centered on zero line") + << QGeoRectangle(QGeoCoordinate(30.0, -100.0), + QGeoCoordinate(-30.0, -90.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 90.0), + QGeoCoordinate(-30.0, 100.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 90.0), + QGeoCoordinate(-30.0, -90.0)); + + QTest::newRow("180-degree outer gap cenetered on zero line") + << QGeoRectangle(QGeoCoordinate(30.0, -90.0), + QGeoCoordinate(-30.0, -80.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 80.0), + QGeoCoordinate(-30.0, 90.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -90.0), + QGeoCoordinate(-30.0, 90.0)); + + QTest::newRow("180-degree shift centered on zero line") + << QGeoRectangle(QGeoCoordinate(30.0, -100.0), + QGeoCoordinate(-30.0, -80.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 80.0), + QGeoCoordinate(-30.0, 100.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)); + + QTest::newRow("180-degree inner gap centered on dateline") + << QGeoRectangle(QGeoCoordinate(30.0, 80.0), + QGeoCoordinate(-30.0, 90.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -90.0), + QGeoCoordinate(-30.0, -80.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -90.0), + QGeoCoordinate(-30.0, 90.0)); + + QTest::newRow("180-degree outer gap centered on dateline") + << QGeoRectangle(QGeoCoordinate(30.0, 90.0), + QGeoCoordinate(-30.0, 100.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -100.0), + QGeoCoordinate(-30.0, -90.0)) + << QGeoRectangle(QGeoCoordinate(30.0, 90.0), + QGeoCoordinate(-30.0, -90.0)); + + QTest::newRow("180-degree shift centered on dateline") + << QGeoRectangle(QGeoCoordinate(30.0, 80.0), + QGeoCoordinate(-30.0, 100.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -100.0), + QGeoCoordinate(-30.0, -80.0)) + << QGeoRectangle(QGeoCoordinate(30.0, -180.0), + QGeoCoordinate(-30.0, 180.0)); +} + + +void tst_QGeoRectangle::extendShape() +{ + QFETCH(QGeoRectangle, box); + QFETCH(QGeoCoordinate, coord); + QFETCH(QGeoRectangle, out); + + box.extendShape(coord); + QCOMPARE(box, out); +} + +void tst_QGeoRectangle::extendShape_data() +{ + QTest::addColumn("box"); + QTest::addColumn("coord"); + QTest::addColumn("out"); + + QTest::newRow("valid rect - invalid coordinate") + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, 20.0)) + << QGeoCoordinate(100.0, 190.0) + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, 20)); + QTest::newRow("invalid rect - valid coordinate") + << QGeoRectangle() + << QGeoCoordinate(10.0, 10.0) + << QGeoRectangle(); + QTest::newRow("inside rect - not wrapped") + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, 20.0)) + << QGeoCoordinate(10.0, 10.0) + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, 20)); + QTest::newRow("lat outside rect - not wrapped") + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, 20.0)) + << QGeoCoordinate(40.0, 10.0) + << QGeoRectangle(QGeoCoordinate(40.0, -20.0), + QGeoCoordinate(-30.0, 20)); + QTest::newRow("positive lon outside rect - not wrapped") + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, 20.0)) + << QGeoCoordinate(10.0, 40.0) + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, 40)); + QTest::newRow("negative lon outside rect - not wrapped") + << QGeoRectangle(QGeoCoordinate(30.0, -20.0), + QGeoCoordinate(-30.0, 20.0)) + << QGeoCoordinate(10.0, -40.0) + << QGeoRectangle(QGeoCoordinate(30.0, -40.0), + QGeoCoordinate(-30.0, 20.0)); + QTest::newRow("inside rect - wrapped") + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, -160.0)) + << QGeoCoordinate(10.0, -170.0) + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, -160.0)); + QTest::newRow("lat outside rect - wrapped") + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, -160.0)) + << QGeoCoordinate(-40.0, -170.0) + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-40.0, -160.0)); + QTest::newRow("positive lon outside rect - wrapped") + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, -160.0)) + << QGeoCoordinate(10.0, 140.0) + << QGeoRectangle(QGeoCoordinate(30.0, 140.0), + QGeoCoordinate(-30.0, -160.0)); + QTest::newRow("negative lon outside rect - wrapped") + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, -160.0)) + << QGeoCoordinate(10.0, -140.0) + << QGeoRectangle(QGeoCoordinate(30.0, 160.0), + QGeoCoordinate(-30.0, -140.0)); + QTest::newRow("extending over 180 degree line eastward") + << QGeoRectangle(QGeoCoordinate(30.0, 130.0), + QGeoCoordinate(-30.0, 160.0)) + << QGeoCoordinate(10.0, -170.0) + << QGeoRectangle(QGeoCoordinate(30.0, 130.0), + QGeoCoordinate(-30.0, -170)); + QTest::newRow("extending over -180 degree line westward") + << QGeoRectangle(QGeoCoordinate(30.0, -160.0), + QGeoCoordinate(-30.0, -130.0)) + << QGeoCoordinate(10.0, 170.0) + << QGeoRectangle(QGeoCoordinate(30.0, 170.0), + QGeoCoordinate(-30.0, -130)); +} + +void tst_QGeoRectangle::areaComparison_data() +{ + QTest::addColumn("area"); + QTest::addColumn("box"); + QTest::addColumn("equal"); + + QGeoRectangle b1(QGeoCoordinate(10.0, 0.0), QGeoCoordinate(0.0, 10.0)); + QGeoRectangle b2(QGeoCoordinate(20.0, 0.0), QGeoCoordinate(0.0, 20.0)); + QGeoCircle c(QGeoCoordinate(0.0, 0.0), 10); + + QTest::newRow("default constructed") << QGeoShape() << QGeoRectangle() << false; + QTest::newRow("b1 b1") << QGeoShape(b1) << b1 << true; + QTest::newRow("b1 b2") << QGeoShape(b1) << b2 << false; + QTest::newRow("b2 b1") << QGeoShape(b2) << b1 << false; + QTest::newRow("b2 b2") << QGeoShape(b2) << b2 << true; + QTest::newRow("c b1") << QGeoShape(c) << b1 << false; +} + +void tst_QGeoRectangle::areaComparison() +{ + QFETCH(QGeoShape, area); + QFETCH(QGeoRectangle, box); + QFETCH(bool, equal); + + QCOMPARE((area == box), equal); + QCOMPARE((area != box), !equal); + + QCOMPARE((box == area), equal); + QCOMPARE((box != area), !equal); +} + +void tst_QGeoRectangle::circleComparison_data() +{ + QTest::addColumn("circle"); + QTest::addColumn("box"); + QTest::addColumn("equal"); + + QGeoRectangle b(QGeoCoordinate(10.0, 0.0), QGeoCoordinate(0.0, 10.0)); + QGeoCircle c(QGeoCoordinate(0.0, 0.0), 10); + + QTest::newRow("default constructed") << QGeoCircle() << QGeoRectangle() << false; + QTest::newRow("c b") << c << b << false; +} + +void tst_QGeoRectangle::circleComparison() +{ + QFETCH(QGeoCircle, circle); + QFETCH(QGeoRectangle, box); + QFETCH(bool, equal); + + QCOMPARE((circle == box), equal); + QCOMPARE((circle != box), !equal); + + QCOMPARE((box == circle), equal); + QCOMPARE((box != circle), !equal); +} + +QTEST_MAIN(tst_QGeoRectangle) +#include "tst_qgeorectangle.moc" + diff --git a/tests/auto/qgeoroute/qgeoroute.pro b/tests/auto/qgeoroute/qgeoroute.pro new file mode 100644 index 0000000..87ace5c --- /dev/null +++ b/tests/auto/qgeoroute/qgeoroute.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeoroute + +# Input +HEADERS += tst_qgeoroute.h +SOURCES += tst_qgeoroute.cpp + +QT += location testlib diff --git a/tests/auto/qgeoroute/tst_qgeoroute.cpp b/tests/auto/qgeoroute/tst_qgeoroute.cpp new file mode 100644 index 0000000..6cef986 --- /dev/null +++ b/tests/auto/qgeoroute/tst_qgeoroute.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tst_qgeoroute.h" + + +tst_QGeoRoute::tst_QGeoRoute() +{ +} + +void tst_QGeoRoute::initTestCase() +{ +} + +void tst_QGeoRoute::cleanupTestCase() +{ +} + +void tst_QGeoRoute::init() +{ + qgeoroute = new QGeoRoute(); + qgeocoordinate = new QGeoCoordinate(); +} + +void tst_QGeoRoute::cleanup() +{ + delete qgeoroute; + delete qgeocoordinate; +} + +void tst_QGeoRoute::constructor() +{ + QString empty = ""; + QGeoRectangle *boundingbox = new QGeoRectangle(); + + QCOMPARE(qgeoroute->bounds(), *boundingbox); + QCOMPARE(qgeoroute->distance(), qreal(0.0)); + QCOMPARE(qgeoroute->path().size(), 0); + QCOMPARE(qgeoroute->routeId(), empty); + QCOMPARE(qgeoroute->travelTime(), 0); + + delete boundingbox; +} + +void tst_QGeoRoute::copy_constructor() +{ + QGeoRoute *qgeoroutecopy = new QGeoRoute(*qgeoroute); + QCOMPARE(*qgeoroute, *qgeoroutecopy); + delete qgeoroutecopy; +} + +void tst_QGeoRoute::destructor() +{ + QGeoRoute *qgeoroutecopy; + + qgeoroutecopy = new QGeoRoute(); + delete qgeoroutecopy; + + qgeoroutecopy = new QGeoRoute(*qgeoroute); + delete qgeoroutecopy; +} + +void tst_QGeoRoute::bounds() +{ + qgeocoordinate->setLatitude(13.3851); + qgeocoordinate->setLongitude(52.5312); + + QGeoRectangle *qgeoboundingbox = new QGeoRectangle(*qgeocoordinate,0.4,0.4); + + qgeoroute->setBounds(*qgeoboundingbox); + + QCOMPARE(qgeoroute->bounds(), *qgeoboundingbox); + + qgeoboundingbox->setWidth(23.1); + + QVERIFY(qgeoroute->bounds().width() != qgeoboundingbox->width()); + + delete qgeoboundingbox; +} + +void tst_QGeoRoute::distance() +{ + qreal distance = 0.0; + + qgeoroute->setDistance(distance); + QCOMPARE(qgeoroute->distance(), distance); + + distance = 34.4324; + QVERIFY(qgeoroute->distance() != distance); + + qgeoroute->setDistance(distance); + QCOMPARE(qgeoroute->distance(), distance); +} + +void tst_QGeoRoute::path() +{ + QFETCH(QList, coordinates); + + QList path; + + for (int i = 0; i < coordinates.size(); i += 2) { + path.append(QGeoCoordinate(coordinates.at(i),coordinates.at(i+1))); + } + + qgeoroute->setPath(path); + + QList pathRetrieved = qgeoroute->path(); + QCOMPARE(pathRetrieved, path); + + for (int i = 0; i < pathRetrieved.size(); i++) { + QCOMPARE(pathRetrieved.at(i), path.at(i)); + } +} + +void tst_QGeoRoute::path_data() +{ + QTest::addColumn >("coordinates"); + + QList coordinates; + + coordinates << 0.0 << 0.0; + QTest::newRow("path1") << coordinates ; + + coordinates << -23.23 << 54.45345; + QTest::newRow("path2") << coordinates ; + + coordinates << -85.4324 << -121.343; + QTest::newRow("path3") << coordinates ; + + coordinates << 1.323 << 12.323; + QTest::newRow("path4") << coordinates ; + + coordinates << 1324.323 << 143242.323; + QTest::newRow("path5") << coordinates ; +} + +void tst_QGeoRoute::request() +{ + qgeocoordinate->setLatitude(65.654); + qgeocoordinate->setLongitude(0.4324); + + QGeoCoordinate *qgeocoordinatecopy = new QGeoCoordinate(34.54 , -21.32); + + QList path; + path.append(*qgeocoordinate); + path.append(*qgeocoordinatecopy); + + qgeorouterequest = new QGeoRouteRequest(path); + + qgeoroute->setRequest(*qgeorouterequest); + + QCOMPARE(qgeoroute->request(), *qgeorouterequest); + + QGeoCoordinate *qgeocoordinatecopy2 = new QGeoCoordinate(4.7854 , -121.32); + path.append(*qgeocoordinatecopy2); + + QGeoRouteRequest *qgeorouterequestcopy = new QGeoRouteRequest(path); + + QVERIFY(qgeoroute->request() != *qgeorouterequestcopy); + + delete qgeocoordinatecopy; + delete qgeocoordinatecopy2; + delete qgeorouterequest; + delete qgeorouterequestcopy; +} + +void tst_QGeoRoute::routeId() +{ + QString text = "routeId 4504"; + + qgeoroute->setRouteId(text); + + QCOMPARE(qgeoroute->routeId(), text); + + text = "routeId 1111"; + QVERIFY(qgeoroute->routeId() != text); + +} + +void tst_QGeoRoute::firstrouteSegments() +{ + qgeoroutesegment = new QGeoRouteSegment(); + qgeoroutesegment->setDistance(35.453); + qgeoroutesegment->setTravelTime(56); + + qgeoroute->setFirstRouteSegment(*qgeoroutesegment); + + QCOMPARE(qgeoroute->firstRouteSegment(), *qgeoroutesegment); + + QGeoRouteSegment *qgeoroutesegmentcopy = new QGeoRouteSegment (); + qgeoroutesegmentcopy->setDistance(435.432); + qgeoroutesegmentcopy->setTravelTime(786); + + QVERIFY(qgeoroute->firstRouteSegment() != *qgeoroutesegmentcopy); + + delete qgeoroutesegment; + delete qgeoroutesegmentcopy; + +} + +void tst_QGeoRoute::travelMode() +{ + QFETCH(QGeoRouteRequest::TravelMode, mode); + + qgeoroute->setTravelMode(mode); + QCOMPARE(qgeoroute->travelMode(), mode); +} +void tst_QGeoRoute::travelMode_data() +{ + QTest::addColumn("mode"); + + QTest::newRow("mode1") << QGeoRouteRequest::CarTravel; + QTest::newRow("mode2") << QGeoRouteRequest::PedestrianTravel; + QTest::newRow("mode3") << QGeoRouteRequest::BicycleTravel; + QTest::newRow("mode4") << QGeoRouteRequest::PublicTransitTravel; + QTest::newRow("mode5") << QGeoRouteRequest::TruckTravel; +} + +void tst_QGeoRoute::travelTime() +{ + int time = 0; + qgeoroute->setTravelTime(time); + + QCOMPARE (qgeoroute->travelTime(), time); + + time = 35; + + QVERIFY (qgeoroute->travelTime() != time); + + qgeoroute->setTravelTime(time); + QCOMPARE (qgeoroute->travelTime(), time); +} + +void tst_QGeoRoute::operators() +{ + QGeoRoute *qgeoroutecopy = new QGeoRoute(*qgeoroute); + + QVERIFY(qgeoroute->operator ==(*qgeoroutecopy)); + QVERIFY(!qgeoroute->operator !=(*qgeoroutecopy)); + + qgeoroute->setDistance(543.324); + qgeoroute->setRouteId("RouteId 111"); + qgeoroute->setTravelMode(QGeoRouteRequest::PedestrianTravel); + qgeoroute->setTravelTime(10); + + qgeoroutecopy->setDistance(12.21); + qgeoroutecopy->setRouteId("RouteId 666"); + qgeoroutecopy->setTravelMode(QGeoRouteRequest::BicycleTravel); + qgeoroutecopy->setTravelTime(99); + + QEXPECT_FAIL("", "QGeoRoute equality operators broken", Continue); + QVERIFY(!(qgeoroute->operator ==(*qgeoroutecopy))); + QEXPECT_FAIL("", "QGeoRoute equality operators broken", Continue); + QVERIFY(qgeoroute->operator !=(*qgeoroutecopy)); + + *qgeoroutecopy = qgeoroutecopy->operator =(*qgeoroute); + QVERIFY(qgeoroute->operator ==(*qgeoroutecopy)); + QVERIFY(!qgeoroute->operator !=(*qgeoroutecopy)); + + delete qgeoroutecopy; +} + + + +QTEST_APPLESS_MAIN(tst_QGeoRoute); diff --git a/tests/auto/qgeoroute/tst_qgeoroute.h b/tests/auto/qgeoroute/tst_qgeoroute.h new file mode 100644 index 0000000..ace9095 --- /dev/null +++ b/tests/auto/qgeoroute/tst_qgeoroute.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_QGEOROUTE_H +#define TST_QGEOROUTE_H + +#include +#include +#include + +#include +#include +#include +#include +#include + + +QT_USE_NAMESPACE + +class tst_QGeoRoute : public QObject +{ + Q_OBJECT + +public: + tst_QGeoRoute(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + //Start unit test for QGeoRoute + void constructor(); + void copy_constructor(); + void destructor(); + void bounds(); + void distance(); + void path(); + void path_data(); + void request(); + void routeId(); + void firstrouteSegments(); + void travelMode(); + void travelMode_data(); + void travelTime(); + void operators(); + //End Unit Test for QGeoRoute + +private: + QGeoRoute *qgeoroute; + QGeoRectangle *qgeoboundingbox; + QGeoCoordinate *qgeocoordinate; + QGeoRouteRequest *qgeorouterequest; + QGeoRouteSegment *qgeoroutesegment; + + +}; + +Q_DECLARE_METATYPE( QList); +Q_DECLARE_METATYPE( QList); +Q_DECLARE_METATYPE( QGeoRouteRequest::TravelMode); + + +#endif // TST_QGEOROUTE_H diff --git a/tests/auto/qgeoroutereply/qgeoroutereply.pro b/tests/auto/qgeoroutereply/qgeoroutereply.pro new file mode 100644 index 0000000..29f8480 --- /dev/null +++ b/tests/auto/qgeoroutereply/qgeoroutereply.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeoroutereply + +# Input +HEADERS += tst_qgeoroutereply.h +SOURCES += tst_qgeoroutereply.cpp + +QT += location testlib diff --git a/tests/auto/qgeoroutereply/tst_qgeoroutereply.cpp b/tests/auto/qgeoroutereply/tst_qgeoroutereply.cpp new file mode 100644 index 0000000..447181f --- /dev/null +++ b/tests/auto/qgeoroutereply/tst_qgeoroutereply.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tst_qgeoroutereply.h" + +QT_USE_NAMESPACE + +void tst_QGeoRouteReply::initTestCase() +{ + qgeocoordinate1 = new QGeoCoordinate(43.5435 , 76.342); + qgeocoordinate2 = new QGeoCoordinate(-43.5435 , 176.342); + qgeocoordinate3 = new QGeoCoordinate(-13.5435 , +76.342); + + waypoints.append(*qgeocoordinate1); + waypoints.append(*qgeocoordinate2); + waypoints.append(*qgeocoordinate3); + + qgeorouterequest = new QGeoRouteRequest(waypoints); + reply = new SubRouteReply(*qgeorouterequest); +} + +void tst_QGeoRouteReply::cleanupTestCase() +{ + delete qgeocoordinate1; + delete qgeocoordinate2; + delete qgeocoordinate3; + delete qgeorouterequest; + delete reply; +} + +void tst_QGeoRouteReply::init() +{ + qRegisterMetaType(); + signalerror = new QSignalSpy(reply, SIGNAL(error(QGeoRouteReply::Error,QString))); + signalfinished = new QSignalSpy(reply, SIGNAL(finished())); +} + +void tst_QGeoRouteReply::cleanup() +{ + delete signalerror; + delete signalfinished; +} + +void tst_QGeoRouteReply::constructor() +{ + QVERIFY(!reply->isFinished()); + QCOMPARE(reply->error(), QGeoRouteReply::NoError); + QCOMPARE(reply->request(), *qgeorouterequest); + + QVERIFY(signalerror->isValid()); + QVERIFY(signalfinished->isValid()); + + QCOMPARE(signalerror->count(),0); + QCOMPARE(signalfinished->count(),0); +} + +void tst_QGeoRouteReply::constructor_error() +{ + QFETCH(QGeoRouteReply::Error,error); + QFETCH(QString,msg); + + QVERIFY(signalerror->isValid()); + QVERIFY(signalfinished->isValid()); + + QGeoRouteReply *qgeoroutereplycopy = new QGeoRouteReply(error, msg, 0); + + QCOMPARE(signalerror->count(), 0); + QCOMPARE(signalfinished->count(), 0); + + QVERIFY(qgeoroutereplycopy->isFinished()); + QCOMPARE(qgeoroutereplycopy->error(), error); + QCOMPARE(qgeoroutereplycopy->errorString(), msg); + + delete qgeoroutereplycopy; +} + +void tst_QGeoRouteReply::constructor_error_data() +{ + QTest::addColumn("error"); + QTest::addColumn("msg"); + + QTest::newRow("error1") << QGeoRouteReply::NoError << "No error."; + QTest::newRow("error2") << QGeoRouteReply::EngineNotSetError << "Engine Not Set Error."; + QTest::newRow("error3") << QGeoRouteReply::CommunicationError << "Communication Error."; + QTest::newRow("error4") << QGeoRouteReply::ParseError << "Parse Error."; + QTest::newRow("error5") << QGeoRouteReply::UnsupportedOptionError << "Unsupported Option Error."; + QTest::newRow("error6") << QGeoRouteReply::UnknownError << "Unknown Error."; + +} + +void tst_QGeoRouteReply::destructor() +{ + QGeoRouteReply *qgeoroutereplycopy; + + QFETCH(QGeoRouteReply::Error, error); + QFETCH(QString, msg); + + qgeoroutereplycopy = new QGeoRouteReply(error, msg, 0); + delete qgeoroutereplycopy; + +} + +void tst_QGeoRouteReply::destructor_data() +{ + tst_QGeoRouteReply::constructor_error_data(); +} + +void tst_QGeoRouteReply::routes() +{ + QList routes; + QGeoRoute *route1 = new QGeoRoute(); + QGeoRoute *route2 = new QGeoRoute(); + + route1->setDistance(15.12); + route2->setDistance(20.12); + + routes.append(*route1); + routes.append(*route2); + + reply->callSetRoutes(routes); + + QList routescopy; + routescopy = reply->routes(); + QCOMPARE(routescopy,routes); + + QCOMPARE(routescopy.at(0).distance(),route1->distance()); + QCOMPARE(routescopy.at(1).distance(),route2->distance()); + + delete route1; + delete route2; +} + +void tst_QGeoRouteReply::finished() +{ + QVERIFY(signalerror->isValid()); + QVERIFY(signalfinished->isValid()); + + QCOMPARE(signalerror->count(), 0); + QCOMPARE(signalfinished->count(), 0); + + reply->callSetFinished(true); + + QCOMPARE(signalerror->count(), 0); + QCOMPARE(signalfinished->count(), 1); + + reply->callSetFinished(false); + + QCOMPARE(signalerror->count(), 0); + QCOMPARE(signalfinished->count(), 1); + + reply->callSetFinished(true); + + QCOMPARE(signalerror->count(), 0); + QCOMPARE(signalfinished->count(), 2); +} + +void tst_QGeoRouteReply::abort() +{ + QVERIFY(signalerror->isValid()); + QVERIFY(signalfinished->isValid()); + + QCOMPARE(signalerror->count(), 0); + QCOMPARE(signalfinished->count(), 0); + + reply->abort(); + + QCOMPARE(signalerror->count(), 0); + QCOMPARE(signalfinished->count(), 0); + + reply->abort(); + reply->callSetFinished(false); + reply->abort(); + + QCOMPARE(signalerror->count(), 0); + QCOMPARE(signalfinished->count(), 1); +} + +void tst_QGeoRouteReply::error() +{ + QFETCH(QGeoRouteReply::Error, error); + QFETCH(QString, msg); + + QVERIFY(signalerror->isValid()); + QVERIFY(signalfinished->isValid()); + QCOMPARE(signalerror->count(), 0); + + reply->callSetError(error,msg); + + QCOMPARE(signalerror->count(), 1); + QCOMPARE(signalfinished->count(), 1); + QCOMPARE(reply->errorString(), msg); + QCOMPARE(reply->error(), error); +} + +void tst_QGeoRouteReply::error_data() +{ + QTest::addColumn("error"); + QTest::addColumn("msg"); + + QTest::newRow("error1") << QGeoRouteReply::NoError << "No error."; + QTest::newRow("error2") << QGeoRouteReply::EngineNotSetError << "Engine Not Set Error."; + QTest::newRow("error3") << QGeoRouteReply::CommunicationError << "Communication Error."; + QTest::newRow("error4") << QGeoRouteReply::ParseError << "Parse Error."; + QTest::newRow("error5") << QGeoRouteReply::UnsupportedOptionError << "Unsupported Option Error."; + QTest::newRow("error6") << QGeoRouteReply::UnknownError << "Unknown Error."; +} + +void tst_QGeoRouteReply::request() +{ + SubRouteReply *rr = new SubRouteReply(*qgeorouterequest); + + QCOMPARE(rr->request(), *qgeorouterequest); + + delete rr; +} + +QTEST_APPLESS_MAIN(tst_QGeoRouteReply); diff --git a/tests/auto/qgeoroutereply/tst_qgeoroutereply.h b/tests/auto/qgeoroutereply/tst_qgeoroutereply.h new file mode 100644 index 0000000..aa08d72 --- /dev/null +++ b/tests/auto/qgeoroutereply/tst_qgeoroutereply.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_QGEOROUTEREPLY_H +#define TST_QGEOROUTEREPLY_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +QT_USE_NAMESPACE +class SubRouteReply :public QGeoRouteReply +{ + Q_OBJECT +public: + SubRouteReply(QGeoRouteRequest request):QGeoRouteReply(request) {} + void callSetError(QGeoRouteReply::Error error, QString msg) {setError(error,msg);} + void callSetFinished(bool finished) {setFinished(finished);} + void callSetRoutes(const QList & routes ) {setRoutes(routes);} +}; + +class tst_QGeoRouteReply :public QObject +{ + Q_OBJECT + +public slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + //Start Unit Test for QGeoRouteReply +private slots: + void constructor(); + void constructor_error(); + void constructor_error_data(); + void destructor(); + void destructor_data(); + void routes(); + void finished(); + void abort(); + void error(); + void error_data(); + void request(); + //End Unit Test for QGeoRouteReply + + +private: + QGeoCoordinate *qgeocoordinate1; + QGeoCoordinate *qgeocoordinate2; + QGeoCoordinate *qgeocoordinate3; + QGeoRouteRequest *qgeorouterequest; + QSignalSpy *signalerror; + QSignalSpy *signalfinished; + QList waypoints; + SubRouteReply* reply; +}; + +Q_DECLARE_METATYPE( QList); +Q_DECLARE_METATYPE( QList); +Q_DECLARE_METATYPE( QList); +Q_DECLARE_METATYPE( QGeoRouteReply::Error); + +#endif // TST_QGEOROUTEREPLY_H + diff --git a/tests/auto/qgeorouterequest/qgeorouterequest.pro b/tests/auto/qgeorouterequest/qgeorouterequest.pro new file mode 100644 index 0000000..1fc897c --- /dev/null +++ b/tests/auto/qgeorouterequest/qgeorouterequest.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeorouterequest + +# Input +HEADERS += tst_qgeorouterequest.h +SOURCES += tst_qgeorouterequest.cpp + +QT += location testlib diff --git a/tests/auto/qgeorouterequest/tst_qgeorouterequest.cpp b/tests/auto/qgeorouterequest/tst_qgeorouterequest.cpp new file mode 100644 index 0000000..5c43de6 --- /dev/null +++ b/tests/auto/qgeorouterequest/tst_qgeorouterequest.cpp @@ -0,0 +1,323 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tst_qgeorouterequest.h" + +#include + +QT_USE_NAMESPACE + +tst_QGeoRouteRequest::tst_QGeoRouteRequest() +{ +} + +void tst_QGeoRouteRequest::initTestCase() +{ +} + +void tst_QGeoRouteRequest::cleanupTestCase() +{ +} + +void tst_QGeoRouteRequest::init() +{ + qgeocoordinate = new QGeoCoordinate(); + qgeoboundingbox = new QGeoRectangle(); + qgeorouterequest = new QGeoRouteRequest(); +} + +void tst_QGeoRouteRequest::cleanup() +{ + delete qgeocoordinate; + delete qgeoboundingbox; + delete qgeorouterequest; +} + +void tst_QGeoRouteRequest::constructor_waypoints() +{ + QGeoCoordinate *qgeocoord1 = new QGeoCoordinate(43.5435, 76.342); + QGeoCoordinate *qgeocoord2 = new QGeoCoordinate(-43.5435, 176.342); + QGeoCoordinate *qgeocoord3 = new QGeoCoordinate(-13.5435, +76.342); + + QList waypoints; + waypoints.append(*qgeocoord1); + waypoints.append(*qgeocoord2); + waypoints.append(*qgeocoord3); + + QGeoRouteRequest *copy = new QGeoRouteRequest(waypoints); + + QCOMPARE(copy->waypoints(), waypoints); + QCOMPARE(copy->numberAlternativeRoutes(), 0); + QCOMPARE(copy->routeOptimization(), QGeoRouteRequest::FastestRoute); + QCOMPARE(copy->segmentDetail(), QGeoRouteRequest::BasicSegmentData); + QCOMPARE(copy->travelModes(), QGeoRouteRequest::CarTravel); + QCOMPARE(copy->maneuverDetail(), QGeoRouteRequest::BasicManeuvers); + + delete qgeocoord1; + delete qgeocoord2; + delete qgeocoord3; + delete copy; +} + +void tst_QGeoRouteRequest::constructor_orig_dest() +{ + QGeoCoordinate *qgeocoord1 = new QGeoCoordinate(43.5435, 76.342); + QGeoCoordinate *qgeocoord2 = new QGeoCoordinate(-43.5435, 176.342); + + QGeoRouteRequest *copy = new QGeoRouteRequest(*qgeocoord1, *qgeocoord2); + + QList waypoints; + waypoints.append(*qgeocoord1); + waypoints.append(*qgeocoord2); + + QCOMPARE(copy->waypoints(), waypoints); + QCOMPARE(copy->numberAlternativeRoutes(), 0); + QCOMPARE(copy->routeOptimization(), QGeoRouteRequest::FastestRoute); + QCOMPARE(copy->segmentDetail(), QGeoRouteRequest::BasicSegmentData); + QCOMPARE(copy->travelModes(), QGeoRouteRequest::CarTravel); + QCOMPARE(copy->maneuverDetail(), QGeoRouteRequest::BasicManeuvers); + + delete qgeocoord1; + delete qgeocoord2; + delete copy; +} + +void tst_QGeoRouteRequest::copy_constructor() +{ + QGeoRouteRequest *qgeorouterequestcopy = new QGeoRouteRequest(*qgeorouterequest); + QCOMPARE(*qgeorouterequest, *qgeorouterequestcopy); + delete qgeorouterequestcopy; +} + +void tst_QGeoRouteRequest::destructor() +{ + QGeoRouteRequest *qgeorouterequestcopy; + + qgeorouterequestcopy = new QGeoRouteRequest(); + delete qgeorouterequestcopy; + + qgeorouterequestcopy = new QGeoRouteRequest(*qgeorouterequest); + delete qgeorouterequestcopy; +} + +void tst_QGeoRouteRequest::excludeAreas() +{ + qgeocoordinate->setLatitude(13.3851); + qgeocoordinate->setLongitude(52.5312); + + QGeoCoordinate *qgeocoordinatecopy = new QGeoCoordinate(34.324 , -110.32); + + QGeoRectangle *qgeoboundingboxcopy = new QGeoRectangle(*qgeocoordinate, 0.4, 0.4); + QGeoRectangle *qgeoboundingboxcopy2 = new QGeoRectangle(*qgeocoordinatecopy, 1.2, 0.9); + QList areas; + areas.append(*qgeoboundingboxcopy); + areas.append(*qgeoboundingboxcopy2); + + qgeorouterequest->setExcludeAreas(areas); + + QCOMPARE(qgeorouterequest->excludeAreas(), areas); + + delete qgeoboundingboxcopy; + delete qgeoboundingboxcopy2; + delete qgeocoordinatecopy; +} + +void tst_QGeoRouteRequest::numberAlternativeRoutes() +{ + qgeorouterequest->setNumberAlternativeRoutes(0); + QCOMPARE(qgeorouterequest->numberAlternativeRoutes(), 0); + + qgeorouterequest->setNumberAlternativeRoutes(24); + QCOMPARE(qgeorouterequest->numberAlternativeRoutes(), 24); + + qgeorouterequest->setNumberAlternativeRoutes(-12); + + QCOMPARE(qgeorouterequest->numberAlternativeRoutes(), 0); +} + +void tst_QGeoRouteRequest::routeOptimization() +{ + QFETCH(QGeoRouteRequest::RouteOptimization, optimization); + + QCOMPARE(qgeorouterequest->routeOptimization(),QGeoRouteRequest::FastestRoute); + + qgeorouterequest->setRouteOptimization(optimization); + QCOMPARE(qgeorouterequest->routeOptimization(), optimization); +} + +void tst_QGeoRouteRequest::routeOptimization_data() +{ + QTest::addColumn("optimization"); + + QTest::newRow("optimization1") << QGeoRouteRequest::FastestRoute; + QTest::newRow("optimization2") << QGeoRouteRequest::ShortestRoute; + QTest::newRow("optimization3") << QGeoRouteRequest::MostEconomicRoute; + QTest::newRow("optimization4") << QGeoRouteRequest::MostScenicRoute; + +} + +void tst_QGeoRouteRequest::segmentDetail() +{ + QFETCH(QGeoRouteRequest::SegmentDetail, detail); + + QCOMPARE(qgeorouterequest->segmentDetail(), QGeoRouteRequest::BasicSegmentData); + + qgeorouterequest->setSegmentDetail(detail); + QCOMPARE(qgeorouterequest->segmentDetail(), detail); +} + +void tst_QGeoRouteRequest::segmentDetail_data() +{ + QTest::addColumn("detail"); + + QTest::newRow("detail1") << QGeoRouteRequest::NoSegmentData; + QTest::newRow("detail2") << QGeoRouteRequest::BasicSegmentData; +} + +void tst_QGeoRouteRequest::travelModes() +{ + QFETCH(QGeoRouteRequest::TravelMode,mode); + + QCOMPARE(qgeorouterequest->travelModes(), QGeoRouteRequest::CarTravel); + + qgeorouterequest->setTravelModes(mode); + QCOMPARE(qgeorouterequest->travelModes(), mode); +} + +void tst_QGeoRouteRequest::travelModes_data() +{ + QTest::addColumn("mode"); + + QTest::newRow("mode1") << QGeoRouteRequest::CarTravel; + QTest::newRow("mode2") << QGeoRouteRequest::PedestrianTravel; + QTest::newRow("mode3") << QGeoRouteRequest::BicycleTravel; + QTest::newRow("mode4") << QGeoRouteRequest::PublicTransitTravel; + QTest::newRow("mode5") << QGeoRouteRequest::TruckTravel; +} + +void tst_QGeoRouteRequest::waypoints() +{ + QFETCH(QList, coordinates); + + QList waypoints; + + for (int i = 0; i < coordinates.size(); i += 2) { + waypoints.append(QGeoCoordinate(coordinates.at(i), coordinates.at(i+1))); + } + + qgeorouterequest->setWaypoints(waypoints); + + QList waypointsRetrieved = qgeorouterequest->waypoints(); + QCOMPARE(waypointsRetrieved, waypoints); + + for (int i=0; i < waypointsRetrieved.size(); i++) { + QCOMPARE(waypointsRetrieved.at(i), waypoints.at(i)); + } +} + +void tst_QGeoRouteRequest::waypoints_data() +{ + QTest::addColumn >("coordinates"); + + QList coordinates; + + coordinates << 0.0 << 0.0; + QTest::newRow("path1") << coordinates ; + + coordinates << -23.23 << 54.45345; + QTest::newRow("path2") << coordinates ; + + coordinates << -85.4324 << -121.343; + QTest::newRow("path3") << coordinates ; + + coordinates << 1.323 << 12.323; + QTest::newRow("path4") << coordinates ; + + coordinates << 1324.323 << 143242.323; + QTest::newRow("path5") << coordinates ; +} + +void tst_QGeoRouteRequest::maneuverDetail() +{ + QFETCH(QGeoRouteRequest::ManeuverDetail,maneuver); + + QCOMPARE(qgeorouterequest->maneuverDetail(), QGeoRouteRequest::BasicManeuvers); + + qgeorouterequest->setManeuverDetail(maneuver); + QCOMPARE(qgeorouterequest->maneuverDetail(), maneuver); +} + +void tst_QGeoRouteRequest::maneuverDetail_data() +{ + QTest::addColumn("maneuver"); + + QTest::newRow("maneuver1") << QGeoRouteRequest::NoManeuvers; + QTest::newRow("maneuver2") << QGeoRouteRequest::BasicManeuvers; + +} + +void tst_QGeoRouteRequest::featureWeight_data() +{ + QTest::addColumn("type"); + QTest::addColumn("checkTypes"); + QTest::addColumn("initialWeight"); + QTest::addColumn("newWeight"); + QTest::addColumn("expectWeight"); + + QTest::newRow("NoFeature") << QGeoRouteRequest::NoFeature << false + << QGeoRouteRequest::NeutralFeatureWeight + << QGeoRouteRequest::PreferFeatureWeight + << QGeoRouteRequest::NeutralFeatureWeight; + QTest::newRow("TollFeature") << QGeoRouteRequest::TollFeature << true + << QGeoRouteRequest::NeutralFeatureWeight + << QGeoRouteRequest::PreferFeatureWeight + << QGeoRouteRequest::PreferFeatureWeight; + QTest::newRow("HighwayFeature") << QGeoRouteRequest::HighwayFeature << true + << QGeoRouteRequest::NeutralFeatureWeight + << QGeoRouteRequest::RequireFeatureWeight + << QGeoRouteRequest::RequireFeatureWeight; +} + +void tst_QGeoRouteRequest::featureWeight() +{ + QFETCH(QGeoRouteRequest::FeatureType, type); + QFETCH(bool, checkTypes); + QFETCH(QGeoRouteRequest::FeatureWeight, initialWeight); + QFETCH(QGeoRouteRequest::FeatureWeight, newWeight); + QFETCH(QGeoRouteRequest::FeatureWeight, expectWeight); + + QCOMPARE(qgeorouterequest->featureWeight(type), initialWeight); + qgeorouterequest->setFeatureWeight(type, newWeight); + QCOMPARE(qgeorouterequest->featureWeight(type), expectWeight); + + if (checkTypes) + QVERIFY(qgeorouterequest->featureTypes().contains(type)); +} + + +QTEST_APPLESS_MAIN(tst_QGeoRouteRequest); diff --git a/tests/auto/qgeorouterequest/tst_qgeorouterequest.h b/tests/auto/qgeorouterequest/tst_qgeorouterequest.h new file mode 100644 index 0000000..736510f --- /dev/null +++ b/tests/auto/qgeorouterequest/tst_qgeorouterequest.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_QGEOROUTEREQUEST_H +#define TST_QGEOROUTEREQUEST_H + +#include +#include +#include + +#include +#include +#include + + +QT_USE_NAMESPACE + +class tst_QGeoRouteRequest : public QObject +{ + Q_OBJECT + +public: + tst_QGeoRouteRequest(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + //Start Unit Test for QGeoRouteRequest + void constructor_waypoints(); + void constructor_orig_dest(); + void copy_constructor(); + void destructor(); + void excludeAreas(); + void numberAlternativeRoutes(); + void routeOptimization(); + void routeOptimization_data(); + void segmentDetail(); + void segmentDetail_data(); + void travelModes(); + void travelModes_data(); + void waypoints(); + void waypoints_data(); + void maneuverDetail(); + void maneuverDetail_data(); + void featureWeight(); + void featureWeight_data(); + //End Unit Test for QGeoRouteRequest + +private: + QGeoCoordinate *qgeocoordinate; + QGeoRectangle *qgeoboundingbox; + QGeoRouteRequest *qgeorouterequest; + +}; + +Q_DECLARE_METATYPE( QList); +Q_DECLARE_METATYPE( QGeoRouteRequest::RouteOptimization ); +Q_DECLARE_METATYPE( QGeoRouteRequest::SegmentDetail ); +Q_DECLARE_METATYPE( QGeoRouteRequest::TravelMode ); +Q_DECLARE_METATYPE( QGeoRouteRequest::FeatureType); +Q_DECLARE_METATYPE( QGeoRouteRequest::FeatureWeight); +Q_DECLARE_METATYPE( QGeoRouteRequest::ManeuverDetail ); + +#endif // TST_QGEOROUTEREQUEST_H + diff --git a/tests/auto/qgeoroutesegment/qgeoroutesegment.pro b/tests/auto/qgeoroutesegment/qgeoroutesegment.pro new file mode 100644 index 0000000..413363f --- /dev/null +++ b/tests/auto/qgeoroutesegment/qgeoroutesegment.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeoroutesegment + +# Input +HEADERS += tst_qgeoroutesegment.h +SOURCES += tst_qgeoroutesegment.cpp + +QT += location testlib diff --git a/tests/auto/qgeoroutesegment/tst_qgeoroutesegment.cpp b/tests/auto/qgeoroutesegment/tst_qgeoroutesegment.cpp new file mode 100644 index 0000000..ebe2b56 --- /dev/null +++ b/tests/auto/qgeoroutesegment/tst_qgeoroutesegment.cpp @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tst_qgeoroutesegment.h" + +QT_USE_NAMESPACE + +tst_QGeoRouteSegment::tst_QGeoRouteSegment() +{ +} + +void tst_QGeoRouteSegment::initTestCase() +{ +} + +void tst_QGeoRouteSegment::cleanupTestCase() +{ +} + +void tst_QGeoRouteSegment::init() +{ + qgeocoordinate = new QGeoCoordinate(); + qgeoroutesegment = new QGeoRouteSegment(); + qgeomaneuver = new QGeoManeuver(); +} + +void tst_QGeoRouteSegment::cleanup() +{ + delete qgeocoordinate; + delete qgeoroutesegment; +} + +void tst_QGeoRouteSegment::constructor() +{ + QVERIFY(!qgeoroutesegment->isValid()); + QCOMPARE(qgeoroutesegment->distance(), qreal(0.0)); + QCOMPARE(qgeoroutesegment->maneuver(),*qgeomaneuver); + QCOMPARE(qgeoroutesegment->travelTime(),0); +} + +void tst_QGeoRouteSegment::copy_constructor() +{ + QGeoRouteSegment *qgeoroutesegmentcopy = new QGeoRouteSegment(*qgeoroutesegment); + + QCOMPARE(*qgeoroutesegment, *qgeoroutesegmentcopy); + + QVERIFY(!qgeoroutesegmentcopy->isValid()); + QCOMPARE(qgeoroutesegmentcopy->distance(), qreal(0.0)); + QCOMPARE(qgeoroutesegmentcopy->maneuver(), *qgeomaneuver); + QCOMPARE(qgeoroutesegmentcopy->travelTime(), 0); + + delete qgeoroutesegmentcopy; +} + +void tst_QGeoRouteSegment::destructor() +{ + QGeoRouteSegment *qgeoroutesegmentcopy; + + qgeoroutesegmentcopy = new QGeoRouteSegment(); + delete qgeoroutesegmentcopy; + + qgeoroutesegmentcopy = new QGeoRouteSegment(*qgeoroutesegment); + delete qgeoroutesegmentcopy; +} + + +void tst_QGeoRouteSegment::travelTime() +{ + QFETCH(int, traveltime); + + QGeoRouteSegment sgmt; + QVERIFY(!sgmt.isValid()); + + sgmt.setTravelTime(traveltime); + + QVERIFY(sgmt.isValid()); + QCOMPARE(sgmt.travelTime(), traveltime); +} + +void tst_QGeoRouteSegment::travelTime_data() +{ + QTest::addColumn("traveltime"); + + QTest::newRow("travel1") << 0; + QTest::newRow("travel2") << -50; + QTest::newRow("travel3") << 324556; +} + +void tst_QGeoRouteSegment::distance() +{ + QFETCH(qreal, distance); + + QGeoRouteSegment sgmt; + QVERIFY(!sgmt.isValid()); + + sgmt.setDistance(distance); + + QVERIFY(sgmt.isValid()); + QCOMPARE(sgmt.distance(), distance); +} + +void tst_QGeoRouteSegment::distance_data() +{ + QTest::addColumn("distance"); + + QTest::newRow("distance1") << qreal(0.0) ; + QTest::newRow("distance2") << qreal(-50.3435) ; + QTest::newRow("distance3") << qreal(324556.543534) ; +} + + +void tst_QGeoRouteSegment::path() +{ + QFETCH(QList, coordinates); + + QGeoRouteSegment sgmt; + QVERIFY(!sgmt.isValid()); + + QList path; + + for (int i = 0; i < coordinates.size(); i += 2) { + path.append(QGeoCoordinate(coordinates.at(i), coordinates.at(i+1))); + } + + sgmt.setPath(path); + QVERIFY(sgmt.isValid()); + + QList pathretrieved = sgmt.path(); + QCOMPARE(pathretrieved, path); + + for (int i = 0; i < pathretrieved.size(); i++) { + QCOMPARE(pathretrieved.at(i), path.at(i)); + } +} + +void tst_QGeoRouteSegment::path_data() +{ + QTest::addColumn >("coordinates"); + + QList coordinates; + + coordinates << 0.0 << 0.0; + QTest::newRow("path1") << coordinates; + + coordinates << -23.23 << 54.45345; + QTest::newRow("path2") << coordinates; + + coordinates << -85.4324 << -121.343; + QTest::newRow("path3") << coordinates; + + coordinates << 1.323 << 12.323; + QTest::newRow("path4") << coordinates; + + coordinates << 1324.323 << 143242.323; + QTest::newRow("path5") << coordinates; +} + +void tst_QGeoRouteSegment::nextroutesegment() +{ + QGeoRouteSegment sgmt; + QVERIFY(!sgmt.isValid()); + + QGeoRouteSegment *qgeoroutesegmentcopy = new QGeoRouteSegment(); + qgeoroutesegmentcopy->setDistance(45.34); + + sgmt.setNextRouteSegment(*qgeoroutesegmentcopy); + + QVERIFY(sgmt.isValid()); + QCOMPARE(sgmt.nextRouteSegment(), *qgeoroutesegmentcopy); + + QCOMPARE((sgmt.nextRouteSegment()).distance(), + qgeoroutesegmentcopy->distance()); + delete qgeoroutesegmentcopy; + +} + +void tst_QGeoRouteSegment::maneuver() +{ + QGeoRouteSegment sgmt; + QVERIFY(!sgmt.isValid()); + + sgmt.setManeuver(*qgeomaneuver); + QCOMPARE(sgmt.maneuver(), *qgeomaneuver); + QVERIFY(sgmt.isValid()); +} + +void tst_QGeoRouteSegment::operators() +{ + //Create a copy and see that they are the same + QGeoRouteSegment *qgeoroutesegmentcopy = new QGeoRouteSegment(*qgeoroutesegment); + QGeoRouteSegment *trueSgmtCopy = new QGeoRouteSegment(); + + QVERIFY(qgeoroutesegment->operator ==(*qgeoroutesegmentcopy)); + QVERIFY(!qgeoroutesegment->operator !=(*qgeoroutesegmentcopy)); + QVERIFY(!qgeoroutesegment->isValid()); + QVERIFY(!qgeoroutesegmentcopy->isValid()); + + //Same segment ? content is the same but pointer different + QVERIFY(qgeoroutesegment->operator ==(*trueSgmtCopy)); + QVERIFY(!qgeoroutesegment->operator !=(*trueSgmtCopy)); + QVERIFY(!trueSgmtCopy->isValid()); + + QFETCH(double, distance); + QFETCH(int, traveltime); + QFETCH(QList, coordinates); + + QList path; + + for (int i = 0; i < coordinates.size(); i += 2) { + path.append(QGeoCoordinate(coordinates.at(i), coordinates.at(i+1))); + } + + qgeoroutesegment->setDistance(distance); + qgeoroutesegment->setTravelTime(traveltime); + qgeoroutesegment->setPath(path); + + trueSgmtCopy->setDistance(distance); + trueSgmtCopy->setTravelTime(traveltime); + trueSgmtCopy->setPath(path); + + QCOMPARE(qgeoroutesegment->distance(), distance); + QCOMPARE(qgeoroutesegment->travelTime(), traveltime); + QCOMPARE(qgeoroutesegment->path(), path); + + QCOMPARE(qgeoroutesegmentcopy->distance(), distance); + QCOMPARE(qgeoroutesegmentcopy->travelTime(), traveltime); + QCOMPARE(qgeoroutesegmentcopy->path(), path); + + QCOMPARE(trueSgmtCopy->distance(), distance); + QCOMPARE(trueSgmtCopy->travelTime(), traveltime); + QCOMPARE(trueSgmtCopy->path(), path); + + //Same as based off copy constructor (d-pointer shared) + QVERIFY(qgeoroutesegment->operator==(*qgeoroutesegmentcopy)); + QVERIFY(!qgeoroutesegment->operator!=(*qgeoroutesegmentcopy)); + + //Same as based off same content although different d-pointer + QVERIFY(qgeoroutesegment->operator==(*trueSgmtCopy)); + QVERIFY(!qgeoroutesegment->operator!=(*trueSgmtCopy)); + + const int newTravelTime = 111; + trueSgmtCopy->setTravelTime(newTravelTime); + QCOMPARE(trueSgmtCopy->travelTime(), newTravelTime); + + //different d-pointer and different content + QVERIFY(!qgeoroutesegment->operator==(*trueSgmtCopy)); + QVERIFY(qgeoroutesegment->operator!=(*trueSgmtCopy)); + + //Assign one segment to the other and test that they are the same again + *qgeoroutesegmentcopy = qgeoroutesegmentcopy->operator =(*qgeoroutesegment); + QVERIFY(qgeoroutesegment->operator==(*qgeoroutesegmentcopy)); + QVERIFY(!qgeoroutesegment->operator!=(*qgeoroutesegmentcopy)); + + *qgeoroutesegmentcopy = qgeoroutesegmentcopy->operator =(*trueSgmtCopy); + QVERIFY(trueSgmtCopy->operator==(*qgeoroutesegmentcopy)); + QVERIFY(!trueSgmtCopy->operator!=(*qgeoroutesegmentcopy)); + + delete trueSgmtCopy; + delete qgeoroutesegmentcopy; +} + +void tst_QGeoRouteSegment::operators_data() +{ + QTest::addColumn("distance"); + QTest::addColumn("traveltime"); + QTest::addColumn >("coordinates"); + + QList coordinates; + + coordinates << 0.0 << 0.0 << 23.234 << -121.767 << 8.43534 << 32.789; + QTest::newRow("set1") << 143545.43 << 45665 << coordinates; + + coordinates << 42.343 << -38.657; + QTest::newRow("set2") << 745654.43 << 786585 << coordinates; +} + +QTEST_APPLESS_MAIN(tst_QGeoRouteSegment); diff --git a/tests/auto/qgeoroutesegment/tst_qgeoroutesegment.h b/tests/auto/qgeoroutesegment/tst_qgeoroutesegment.h new file mode 100644 index 0000000..628293d --- /dev/null +++ b/tests/auto/qgeoroutesegment/tst_qgeoroutesegment.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_QGEOROUTESEGMENT_H +#define TST_QGEOROUTESEGMENT_H + +#include +#include +#include + +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QGeoRouteSegment : public QObject +{ + Q_OBJECT + +public: + tst_QGeoRouteSegment(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + //Start unit test for QGeoRouteSegment + void constructor(); + void copy_constructor(); + void destructor(); + void travelTime(); + void travelTime_data(); + void distance(); + void distance_data(); + void path(); + void path_data(); + void maneuver(); + void nextroutesegment(); + void operators(); + void operators_data(); + //End Unit Test for QGeoRouteSegment + +private: + + QGeoCoordinate *qgeocoordinate; + QGeoRouteSegment *qgeoroutesegment; + QGeoManeuver *qgeomaneuver; + +}; + +Q_DECLARE_METATYPE( QList); + +#endif // TST_GEOROUTESEGMENT_H + diff --git a/tests/auto/qgeoroutexmlparser/fixtures.qrc b/tests/auto/qgeoroutexmlparser/fixtures.qrc new file mode 100644 index 0000000..8288ea0 --- /dev/null +++ b/tests/auto/qgeoroutexmlparser/fixtures.qrc @@ -0,0 +1,6 @@ + + + route1.xml + route2.xml + + diff --git a/tests/auto/qgeoroutexmlparser/qgeoroutexmlparser.pro b/tests/auto/qgeoroutexmlparser/qgeoroutexmlparser.pro new file mode 100644 index 0000000..f9da965 --- /dev/null +++ b/tests/auto/qgeoroutexmlparser/qgeoroutexmlparser.pro @@ -0,0 +1,13 @@ +CONFIG += testcase +TARGET = tst_qgeoroutexmlparser + +plugin.path = ../../../src/plugins/geoservices/nokia/ + +SOURCES += tst_qgeoroutexmlparser.cpp \ + $$plugin.path/qgeoroutexmlparser.cpp +HEADERS += $$plugin.path/qgeoroutexmlparser.h +INCLUDEPATH += $$plugin.path +RESOURCES += fixtures.qrc + +QT += location testlib + diff --git a/tests/auto/qgeoroutexmlparser/route1.xml b/tests/auto/qgeoroutexmlparser/route1.xml new file mode 100644 index 0000000..a66f4b4 --- /dev/null +++ b/tests/auto/qgeoroutexmlparser/route1.xml @@ -0,0 +1 @@ +2012-02-21T02:51:16.997+01:002012-02-21T02:50:00.025+010046415962012-02-21T02:50:02.008+0100143512012-02-21T02:50:02.008+010045802011Q2_DICIrouteserver,9.1-2011.12.20-hotfix6.2.11.151routing-route-service,6.2.11.2REMuJj4AAAAzMzMzM5M7wIlBYOXQImNAAAAAQEGTO8AAAACg0CJjQAAAAAAAAPB_AAAAAAAA8H9PWzP4XI_C9Sjc7QBv5_up8YKMAQEAAIAr3O0AAwAAgPKCjAEBAAAAAADA_wEAAAAAAMD_gTMrH2wAy2s1l8HnuszFwwBp-130852017-27.5752144153.0879669-27.575153.088stopOver+130731232-27.4650097153.0230255-27.465153.023stopOverfastestNowcarenabled-27.5752144,153.0879669 -27.5752602,153.0882263 -27.5753899,153.0891418 -27.5754299,153.089447 -27.5755901,153.0905914 -27.5756607,153.0911255 -27.5757198,153.091568 -27.5757504,153.0918121 -27.5757809,153.0920258 -27.5752907,153.0918121 -27.5749302,153.0916748 -27.5740795,153.0913239 -27.5734901,153.091095 -27.5731201,153.0909119 -27.5728893,153.0908051 -27.5723991,153.0906067 -27.5722103,153.0904999 -27.5719509,153.0903625 -27.5717106,153.0902557 -27.5714703,153.0901337 -27.5711498,153.0900116 -27.5710392,153.0898895 -27.5704498,153.0897064 -27.5699806,153.0894012 -27.5698109,153.0892334 -27.5696507,153.0890503 -27.5693398,153.0885468 -27.5685005,153.0870361 -27.5683002,153.0866852 -27.5681305,153.0865479 -27.5675297,153.0855865 -27.5662403,153.0835724 -27.5645599,153.0809937 -27.5645103,153.0809174 -27.5644493,153.0808258 -27.5643902,153.080719 -27.5632801,153.0790863 -27.5629597,153.0786438 -27.5626793,153.0782623 -27.5623398,153.0778046 -27.5618191,153.0771027 -27.5615292,153.0767517 -27.56147,153.0766754 -27.5613995,153.0765839 -27.5612907,153.0764771 -27.5612202,153.0764008 -27.5611191,153.0762939 -27.5605106,153.0756226 -27.5587692,153.0739288 -27.5560703,153.0716248 -27.5530605,153.0691223 -27.5518303,153.0680542 -27.5516701,153.0679169 -27.5500202,153.0665436 -27.5492992,153.0659485 -27.5465107,153.0635986 -27.5454407,153.0627136 -27.5446301,153.062027 -27.5444107,153.0618439 -27.5436707,153.0612946 -27.5428104,153.0607452 -27.5418606,153.0602264 -27.5410309,153.0598297 -27.5407505,153.0596924 -27.5401402,153.0594635 -27.5396996,153.0593262 -27.5380306,153.0588379 -27.5377598,153.0587616 -27.5372505,153.0586548 -27.5360794,153.0584106 -27.5345192,153.058075 -27.5335693,153.0578766 -27.5287495,153.0568542 -27.5279198,153.0566559 -27.5272694,153.0564575 -27.52668,153.0562439 -27.5265102,153.0561676 -27.5260201,153.055954 -27.5256195,153.0557709 -27.5253601,153.0556335 -27.5246906,153.0552826 -27.5240498,153.0548706 -27.5235901,153.0545044 -27.5233498,153.0543213 -27.5231991,153.0542145 -27.5209904,153.052475 -27.5201492,153.0517731 -27.5198994,153.0515747 -27.5198002,153.0514832 -27.5197201,153.0514374 -27.5196705,153.0513916 -27.5188999,153.0507507 -27.51824,153.0500336 -27.5175304,153.0490723 -27.5173092,153.0487061 -27.5169201,153.0479431 -27.5146999,153.0434113 -27.5142193,153.0424347 -27.5138206,153.0417633 -27.5135002,153.0413666 -27.5131302,153.0409546 -27.5129299,153.0407715 -27.5126896,153.0405731 -27.5122299,153.0402832 -27.5098591,153.0390167 -27.5095406,153.0388641 -27.5092793,153.0387726 -27.5090008,153.038681 -27.5088291,153.0386353 -27.5083199,153.0385437 -27.5077591,153.0385132 -27.5072899,153.0385437 -27.5068703,153.0386047 -27.5041294,153.0391083 -27.50317,153.0392914 -27.5017509,153.0395508 -27.5009995,153.0397034 -27.5007401,153.0397186 -27.5005703,153.0397339 -27.500351,153.0397339 -27.5001106,153.0397339 -27.4993591,153.0396729 -27.4989605,153.039566 -27.4988594,153.0395508 -27.49823,153.0393066 -27.4977093,153.0390167 -27.4974995,153.0388794 -27.4974003,153.0388031 -27.4970894,153.0386047 -27.4968204,153.0384064 -27.4967098,153.0383148 -27.4961395,153.0379028 -27.4959297,153.037735 -27.4958897,153.0377045 -27.4957695,153.0376129 -27.4948807,153.0368958 -27.4944096,153.0365143 -27.4940605,153.0361481 -27.4939404,153.0359955 -27.4934292,153.0354004 -27.4932709,153.0351868 -27.4930305,153.0348358 -27.4929104,153.0346222 -27.4927597,153.0343933 -27.4926701,153.0342712 -27.4923592,153.033844 -27.4920998,153.0335999 -27.4918404,153.0334167 -27.4914608,153.0332336 -27.4911499,153.0330963 -27.4907398,153.0330048 -27.4899998,153.0329742 -27.48983,153.0329437 -27.4895992,153.0329132 -27.48946,153.0328827 -27.4889603,153.0328369 -27.4886894,153.0327454 -27.4882698,153.032608 -27.4881802,153.0325928 -27.4880905,153.0325623 -27.4880009,153.032547 -27.4879494,153.0325165 -27.4859409,153.0318909 -27.4850197,153.031601 -27.4841309,153.031311 -27.4839497,153.03125 -27.4838505,153.0312195 -27.4837608,153.031189 -27.4836502,153.0311432 -27.4832993,153.0309906 -27.4827995,153.0307312 -27.4824696,153.0305176 -27.4823093,153.0303955 -27.4822197,153.0303345 -27.4821301,153.0302429 -27.4794807,153.0281525 -27.4792404,153.0279846 -27.4785404,153.0274506 -27.4784508,153.0273743 -27.4780502,153.0270691 -27.4775295,153.0266724 -27.4773006,153.026474 -27.4769592,153.0261993 -27.4766197,153.0259247 -27.4762897,153.0256653 -27.4761295,153.0255432 -27.4759293,153.0253906 -27.4754696,153.0250244 -27.4750309,153.0246735 -27.4747601,153.0244446 -27.4745407,153.0242767 -27.4742794,153.0240784 -27.4738808,153.0237732 -27.4737606,153.0236969 -27.4730091,153.0231171 -27.4729099,153.0230408 -27.4728107,153.0229645 -27.47258,153.0227966 -27.4724197,153.0226593 -27.4721909,153.0224609 -27.4719391,153.0222473 -27.4718609,153.022171 -27.4718399,153.0221558 -27.4718094,153.0221252 -27.4717293,153.0220337 -27.4714603,153.0217743 -27.4713306,153.021637 -27.47122,153.0215912 -27.4705105,153.0209045 -27.4703693,153.0207672 -27.4703102,153.0206757 -27.4700508,153.0203094 -27.4700203,153.0202637 -27.46982,153.0201416 -27.46978,153.0201263 -27.4697304,153.0201263 -27.4696808,153.0201263 -27.4696007,153.0201569 -27.4695091,153.0202332 -27.4692593,153.0205231 -27.4690895,153.0207825 -27.4690208,153.0209045 -27.46875,153.0212402 -27.4686909,153.0213165 -27.4685402,153.0215149 -27.4682808,153.0218353 -27.4678001,153.0224457 -27.46772,153.0225525 -27.4676704,153.0226288 -27.4676208,153.0226898 -27.4674492,153.0229187 -27.4673805,153.0230103 -27.46735,153.023056 -27.4669609,153.0235596 -27.4666996,153.0239258 -27.4664497,153.0242615 -27.4662895,153.0244598 -27.4661503,153.0246735 -27.4660702,153.024765 -27.4658794,153.0247192 -27.4658508,153.024704 -27.4657001,153.024704 -27.4656696,153.024704 -27.4654598,153.0247345 -27.4650002,153.0249329 -27.4648304,153.0250244 -27.4649105,153.024765 -27.4649601,153.0246735 -27.4653893,153.0240021 -27.4654293,153.0239258 -27.4655704,153.0236359 -27.46562,153.0235291 -27.4656105,153.0233459 -27.4655991,153.0231628 -27.4652405,153.0231628 -27.4652004,153.0231476 -27.4651394,153.0230408 -27.4650898,153.0230103 -27.4650307,153.0230103 -27.4640903,153.0233917-27.4640903153.0201263-27.5757809153.0920258-130852017-27.5752144153.0879669-27.575153.088stopOver+130731232-27.4650097153.0230255-27.465153.023stopOver15895.0812.2-27.5752144153.0879669Head toward Logan Rd (95) on Padstow Rd (56). Go for 0.3 miles.24.2403.0-130852017forward-27.5757809153.0920258Turn left onto Logan Rd (95). Go for 0.3 miles.54.8546.0+130851941left-27.5711498153.0900116Take exit #14/M3/City onto M3, Pacific Mtwy. Go for 8.5 miles.556.513578.0+778753942lightLeft-27.4713306153.021637Take exit #2/Turbot Street to the right onto Turbot St. Go for 0.5 miles.65.5840.0+778426174lightRight-27.4660702153.024765Turn left onto Edward St. Go for 450 feet.27.9141.0+840906308left-27.4648304153.0250244Turn sharp left onto Wickham Ter. Go for 0.1 miles.42.8206.0-842383988hardLeft-27.4655991153.0231628Turn right onto Bartley St. Go for 0.1 miles.40.6181.0+130731232right-27.4650097153.0230255Arrive at Bartley St. The trip takes 10 miles and 14 mins.0.00.0forward-130852017-27.5752144,153.0879669 -27.5752602,153.088226326.016.670.016.671.6
    AUQueenslandBrisbaneEight Mile PlainsPadstow Rd
    -848019289-27.5752602,153.0882263 -27.5753899,153.089141891.016.670.016.675.5
    AUQueenslandBrisbaneEight Mile PlainsPadstow Rd
    -848019288-27.5753899,153.0891418 -27.5754299,153.08944730.016.670.016.671.8
    AUQueenslandBrisbaneEight Mile PlainsPadstow Rd
    -848019287-27.5754299,153.089447 -27.5755901,153.0905914114.016.670.016.676.8
    AUQueenslandBrisbaneEight Mile PlainsPadstow Rd
    -848019299-27.5755901,153.0905914 -27.5756607,153.091125553.016.670.016.673.2
    AUQueenslandBrisbaneEight Mile PlainsPadstow Rd
    -848019298-27.5756607,153.0911255 -27.5757198,153.09156844.016.670.016.672.6
    AUQueenslandBrisbaneEight Mile PlainsPadstow Rd
    -736514351-27.5757198,153.091568 -27.5757504,153.091812124.016.670.016.671.4
    AUQueenslandBrisbaneEight Mile PlainsPadstow Rd
    -736514350-27.5757504,153.0918121 -27.5757809,153.092025821.016.670.016.671.3
    AUQueenslandBrisbaneEight Mile PlainsPadstow Rd
    +130851941-27.5757809,153.0920258 -27.5752907,153.0918121 -27.5749302,153.0916748100.019.440.018.065.5
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +130851942-27.5749302,153.0916748 -27.5740795,153.0913239100.019.440.018.065.5
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +130851862-27.5740795,153.0913239 -27.5734901,153.09109569.019.440.018.063.8
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +130851861-27.5734901,153.091095 -27.5731201,153.090911944.019.440.018.062.4
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918831-27.5731201,153.0909119 -27.5728893,153.090805127.019.440.018.061.5
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918832-27.5728893,153.0908051 -27.5723991,153.0906067 -27.5722103,153.090499981.019.440.018.064.5
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918827-27.5722103,153.0904999 -27.5719509,153.090362531.019.440.018.061.7
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918833-27.5719509,153.0903625 -27.5717106,153.090255728.019.440.018.061.6
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918834-27.5717106,153.0902557 -27.5714703,153.090133729.019.440.018.061.6
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +130757981-27.5714703,153.0901337 -27.5711498,153.090011637.019.440.018.062.0
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +778753942-27.5711498,153.0900116 -27.5710392,153.0898895 -27.5704498,153.0897064 -27.5699806,153.0894012 -27.5698109,153.0892334 -27.5696507,153.0890503 -27.5693398,153.0885468256.027.780.018.0614.2
    AUQueenslandBrisbaneEight Mile Plains
    +778753943-27.5693398,153.0885468 -27.5685005,153.0870361 -27.5683002,153.0866852 -27.5681305,153.0865479240.027.780.026.399.1
    AUQueenslandBrisbaneEight Mile Plains
    +842194179-27.5681305,153.0865479 -27.5675297,153.0855865115.027.780.026.394.4
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +842194180-27.5675297,153.0855865 -27.5662403,153.0835724 -27.5645599,153.0809937 -27.5645103,153.0809174569.027.780.026.3921.6
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180257-27.5645103,153.0809174 -27.5644493,153.0808258 -27.5643902,153.08071923.027.780.026.390.9
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy, Riverside Expy
    +778513858-27.5643902,153.080719 -27.5632801,153.0790863 -27.5629597,153.0786438 -27.5626793,153.0782623 -27.5623398,153.0778046366.027.780.026.3913.9
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +130848965-27.5623398,153.0778046 -27.5618191,153.077102790.027.780.026.393.4
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180252-27.5618191,153.0771027 -27.5615292,153.0767517 -27.56147,153.076675457.027.780.026.392.2
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180251-27.56147,153.0766754 -27.5613995,153.076583911.027.780.026.390.4
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180253-27.5613995,153.0765839 -27.5612907,153.0764771 -27.5612202,153.076400826.027.780.026.391.0
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180254-27.5612202,153.0764008 -27.5611191,153.0762939 -27.5605106,153.0756226 -27.5587692,153.0739288 -27.5560703,153.0716248 -27.5530605,153.06912231157.027.780.026.3943.8
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +782455208-27.5530605,153.0691223 -27.5518303,153.0680542172.027.780.026.396.5
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +782455209-27.5518303,153.0680542 -27.5516701,153.067916922.027.780.026.390.8
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +130752000-27.5516701,153.0679169 -27.5500202,153.0665436228.027.780.026.398.6
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +762719178-27.5500202,153.0665436 -27.5492992,153.065948599.027.780.026.393.8
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +762719179-27.5492992,153.0659485 -27.5465107,153.0635986387.027.780.026.3914.7
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +130852721-27.5465107,153.0635986 -27.5454407,153.0627136147.027.780.026.395.6
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +782455204-27.5454407,153.0627136 -27.5446301,153.062027112.027.780.026.394.2
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +851414677-27.5446301,153.062027 -27.5444107,153.0618439 -27.5436707,153.0612946 -27.5428104,153.0607452 -27.5418606,153.0602264 -27.5410309,153.0598297456.027.780.026.3917.3
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +851414678-27.5410309,153.0598297 -27.5407505,153.0596924 -27.5401402,153.0594635105.027.780.026.394.0
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +828675832-27.5401402,153.0594635 -27.5396996,153.0593262 -27.5380306,153.0588379242.027.780.026.399.2
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +828675833-27.5380306,153.0588379 -27.5377598,153.058761631.027.780.026.391.2
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +821207667-27.5377598,153.0587616 -27.5372505,153.0586548 -27.5360794,153.0584106190.027.780.026.397.2
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +821207668-27.5360794,153.0584106 -27.5345192,153.058075176.027.780.026.396.7
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +851414665-27.5345192,153.058075 -27.5335693,153.0578766107.027.780.026.394.1
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +851414666-27.5335693,153.0578766 -27.5287495,153.0568542 -27.5279198,153.0566559 -27.5272694,153.0564575 -27.52668,153.0562439 -27.5265102,153.0561676803.027.780.026.3930.4
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +782385629-27.5265102,153.0561676 -27.5260201,153.055954 -27.5256195,153.0557709106.027.780.026.394.0
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +130853483-27.5256195,153.0557709 -27.5253601,153.0556335 -27.5246906,153.0552826 -27.5240498,153.0548706 -27.5235901,153.0545044258.027.780.026.399.8
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +772093015-27.5235901,153.0545044 -27.5233498,153.054321332.027.780.026.391.2
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +772093016-27.5233498,153.0543213 -27.5231991,153.054214519.027.780.026.390.7
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +736390885-27.5231991,153.0542145 -27.5209904,153.052475299.027.780.026.3911.3
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +782385634-27.5209904,153.052475 -27.5201492,153.0517731116.027.780.026.394.4
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +782385635-27.5201492,153.0517731 -27.5198994,153.051574733.027.780.026.391.3
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +778520137-27.5198994,153.0515747 -27.5198002,153.051483214.027.780.026.390.5
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +791180780-27.5198002,153.0514832 -27.5197201,153.05143749.027.780.026.390.3
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +791180781-27.5197201,153.0514374 -27.5196705,153.0513916 -27.5188999,153.0507507 -27.51824,153.0500336 -27.5175304,153.0490723 -27.5173092,153.0487061 -27.5169201,153.0479431 -27.5146999,153.0434113979.027.780.026.3937.1
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +130853660-27.5146999,153.0434113 -27.5142193,153.0424347 -27.5138206,153.0417633 -27.5135002,153.0413666 -27.5131302,153.0409546 -27.5129299,153.0407715329.027.780.026.3912.5
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +833000842-27.5129299,153.0407715 -27.5126896,153.0405731 -27.5122299,153.0402832 -27.5098591,153.0390167 -27.5095406,153.0388641 -27.5092793,153.0387726452.027.780.026.3917.1
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +833000843-27.5092793,153.0387726 -27.5090008,153.038681 -27.5088291,153.0386353 -27.5083199,153.0385437109.027.780.026.394.1
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +130853471-27.5083199,153.0385437 -27.5077591,153.0385132 -27.5072899,153.0385437114.027.780.026.394.3
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +130853513-27.5072899,153.0385437 -27.5068703,153.0386047 -27.5041294,153.0391083355.027.780.026.3913.5
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +782459444-27.5041294,153.0391083 -27.50317,153.0392914108.027.780.026.394.1
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +782459445-27.50317,153.0392914 -27.5017509,153.0395508159.027.780.026.396.0
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +779045467-27.5017509,153.0395508 -27.5009995,153.0397034 -27.5007401,153.0397186113.027.780.026.394.3
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +779045468-27.5007401,153.0397186 -27.5005703,153.0397339 -27.500351,153.039733943.022.220.022.221.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +821196745-27.500351,153.0397339 -27.5001106,153.0397339 -27.4993591,153.0396729 -27.4989605,153.039566156.022.220.022.227.0
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +821196746-27.4989605,153.039566 -27.4988594,153.0395508 -27.49823,153.039306685.022.220.022.223.8
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +812250074-27.49823,153.0393066 -27.4977093,153.0390167 -27.4974995,153.038879491.022.220.022.224.1
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +826731481-27.4974995,153.0388794 -27.4974003,153.038803113.022.220.022.220.6
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +826731482-27.4974003,153.0388031 -27.4970894,153.038604739.022.220.022.221.8
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +130846576-27.4970894,153.0386047 -27.4968204,153.038406435.022.220.022.221.6
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +130738021-27.4968204,153.0384064 -27.4967098,153.038314815.022.220.022.220.7
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +778479274-27.4967098,153.0383148 -27.4961395,153.037902875.022.220.022.223.4
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +778479275-27.4961395,153.0379028 -27.4959297,153.03773528.022.220.022.221.3
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851379612-27.4959297,153.037735 -27.4958897,153.0377045 -27.4957695,153.037612921.022.220.022.220.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851460191-27.4957695,153.0376129 -27.4948807,153.0368958121.022.220.022.225.4
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851460192-27.4948807,153.0368958 -27.4944096,153.0365143 -27.4940605,153.0361481 -27.4939404,153.0359955137.022.220.022.226.2
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851414374-27.4939404,153.0359955 -27.4934292,153.0354004 -27.4932709,153.0351868109.022.220.022.224.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +812250623-27.4932709,153.0351868 -27.4930305,153.034835843.022.220.022.221.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +802502378-27.4930305,153.0348358 -27.4929104,153.034622224.022.220.022.221.1
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +811756237-27.4929104,153.0346222 -27.4927597,153.034393328.022.220.022.221.3
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +811756238-27.4927597,153.0343933 -27.4926701,153.034271215.022.220.022.220.7
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +840727547-27.4926701,153.0342712 -27.4923592,153.033844 -27.4920998,153.0335999 -27.4918404,153.0334167 -27.4914608,153.0332336 -27.4911499,153.0330963 -27.4907398,153.0330048255.022.220.022.2211.5
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +840727548-27.4907398,153.0330048 -27.4899998,153.032974282.022.220.022.223.7
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851414359-27.4899998,153.0329742 -27.48983,153.0329437 -27.4895992,153.0329132 -27.48946,153.032882760.022.220.022.222.7
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851414360-27.48946,153.0328827 -27.4889603,153.032836955.022.220.022.222.5
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +130846600-27.4889603,153.0328369 -27.4886894,153.032745431.022.220.022.221.4
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +791180777-27.4886894,153.0327454 -27.4882698,153.032608 -27.4881802,153.032592858.022.220.022.222.6
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +792771312-27.4881802,153.0325928 -27.4880905,153.0325623 -27.4880009,153.03254720.022.220.022.220.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +791180779-27.4880009,153.032547 -27.4879494,153.0325165 -27.4859409,153.0318909 -27.4850197,153.031601344.022.220.022.2215.5
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +778398386-27.4850197,153.031601 -27.4841309,153.031311102.022.220.022.224.6
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +792296089-27.4841309,153.031311 -27.4839497,153.0312521.022.220.022.220.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +792296090-27.4839497,153.03125 -27.4838505,153.031219511.022.220.022.220.5
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +792296087-27.4838505,153.0312195 -27.4837608,153.03118910.022.220.022.220.5
    AUQueenslandBrisbaneKangaroo PointM3, Pacific Mtwy
    +792296088-27.4837608,153.031189 -27.4836502,153.031143213.022.220.022.220.6
    AUQueenslandBrisbaneKangaroo PointM3, Pacific Mtwy
    +130846658-27.4836502,153.0311432 -27.4832993,153.030990641.022.220.022.221.8
    AUQueenslandBrisbaneKangaroo PointM3, Pacific Mtwy
    +130846657-27.4832993,153.0309906 -27.4827995,153.0307312 -27.4824696,153.0305176103.022.220.022.224.6
    AUQueenslandBrisbaneKangaroo PointM3, Pacific Mtwy, Riverside Expy
    +831959706-27.4824696,153.0305176 -27.4823093,153.0303955 -27.4822197,153.030334533.022.220.022.221.5
    AUQueenslandBrisbaneKangaroo PointCaptain Cook Brg, M3, Pacific Mtwy, Riverside Expy
    +831959707-27.4822197,153.0303345 -27.4821301,153.0302429 -27.4794807,153.0281525373.022.220.022.2216.8
    AUQueenslandBrisbaneKangaroo PointCaptain Cook Brg, M3, Pacific Mtwy, Riverside Expy
    +132965050-27.4794807,153.0281525 -27.4792404,153.027984631.022.220.022.221.4
    AUQueenslandBrisbaneKangaroo PointCaptain Cook Brg, M3, Pacific Mtwy, Riverside Expy
    +779153921-27.4792404,153.0279846 -27.4785404,153.0274506 -27.4784508,153.0273743106.022.220.022.224.8
    AUQueenslandBrisbaneM3, Pacific Mtwy, Captain Cook Brg, Riverside Expy
    +779153922-27.4784508,153.0273743 -27.4780502,153.027069153.022.220.022.222.4
    AUQueenslandBrisbaneM3, Pacific Mtwy, Captain Cook Brg, Riverside Expy
    +778599510-27.4780502,153.0270691 -27.4775295,153.026672469.022.220.022.223.1
    AUQueenslandBrisbaneM3, Pacific Mtwy, Captain Cook Brg, Riverside Expy
    +792648393-27.4775295,153.0266724 -27.4773006,153.02647432.019.440.018.061.8
    AUQueenslandBrisbaneM3, Pacific Mtwy, Captain Cook Brg, Riverside Expy
    +792648394-27.4773006,153.026474 -27.4769592,153.026199346.019.440.018.062.5
    AUQueenslandBrisbaneM3, Pacific Mtwy, Captain Cook Brg, Riverside Expy
    +792648392-27.4769592,153.0261993 -27.4766197,153.025924746.019.440.018.062.5
    AUQueenslandBrisbaneM3, Pacific Mtwy, Captain Cook Brg, Riverside Expy
    +130851656-27.4766197,153.0259247 -27.4762897,153.025665344.019.440.018.062.4
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +778829848-27.4762897,153.0256653 -27.4761295,153.025543221.019.440.018.061.2
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +779144197-27.4761295,153.0255432 -27.4759293,153.025390626.019.440.018.061.4
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +779144198-27.4759293,153.0253906 -27.4754696,153.025024462.019.440.018.063.4
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +821196747-27.4754696,153.0250244 -27.4750309,153.024673559.019.440.018.063.3
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +821196748-27.4750309,153.0246735 -27.4747601,153.024444637.019.440.018.062.0
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +779144211-27.4747601,153.0244446 -27.4745407,153.024276729.019.440.018.061.6
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +779144212-27.4745407,153.0242767 -27.4742794,153.024078435.019.440.018.061.9
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +130851752-27.4742794,153.0240784 -27.4738808,153.023773253.019.440.018.062.9
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +132965668-27.4738808,153.0237732 -27.4737606,153.023696915.019.440.018.060.8
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +778426177-27.4737606,153.0236969 -27.4730091,153.0231171101.019.440.018.065.6
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +778426178-27.4730091,153.0231171 -27.4729099,153.023040813.019.440.018.060.7
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +778426179-27.4729099,153.0230408 -27.4728107,153.022964513.019.440.018.060.7
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +841489920-27.4728107,153.0229645 -27.47258,153.0227966 -27.4724197,153.022659352.019.440.018.062.9
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +841489921-27.4724197,153.0226593 -27.4721909,153.022460932.019.440.018.061.8
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +841489915-27.4721909,153.0224609 -27.4719391,153.022247335.019.440.018.061.9
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +778398393-27.4719391,153.0222473 -27.4718609,153.02217111.019.440.018.060.6
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +782442370-27.4718609,153.022171 -27.4718399,153.02215582.019.440.018.060.1
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +782442371-27.4718399,153.0221558 -27.4718094,153.02212524.019.440.018.060.2
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +778398394-27.4718094,153.0221252 -27.4717293,153.022033712.019.440.018.060.7
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +841489916-27.4717293,153.0220337 -27.4714603,153.021774339.019.440.018.062.2
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +841489917-27.4714603,153.0217743 -27.4713306,153.02163719.019.440.018.061.1
    AUQueenslandBrisbaneM3, Pacific Mtwy, Riverside Expy
    +778426174-27.4713306,153.021637 -27.47122,153.0215912 -27.4705105,153.0209045117.016.670.016.677.0
    AUQueenslandBrisbane
    +778426175-27.4705105,153.0209045 -27.4703693,153.0207672 -27.4703102,153.020675731.016.670.016.671.9
    AUQueenslandBrisbane
    +840910687-27.4703102,153.0206757 -27.4700508,153.020309446.016.670.016.672.8
    AUQueenslandBrisbane
    +840910688-27.4700508,153.0203094 -27.4700203,153.0202637 -27.46982,153.0201416 -27.46978,153.0201263 -27.4697304,153.0201263 -27.4696808,153.020126346.016.670.016.672.8
    AUQueenslandBrisbane
    +779153927-27.4696808,153.0201263 -27.4696007,153.02015699.016.670.016.670.5
    AUQueenslandBrisbane
    +778594823-27.4696007,153.0201569 -27.4695091,153.0202332 -27.4692593,153.0205231 -27.4690895,153.0207825 -27.4690208,153.020904598.016.670.016.675.9
    AUQueenslandBrisbane
    +840910525-27.4690208,153.0209045 -27.46875,153.021240244.016.670.016.672.6
    AUQueenslandBrisbaneTurbot St
    +840910526-27.46875,153.0212402 -27.4686909,153.02131659.016.670.016.670.5
    AUQueenslandBrisbaneTurbot St
    +130731944-27.4686909,153.0213165 -27.4685402,153.021514925.016.670.016.671.5
    AUQueenslandBrisbaneTurbot St
    +840909575-27.4685402,153.0215149 -27.4682808,153.021835342.016.670.016.672.5
    AUQueenslandBrisbaneTurbot St
    +840909576-27.4682808,153.0218353 -27.4678001,153.022445780.016.670.016.674.8
    AUQueenslandBrisbaneTurbot St
    +130851044-27.4678001,153.0224457 -27.46772,153.022552513.016.670.016.670.8
    AUQueenslandBrisbaneTurbot St
    +840909577-27.46772,153.0225525 -27.4676704,153.02262889.016.670.016.670.5
    AUQueenslandBrisbaneTurbot St
    +840910689-27.4676704,153.0226288 -27.4676208,153.02268988.016.670.016.670.5
    AUQueenslandBrisbaneTurbot St
    +840910690-27.4676208,153.0226898 -27.4674492,153.022918729.016.670.016.671.7
    AUQueenslandBrisbaneTurbot St
    +744461076-27.4674492,153.0229187 -27.4673805,153.023010311.016.670.016.670.7
    AUQueenslandBrisbaneTurbot St
    +779115654-27.4673805,153.0230103 -27.46735,153.0230565.016.670.016.670.3
    AUQueenslandBrisbaneTurbot St
    +840906306-27.46735,153.023056 -27.4669609,153.023559665.016.670.016.673.9
    AUQueenslandBrisbaneTurbot St
    +840906305-27.4669609,153.0235596 -27.4666996,153.023925846.016.670.016.672.8
    AUQueenslandBrisbaneTurbot St
    +779143470-27.4666996,153.0239258 -27.4664497,153.024261543.016.670.016.672.6
    AUQueenslandBrisbaneTurbot St
    +779143471-27.4664497,153.0242615 -27.4662895,153.024459826.016.670.016.671.6
    AUQueenslandBrisbaneTurbot St
    +779114892-27.4662895,153.0244598 -27.4661503,153.024673526.016.670.016.671.6
    AUQueenslandBrisbaneTurbot St
    +779114893-27.4661503,153.0246735 -27.4660702,153.02476512.016.670.016.670.7
    AUQueenslandBrisbaneTurbot St
    +840906308-27.4660702,153.024765 -27.4658794,153.024719221.013.890.09.722.2
    AUQueenslandBrisbaneEdward St
    +840906309-27.4658794,153.0247192 -27.4658508,153.024704 -27.4657001,153.024704 -27.4656696,153.02470423.013.890.09.722.4
    AUQueenslandBrisbaneEdward St
    +840906307-27.4656696,153.024704 -27.4654598,153.024734523.013.890.09.722.4
    AUQueenslandBrisbaneEdward St
    +840906312-27.4654598,153.0247345 -27.4650002,153.024932954.013.890.09.725.6
    AUQueenslandBrisbaneSpring HillEdward St
    +840906313-27.4650002,153.0249329 -27.4648304,153.025024420.013.890.09.722.1
    AUQueenslandBrisbaneSpring HillEdward St
    -842383988-27.4648304,153.0250244 -27.4649105,153.024765 -27.4649601,153.024673537.013.890.09.723.8
    AUQueenslandBrisbaneSpring HillWickham Ter
    -842383987-27.4649601,153.0246735 -27.4653893,153.024002181.013.890.09.728.3
    AUQueenslandBrisbaneSpring HillWickham Ter
    -779110719-27.4653893,153.0240021 -27.4654293,153.02392588.013.890.09.720.8
    AUQueenslandBrisbaneSpring HillWickham Ter
    -779110718-27.4654293,153.0239258 -27.4655704,153.0236359 -27.46562,153.023529144.013.890.09.724.5
    AUQueenslandBrisbaneSpring HillWickham Ter
    +779110714-27.46562,153.0235291 -27.4656105,153.0233459 -27.4655991,153.023162836.013.890.09.723.7
    AUQueenslandBrisbaneSpring HillWickham Ter
    +130731232-27.4655991,153.0231628 -27.4652405,153.0231628 -27.4652004,153.0231476 -27.4651394,153.0230408 -27.4650898,153.0230103 -27.4650307,153.0230103 -27.4640903,153.0233917181.013.890.09.7218.6
    AUQueenslandBrisbaneSpring HillBartley St
    15895.0816.0812.0motorway
    \ No newline at end of file diff --git a/tests/auto/qgeoroutexmlparser/route2.xml b/tests/auto/qgeoroutexmlparser/route2.xml new file mode 100644 index 0000000..60cd1de --- /dev/null +++ b/tests/auto/qgeoroutexmlparser/route2.xml @@ -0,0 +1 @@ +2012-02-21T04:31:31.963+01:002012-02-21T04:29:00.020+010046413282012-02-21T04:29:02.066+0100146312012-02-21T04:29:02.066+010049962011Q2_DICIrouteserver,9.1-2011.12.20-hotfix6.2.11.148routing-route-service,6.2.11.2REMuh0MAAAAVHcnlP5Q7wEJg5dAiI2NAAAAA4ECUO8AAAADAIiNjQAAAAAAAAPB_AAAAAAAA8H8lPMsHR9jw9ErZ7QDDMJkqGIWMAQEAAABT2e0AAwAAABiFjAEBAAAAAADA_wEAAAAAAMD_4djU4GNxAstrNZeB_hDlxQbgt2cxF1g+130759717-27.5791149153.0979919-27.5791153.098stopOver-130730440-27.4622307153.0397949-27.4622153.0398stopOverfastestNowcarenabled-27.5791149,153.0979919 -27.5790405,153.0978851 -27.5786304,153.0973816 -27.57798,153.0966034 -27.5778008,153.0964661 -27.5776005,153.0963745 -27.5773792,153.096344 -27.5764599,153.0965271 -27.5764999,153.0963898 -27.5763397,153.0951233 -27.5762405,153.0945129 -27.5761204,153.0936279 -27.5759907,153.0927582 -27.5759201,153.0922546 -27.5758991,153.0921021 -27.5757809,153.0920258 -27.5752907,153.0918121 -27.5749302,153.0916748 -27.5740795,153.0913239 -27.5734901,153.091095 -27.5731201,153.0909119 -27.5728893,153.0908051 -27.5723991,153.0906067 -27.5722103,153.0904999 -27.5719509,153.0903625 -27.5717106,153.0902557 -27.5714703,153.0901337 -27.5711498,153.0900116 -27.5710392,153.0898895 -27.5704498,153.0897064 -27.5699806,153.0894012 -27.5698109,153.0892334 -27.5696507,153.0890503 -27.5693398,153.0885468 -27.5685005,153.0870361 -27.5683002,153.0866852 -27.5681305,153.0865479 -27.5675297,153.0855865 -27.5662403,153.0835724 -27.5645599,153.0809937 -27.5645103,153.0809174 -27.5644493,153.0808258 -27.5643902,153.080719 -27.5632801,153.0790863 -27.5629597,153.0786438 -27.5626793,153.0782623 -27.5623398,153.0778046 -27.5618191,153.0771027 -27.5615292,153.0767517 -27.56147,153.0766754 -27.5613995,153.0765839 -27.5612907,153.0764771 -27.5612202,153.0764008 -27.5611191,153.0762939 -27.5605106,153.0756226 -27.5587692,153.0739288 -27.5560703,153.0716248 -27.5530605,153.0691223 -27.5518303,153.0680542 -27.5516701,153.0679169 -27.5500202,153.0665436 -27.5492992,153.0659485 -27.5465107,153.0635986 -27.5454407,153.0627136 -27.5446301,153.062027 -27.5444107,153.0618439 -27.5436707,153.0612946 -27.5428104,153.0607452 -27.5418606,153.0602264 -27.5410309,153.0598297 -27.5407505,153.0596924 -27.5401402,153.0594635 -27.5396996,153.0593262 -27.5380306,153.0588379 -27.5377598,153.0587616 -27.5372505,153.0586548 -27.5360794,153.0584106 -27.5345192,153.058075 -27.5335693,153.0578766 -27.5287495,153.0568542 -27.5279198,153.0566559 -27.5272694,153.0564575 -27.52668,153.0562439 -27.5265102,153.0561676 -27.5260201,153.055954 -27.5256195,153.0557709 -27.5253601,153.0556335 -27.5246906,153.0552826 -27.5240498,153.0548706 -27.5235901,153.0545044 -27.5233498,153.0543213 -27.5231991,153.0542145 -27.5209904,153.052475 -27.5201492,153.0517731 -27.5198994,153.0515747 -27.5198002,153.0514832 -27.5197201,153.0514374 -27.5196705,153.0513916 -27.5188999,153.0507507 -27.51824,153.0500336 -27.5175304,153.0490723 -27.5173092,153.0487061 -27.5169201,153.0479431 -27.5146999,153.0434113 -27.5142193,153.0424347 -27.5138206,153.0417633 -27.5135002,153.0413666 -27.5131302,153.0409546 -27.5129299,153.0407715 -27.5126896,153.0405731 -27.5122299,153.0402832 -27.5098591,153.0390167 -27.5095406,153.0388641 -27.5092793,153.0387726 -27.5090008,153.038681 -27.5088291,153.0386353 -27.5083199,153.0385437 -27.5077591,153.0385132 -27.5072899,153.0385437 -27.5068703,153.0386047 -27.5041294,153.0391083 -27.50317,153.0392914 -27.5017509,153.0395508 -27.5009995,153.0397034 -27.5007401,153.0397186 -27.5005703,153.0397339 -27.500351,153.0397339 -27.5001106,153.0397339 -27.4993591,153.0396729 -27.4989605,153.039566 -27.4988594,153.0395508 -27.49823,153.0393066 -27.4977093,153.0390167 -27.4974995,153.0388794 -27.4974003,153.0388031 -27.4970894,153.0386047 -27.4968204,153.0384064 -27.4967098,153.0383148 -27.4961395,153.0379028 -27.4959297,153.037735 -27.4958897,153.0377045 -27.4957695,153.0376129 -27.4948807,153.0368958 -27.4944096,153.0365143 -27.4940605,153.0361481 -27.4939404,153.0359955 -27.4934292,153.0354004 -27.4932709,153.0351868 -27.4930305,153.0348358 -27.4929104,153.0346222 -27.4927597,153.0343933 -27.4926701,153.0342712 -27.4923592,153.033844 -27.4920998,153.0335999 -27.4918404,153.0334167 -27.4914608,153.0332336 -27.4911499,153.0330963 -27.4907398,153.0330048 -27.4899998,153.0329742 -27.48983,153.0329437 -27.4895992,153.0329132 -27.48946,153.0328827 -27.4889603,153.0328369 -27.48876,153.0327148 -27.4883404,153.0325317 -27.4881706,153.0324554 -27.4880695,153.0324249 -27.4879494,153.0323639 -27.4862995,153.0316925 -27.4859505,153.0314331 -27.4858799,153.0313721 -27.4858608,153.0312653 -27.4857903,153.0307465 -27.4857407,153.0303955 -27.4856205,153.0303345 -27.4855099,153.030304 -27.4854107,153.0303192 -27.4853191,153.0303192 -27.4852009,153.0303345 -27.4847908,153.0304108 -27.4846802,153.030426 -27.4845104,153.0305481 -27.4840393,153.0306396 -27.4839497,153.0306854 -27.48386,153.030777 -27.4838104,153.0309143 -27.4838409,153.0311584 -27.4838505,153.0312195 -27.4838696,153.0313873 -27.4839096,153.0316467 -27.4839706,153.032074 -27.4839993,153.0322571 -27.4840908,153.0328674 -27.4842205,153.033844 -27.4843693,153.0347748 -27.4844799,153.0354462 -27.4845104,153.0355835 -27.4845505,153.0358429 -27.4842796,153.0358429 -27.4836693,153.0358734 -27.4832802,153.0358429 -27.4829197,153.0358429 -27.48283,153.0358429 -27.4825592,153.0358429 -27.4824104,153.0358429 -27.4821491,153.0358276 -27.4819107,153.0358124 -27.4816303,153.0358124 -27.4814301,153.0358124 -27.4812508,153.0358124 -27.4805794,153.0357819 -27.4801197,153.0357666 -27.4793091,153.0357513 -27.4792404,153.0357513 -27.47855,153.0357361 -27.4782295,153.0357361 -27.4780293,153.0357513 -27.4774399,153.0357208 -27.4769306,153.0357056 -27.4765701,153.0357056 -27.4764309,153.0357056 -27.4762497,153.0356903 -27.4758797,153.0356445 -27.4758091,153.0356445 -27.4757595,153.0356445 -27.4756107,153.0355835 -27.4752903,153.0355835 -27.4749699,153.035553 -27.4743099,153.035553 -27.4732399,153.0355377 -27.4727306,153.0355225 -27.4721909,153.0355225 -27.4716606,153.0355072 -27.4712791,153.0355072 -27.4712505,153.0355072 -27.4712105,153.0355225 -27.4710503,153.0355835 -27.4709301,153.0356293 -27.4706802,153.035675 -27.4706097,153.035675 -27.4704609,153.0357056 -27.4702797,153.0357971 -27.4698505,153.0358429 -27.4694595,153.0358734 -27.4692192,153.0358734 -27.4685898,153.0358887 -27.4680004,153.0359039 -27.4671707,153.0358582 -27.4659309,153.0358124 -27.4658508,153.0358124 -27.4657001,153.0358124 -27.4648304,153.0357819 -27.4647808,153.0357819 -27.4627304,153.0357056 -27.4624996,153.0357056 -27.4624195,153.0357056 -27.4622898,153.0357056 -27.4619904,153.0357056 -27.4617596,153.0356598 -27.4615402,153.0356445 -27.4613094,153.035614 -27.4611397,153.035553 -27.4609203,153.0354004 -27.4607296,153.0351868 -27.4606895,153.0351563 -27.4605103,153.0349731 -27.4603901,153.0348511 -27.4602909,153.0347595 -27.4601593,153.034729 -27.4600105,153.0347443 -27.4598408,153.0348969 -27.4597702,153.0349731 -27.4596596,153.03508 -27.4593391,153.0354309 -27.4590492,153.0357361 -27.4593506,153.0361176 -27.4595604,153.036377 -27.4598408,153.0367432 -27.45998,153.0369263 -27.4601803,153.0371857 -27.4603996,153.0374603 -27.4608307,153.0379944 -27.4615307,153.0388947 -27.4622307,153.0397949-27.4590492153.030304-27.5791149153.0979919+130759717-27.5791149153.0979919-27.5791153.098stopOver-130730440-27.4622307153.0397949-27.4622153.0398stopOver17283.0998.2-27.5791149153.0979919Head toward Electronics St on McKechnie Dr. Go for 0.2 miles.47.3361.0+130759717forward-27.5764599153.0965271Turn left onto Miles Platting Rd (56). Go for 0.2 miles.34.2375.0+767490982left-27.5759907153.0927582Turn right onto Logan Rd (95). Go for 0.4 miles.54.6626.0+130759055right-27.5711498153.0900116Take exit #14/M3/City onto M3, Pacific Mtwy. Go for 7 miles.446.011321.0+778753942lightLeft-27.4889603153.0328369Take exit #2/41/Stanley Street/South Bank onto Stanley St (41). Go for 0.3 miles.30.6468.0+791185396lightLeft-27.4857407153.0303955Turn right onto Allen St toward Vulture Street. Go for 400 feet.22.5118.0+130735264right-27.4846802153.030426Bear right to stay on Allen St. Go for 350 feet.11.5114.0+130735018lightRight-27.4838104153.0309143Bear right onto Vulture St (41). Go for 0.3 miles.38.7489.0-130856418lightRight-27.4845505153.0358429Turn left onto Main St (15). Go for 0.6 miles.99.0966.0+828228207left-27.4757595153.0356445Continue on Main St (15) at exit #2. Go for 1 mile.94.31572.0+811754670forward-27.4615402153.0356445Take right ramp onto McLachlan St toward 25/Eagle Farm/Brisbane Airport. Go for 0.2 miles.23.5343.0+828880864lightRight-27.4590492153.0357361Turn right onto Brunswick St. Go for 0.3 miles.95.9530.0-130729663right-27.4622307153.0397949Your destination on Brunswick St is on the left. The trip takes 11 miles and 17 mins.0.00.0forward+130759717-27.5791149,153.0979919 -27.5790405,153.0978851 -27.5786304,153.097381680.013.890.09.728.2
    AUQueenslandBrisbaneEight Mile PlainsMcKechnie Dr
    +130851874-27.5786304,153.0973816 -27.57798,153.0966034 -27.5778008,153.0964661 -27.5776005,153.0963745 -27.5773792,153.096344178.013.890.09.7218.3
    AUQueenslandBrisbaneEight Mile PlainsMcKechnie Dr
    +130851875-27.5773792,153.096344 -27.5764599,153.0965271103.013.890.09.7210.6
    AUQueenslandBrisbaneEight Mile PlainsMcKechnie Dr
    +767490982-27.5764599,153.0965271 -27.5764999,153.0963898 -27.5763397,153.0951233140.016.670.016.678.4
    AUQueenslandBrisbaneEight Mile PlainsMiles Platting Rd
    +767490983-27.5763397,153.0951233 -27.5762405,153.094512961.016.670.016.673.7
    AUQueenslandBrisbaneEight Mile PlainsMiles Platting Rd
    +130759112-27.5762405,153.0945129 -27.5761204,153.093627988.016.670.016.675.3
    AUQueenslandBrisbaneEight Mile PlainsMiles Platting Rd
    +130759082-27.5761204,153.0936279 -27.5759907,153.092758286.016.670.016.675.2
    AUQueenslandBrisbaneEight Mile PlainsMiles Platting Rd
    +130759055-27.5759907,153.0927582 -27.5759201,153.092254650.016.670.016.673.0
    AUQueenslandBrisbaneEight Mile PlainsMiles Platting Rd
    +130759043-27.5759201,153.0922546 -27.5758991,153.092102115.016.670.016.670.9
    AUQueenslandBrisbaneEight Mile Plains
    +130759031-27.5758991,153.0921021 -27.5757809,153.092025815.019.440.018.060.8
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +130851941-27.5757809,153.0920258 -27.5752907,153.0918121 -27.5749302,153.0916748100.019.440.018.065.5
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +130851942-27.5749302,153.0916748 -27.5740795,153.0913239100.019.440.018.065.5
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +130851862-27.5740795,153.0913239 -27.5734901,153.09109569.019.440.018.063.8
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +130851861-27.5734901,153.091095 -27.5731201,153.090911944.019.440.018.062.4
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918831-27.5731201,153.0909119 -27.5728893,153.090805127.019.440.018.061.5
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918832-27.5728893,153.0908051 -27.5723991,153.0906067 -27.5722103,153.090499981.019.440.018.064.5
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918827-27.5722103,153.0904999 -27.5719509,153.090362531.019.440.018.061.7
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918833-27.5719509,153.0903625 -27.5717106,153.090255728.019.440.018.061.6
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +846918834-27.5717106,153.0902557 -27.5714703,153.090133729.019.440.018.061.6
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +130757981-27.5714703,153.0901337 -27.5711498,153.090011637.019.440.018.062.0
    AUQueenslandBrisbaneEight Mile PlainsLogan Rd
    +778753942-27.5711498,153.0900116 -27.5710392,153.0898895 -27.5704498,153.0897064 -27.5699806,153.0894012 -27.5698109,153.0892334 -27.5696507,153.0890503 -27.5693398,153.0885468256.027.780.018.0614.2
    AUQueenslandBrisbaneEight Mile Plains
    +778753943-27.5693398,153.0885468 -27.5685005,153.0870361 -27.5683002,153.0866852 -27.5681305,153.0865479240.027.780.026.399.1
    AUQueenslandBrisbaneEight Mile Plains
    +842194179-27.5681305,153.0865479 -27.5675297,153.0855865115.027.780.026.394.4
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +842194180-27.5675297,153.0855865 -27.5662403,153.0835724 -27.5645599,153.0809937 -27.5645103,153.0809174569.027.780.026.3921.6
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180257-27.5645103,153.0809174 -27.5644493,153.0808258 -27.5643902,153.08071923.027.780.026.390.9
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy, Riverside Expy
    +778513858-27.5643902,153.080719 -27.5632801,153.0790863 -27.5629597,153.0786438 -27.5626793,153.0782623 -27.5623398,153.0778046366.027.780.026.3913.9
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +130848965-27.5623398,153.0778046 -27.5618191,153.077102790.027.780.026.393.4
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180252-27.5618191,153.0771027 -27.5615292,153.0767517 -27.56147,153.076675457.027.780.026.392.2
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180251-27.56147,153.0766754 -27.5613995,153.076583911.027.780.026.390.4
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180253-27.5613995,153.0765839 -27.5612907,153.0764771 -27.5612202,153.076400826.027.780.026.391.0
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +791180254-27.5612202,153.0764008 -27.5611191,153.0762939 -27.5605106,153.0756226 -27.5587692,153.0739288 -27.5560703,153.0716248 -27.5530605,153.06912231157.027.780.026.3943.8
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +782455208-27.5530605,153.0691223 -27.5518303,153.0680542172.027.780.026.396.5
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +782455209-27.5518303,153.0680542 -27.5516701,153.067916922.027.780.026.390.8
    AUQueenslandBrisbaneMacgregorM3, Pacific Mtwy
    +130752000-27.5516701,153.0679169 -27.5500202,153.0665436228.027.780.026.398.6
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +762719178-27.5500202,153.0665436 -27.5492992,153.065948599.027.780.026.393.8
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +762719179-27.5492992,153.0659485 -27.5465107,153.0635986387.027.780.026.3914.7
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +130852721-27.5465107,153.0635986 -27.5454407,153.0627136147.027.780.026.395.6
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +782455204-27.5454407,153.0627136 -27.5446301,153.062027112.027.780.026.394.2
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +851414677-27.5446301,153.062027 -27.5444107,153.0618439 -27.5436707,153.0612946 -27.5428104,153.0607452 -27.5418606,153.0602264 -27.5410309,153.0598297456.027.780.026.3917.3
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +851414678-27.5410309,153.0598297 -27.5407505,153.0596924 -27.5401402,153.0594635105.027.780.026.394.0
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +828675832-27.5401402,153.0594635 -27.5396996,153.0593262 -27.5380306,153.0588379242.027.780.026.399.2
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +828675833-27.5380306,153.0588379 -27.5377598,153.058761631.027.780.026.391.2
    AUQueenslandBrisbaneNathanM3, Pacific Mtwy
    +821207667-27.5377598,153.0587616 -27.5372505,153.0586548 -27.5360794,153.0584106190.027.780.026.397.2
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +821207668-27.5360794,153.0584106 -27.5345192,153.058075176.027.780.026.396.7
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +851414665-27.5345192,153.058075 -27.5335693,153.0578766107.027.780.026.394.1
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +851414666-27.5335693,153.0578766 -27.5287495,153.0568542 -27.5279198,153.0566559 -27.5272694,153.0564575 -27.52668,153.0562439 -27.5265102,153.0561676803.027.780.026.3930.4
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +782385629-27.5265102,153.0561676 -27.5260201,153.055954 -27.5256195,153.0557709106.027.780.026.394.0
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +130853483-27.5256195,153.0557709 -27.5253601,153.0556335 -27.5246906,153.0552826 -27.5240498,153.0548706 -27.5235901,153.0545044258.027.780.026.399.8
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +772093015-27.5235901,153.0545044 -27.5233498,153.054321332.027.780.026.391.2
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +772093016-27.5233498,153.0543213 -27.5231991,153.054214519.027.780.026.390.7
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +736390885-27.5231991,153.0542145 -27.5209904,153.052475299.027.780.026.3911.3
    AUQueenslandBrisbaneTarragindiM3, Pacific Mtwy
    +782385634-27.5209904,153.052475 -27.5201492,153.0517731116.027.780.026.394.4
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +782385635-27.5201492,153.0517731 -27.5198994,153.051574733.027.780.026.391.3
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +778520137-27.5198994,153.0515747 -27.5198002,153.051483214.027.780.026.390.5
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +791180780-27.5198002,153.0514832 -27.5197201,153.05143749.027.780.026.390.3
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +791180781-27.5197201,153.0514374 -27.5196705,153.0513916 -27.5188999,153.0507507 -27.51824,153.0500336 -27.5175304,153.0490723 -27.5173092,153.0487061 -27.5169201,153.0479431 -27.5146999,153.0434113979.027.780.026.3937.1
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +130853660-27.5146999,153.0434113 -27.5142193,153.0424347 -27.5138206,153.0417633 -27.5135002,153.0413666 -27.5131302,153.0409546 -27.5129299,153.0407715329.027.780.026.3912.5
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +833000842-27.5129299,153.0407715 -27.5126896,153.0405731 -27.5122299,153.0402832 -27.5098591,153.0390167 -27.5095406,153.0388641 -27.5092793,153.0387726452.027.780.026.3917.1
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +833000843-27.5092793,153.0387726 -27.5090008,153.038681 -27.5088291,153.0386353 -27.5083199,153.0385437109.027.780.026.394.1
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +130853471-27.5083199,153.0385437 -27.5077591,153.0385132 -27.5072899,153.0385437114.027.780.026.394.3
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +130853513-27.5072899,153.0385437 -27.5068703,153.0386047 -27.5041294,153.0391083355.027.780.026.3913.5
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +782459444-27.5041294,153.0391083 -27.50317,153.0392914108.027.780.026.394.1
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +782459445-27.50317,153.0392914 -27.5017509,153.0395508159.027.780.026.396.0
    AUQueenslandBrisbaneGreenslopesM3, Pacific Mtwy
    +779045467-27.5017509,153.0395508 -27.5009995,153.0397034 -27.5007401,153.0397186113.027.780.026.394.3
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +779045468-27.5007401,153.0397186 -27.5005703,153.0397339 -27.500351,153.039733943.022.220.022.221.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +821196745-27.500351,153.0397339 -27.5001106,153.0397339 -27.4993591,153.0396729 -27.4989605,153.039566156.022.220.022.227.0
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +821196746-27.4989605,153.039566 -27.4988594,153.0395508 -27.49823,153.039306685.022.220.022.223.8
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +812250074-27.49823,153.0393066 -27.4977093,153.0390167 -27.4974995,153.038879491.022.220.022.224.1
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +826731481-27.4974995,153.0388794 -27.4974003,153.038803113.022.220.022.220.6
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +826731482-27.4974003,153.0388031 -27.4970894,153.038604739.022.220.022.221.8
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +130846576-27.4970894,153.0386047 -27.4968204,153.038406435.022.220.022.221.6
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +130738021-27.4968204,153.0384064 -27.4967098,153.038314815.022.220.022.220.7
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +778479274-27.4967098,153.0383148 -27.4961395,153.037902875.022.220.022.223.4
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +778479275-27.4961395,153.0379028 -27.4959297,153.03773528.022.220.022.221.3
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851379612-27.4959297,153.037735 -27.4958897,153.0377045 -27.4957695,153.037612921.022.220.022.220.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851460191-27.4957695,153.0376129 -27.4948807,153.0368958121.022.220.022.225.4
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851460192-27.4948807,153.0368958 -27.4944096,153.0365143 -27.4940605,153.0361481 -27.4939404,153.0359955137.022.220.022.226.2
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851414374-27.4939404,153.0359955 -27.4934292,153.0354004 -27.4932709,153.0351868109.022.220.022.224.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +812250623-27.4932709,153.0351868 -27.4930305,153.034835843.022.220.022.221.9
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +802502378-27.4930305,153.0348358 -27.4929104,153.034622224.022.220.022.221.1
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +811756237-27.4929104,153.0346222 -27.4927597,153.034393328.022.220.022.221.3
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +811756238-27.4927597,153.0343933 -27.4926701,153.034271215.022.220.022.220.7
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +840727547-27.4926701,153.0342712 -27.4923592,153.033844 -27.4920998,153.0335999 -27.4918404,153.0334167 -27.4914608,153.0332336 -27.4911499,153.0330963 -27.4907398,153.0330048255.022.220.022.2211.5
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +840727548-27.4907398,153.0330048 -27.4899998,153.032974282.022.220.022.223.7
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851414359-27.4899998,153.0329742 -27.48983,153.0329437 -27.4895992,153.0329132 -27.48946,153.032882760.022.220.022.222.7
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +851414360-27.48946,153.0328827 -27.4889603,153.032836955.022.220.022.222.5
    AUQueenslandBrisbaneWoolloongabbaM3, Pacific Mtwy
    +791185396-27.4889603,153.0328369 -27.48876,153.0327148 -27.4883404,153.0325317 -27.4881706,153.032455495.016.670.016.675.7
    AUQueenslandBrisbaneWoolloongabba
    +792771311-27.4881706,153.0324554 -27.4880695,153.0324249 -27.4879494,153.032363926.016.670.016.671.6
    AUQueenslandBrisbaneWoolloongabba
    +778513855-27.4879494,153.0323639 -27.4862995,153.0316925 -27.4859505,153.0314331 -27.4858799,153.0313721251.016.670.016.6715.1
    AUQueenslandBrisbaneWoolloongabba
    +130735305-27.4858799,153.0313721 -27.4858608,153.031265310.016.670.016.670.6
    AUQueenslandBrisbaneWoolloongabbaStanley St
    +130735302-27.4858608,153.0312653 -27.4857903,153.030746551.016.670.016.673.1
    AUQueenslandBrisbaneWoolloongabbaStanley St
    +130735282-27.4857903,153.0307465 -27.4857407,153.030395535.016.670.016.672.1
    AUQueenslandBrisbaneWoolloongabbaStanley St
    +130735264-27.4857407,153.0303955 -27.4856205,153.030334514.016.670.016.670.8
    AUQueenslandBrisbaneWoolloongabbaAllen St
    +841394290-27.4856205,153.0303345 -27.4855099,153.030304 -27.4854107,153.030319223.016.670.016.671.4
    AUQueenslandBrisbaneWoolloongabbaAllen St
    +841394291-27.4854107,153.0303192 -27.4853191,153.030319210.016.670.016.670.6
    AUQueenslandBrisbaneWoolloongabbaAllen St
    +841394292-27.4853191,153.0303192 -27.4852009,153.030334513.016.670.016.670.8
    AUQueenslandBrisbaneWoolloongabbaAllen St
    +130735130-27.4852009,153.0303345 -27.4847908,153.030410846.016.670.016.672.8
    AUQueenslandBrisbaneWoolloongabbaAllen St
    +130735029-27.4847908,153.0304108 -27.4846802,153.03042612.016.670.016.670.7
    AUQueenslandBrisbaneWoolloongabbaAllen St
    +130735018-27.4846802,153.030426 -27.4845104,153.0305481 -27.4840393,153.0306396 -27.4839497,153.0306854 -27.48386,153.030777 -27.4838104,153.0309143114.016.670.016.676.8
    AUQueenslandBrisbaneWoolloongabbaAllen St
    -130856418-27.4838104,153.0309143 -27.4838409,153.0311584 -27.4838505,153.0312195 -27.4838696,153.0313873 -27.4839096,153.031646773.016.670.016.674.4
    AUQueenslandBrisbaneKangaroo PointVulture St
    -842230577-27.4839096,153.0316467 -27.4839706,153.03207442.016.670.016.672.5
    AUQueenslandBrisbaneKangaroo PointVulture St
    -842230583-27.4839706,153.032074 -27.4839993,153.032257118.016.670.016.671.1
    AUQueenslandBrisbaneKangaroo PointVulture St
    -842230582-27.4839993,153.0322571 -27.4840908,153.032867461.016.670.016.673.7
    AUQueenslandBrisbaneKangaroo PointVulture St
    -130734905-27.4840908,153.0328674 -27.4842205,153.03384497.016.670.016.675.8
    AUQueenslandBrisbaneKangaroo PointVulture St
    -130734936-27.4842205,153.033844 -27.4843693,153.034774893.016.670.016.675.6
    AUQueenslandBrisbaneKangaroo PointVulture St
    -811701153-27.4843693,153.0347748 -27.4844799,153.035446267.016.670.016.674.0
    AUQueenslandBrisbaneWoolloongabbaVulture St
    -811701152-27.4844799,153.0354462 -27.4845104,153.035583513.016.670.016.670.8
    AUQueenslandBrisbaneWoolloongabbaVulture St
    -782798239-27.4845104,153.0355835 -27.4845505,153.035842925.016.670.016.671.5
    AUQueenslandBrisbaneWoolloongabbaVulture St
    +828228207-27.4845505,153.0358429 -27.4842796,153.035842930.016.670.016.671.8
    AUQueenslandBrisbaneKangaroo PointMain St
    +828228208-27.4842796,153.0358429 -27.4836693,153.035873467.016.670.016.674.0
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734780-27.4836693,153.0358734 -27.4832802,153.035842943.016.670.016.672.6
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734695-27.4832802,153.0358429 -27.4829197,153.035842940.016.670.016.672.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734588-27.4829197,153.0358429 -27.48283,153.03584299.016.670.016.670.5
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734560-27.48283,153.0358429 -27.4825592,153.035842930.016.670.016.671.8
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734480-27.4825592,153.0358429 -27.4824104,153.035842916.016.670.016.671.0
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734442-27.4824104,153.0358429 -27.4821491,153.035827629.016.670.016.671.7
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734384-27.4821491,153.0358276 -27.4819107,153.035812426.016.670.016.671.6
    AUQueenslandBrisbaneKangaroo PointMain St
    +782798224-27.4819107,153.0358124 -27.4816303,153.035812431.016.670.016.671.9
    AUQueenslandBrisbaneKangaroo PointMain St
    +782798225-27.4816303,153.0358124 -27.4814301,153.035812422.016.670.016.671.3
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734269-27.4814301,153.0358124 -27.4812508,153.035812419.016.670.016.671.1
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734246-27.4812508,153.0358124 -27.4805794,153.035781974.016.670.016.674.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734128-27.4805794,153.0357819 -27.4801197,153.035766651.016.670.016.673.1
    AUQueenslandBrisbaneKangaroo PointMain St
    +130734055-27.4801197,153.0357666 -27.4793091,153.035751390.016.670.016.675.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +130733940-27.4793091,153.0357513 -27.4792404,153.03575137.016.670.016.670.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +130733926-27.4792404,153.0357513 -27.47855,153.035736176.016.670.016.674.6
    AUQueenslandBrisbaneKangaroo PointMain St
    +845530476-27.47855,153.0357361 -27.4782295,153.035736135.016.670.016.672.1
    AUQueenslandBrisbaneKangaroo PointMain St
    +845530477-27.4782295,153.0357361 -27.4780293,153.035751322.016.670.016.671.3
    AUQueenslandBrisbaneKangaroo PointMain St
    +845360074-27.4780293,153.0357513 -27.4774399,153.035720865.016.670.016.673.9
    AUQueenslandBrisbaneKangaroo PointMain St
    +845360075-27.4774399,153.0357208 -27.4769306,153.035705656.016.670.016.673.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +845360076-27.4769306,153.0357056 -27.4765701,153.035705640.016.670.016.672.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +130733534-27.4765701,153.0357056 -27.4764309,153.035705615.016.670.016.670.9
    AUQueenslandBrisbaneKangaroo PointMain St
    +811754675-27.4764309,153.0357056 -27.4762497,153.035690320.016.670.016.671.2
    AUQueenslandBrisbaneKangaroo PointMain St
    +821266206-27.4762497,153.0356903 -27.4758797,153.035644541.016.670.016.672.5
    AUQueenslandBrisbaneKangaroo PointMain St
    +821266207-27.4758797,153.0356445 -27.4758091,153.03564457.016.670.016.670.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +130733423-27.4758091,153.0356445 -27.4757595,153.03564455.016.670.016.670.3
    AUQueenslandBrisbaneKangaroo PointMain St
    +811754670-27.4757595,153.0356445 -27.4756107,153.035583517.016.670.016.671.0
    AUQueenslandBrisbaneKangaroo PointMain St
    +811754671-27.4756107,153.0355835 -27.4752903,153.035583535.016.670.016.672.1
    AUQueenslandBrisbaneKangaroo PointMain St
    +781681415-27.4752903,153.0355835 -27.4749699,153.03555335.016.670.016.672.1
    AUQueenslandBrisbaneKangaroo PointMain St
    +130849254-27.4749699,153.035553 -27.4743099,153.03555373.016.670.016.674.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +845463722-27.4743099,153.035553 -27.4732399,153.0355377118.016.670.016.677.1
    AUQueenslandBrisbaneKangaroo PointMain St
    +845463723-27.4732399,153.0355377 -27.4727306,153.035522556.016.670.016.673.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +845463724-27.4727306,153.0355225 -27.4721909,153.035522560.016.670.016.673.6
    AUQueenslandBrisbaneKangaroo PointMain St
    +845463727-27.4721909,153.0355225 -27.4716606,153.035507258.016.670.016.673.5
    AUQueenslandBrisbaneKangaroo PointMain St
    +845463728-27.4716606,153.0355072 -27.4712791,153.035507242.016.670.016.672.5
    AUQueenslandBrisbaneKangaroo PointMain St
    +781679323-27.4712791,153.0355072 -27.4712505,153.03550723.016.670.016.670.2
    AUQueenslandBrisbaneKangaroo PointMain St
    +811498732-27.4712505,153.0355072 -27.4712105,153.0355225 -27.4710503,153.035583523.016.670.016.671.4
    AUQueenslandBrisbaneKangaroo PointMain St
    +130849251-27.4710503,153.0355835 -27.4709301,153.035629314.016.670.016.670.8
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy
    +779133869-27.4709301,153.0356293 -27.4706802,153.035675 -27.4706097,153.03567535.016.670.016.672.1
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy
    +779133870-27.4706097,153.035675 -27.4704609,153.0357056 -27.4702797,153.035797138.016.670.016.672.3
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy
    +130732275-27.4702797,153.0357971 -27.4698505,153.0358429 -27.4694595,153.0358734 -27.4692192,153.0358734118.016.670.016.677.1
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy
    +130732051-27.4692192,153.0358734 -27.4685898,153.035888770.016.670.016.674.2
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy
    +130731930-27.4685898,153.0358887 -27.4680004,153.035903965.016.670.016.673.9
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy
    +778423229-27.4680004,153.0359039 -27.4671707,153.035858292.016.670.016.675.5
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy
    +778423230-27.4671707,153.0358582 -27.4659309,153.0358124137.016.670.016.678.2
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy
    +778423228-27.4659309,153.0358124 -27.4658508,153.03581248.016.670.016.670.5
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy
    +778592178-27.4658508,153.0358124 -27.4657001,153.035812416.016.670.016.671.0
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy, Story Brg
    +779145761-27.4657001,153.0358124 -27.4648304,153.035781996.016.670.016.675.8
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy, Story Brg
    +779145762-27.4648304,153.0357819 -27.4647808,153.0357819 -27.4627304,153.0357056233.016.670.016.6714.0
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy, Story Brg
    +750205884-27.4627304,153.0357056 -27.4624996,153.035705625.016.670.016.671.5
    AUQueenslandBrisbaneKangaroo PointBradfield Hwy, Story Brg
    +779145730-27.4624996,153.0357056 -27.4624195,153.03570568.016.670.016.670.5
    AUQueenslandBrisbaneFortitude ValleyBradfield Hwy, Story Brg
    +779145731-27.4624195,153.0357056 -27.4622898,153.035705614.016.670.016.670.8
    AUQueenslandBrisbaneFortitude ValleyBradfield Hwy, Story Brg
    +779145721-27.4622898,153.0357056 -27.4619904,153.035705633.016.670.016.672.0
    AUQueenslandBrisbaneFortitude ValleyBradfield Hwy, Story Brg
    +130851739-27.4619904,153.0357056 -27.4617596,153.035659826.016.670.016.671.6
    AUQueenslandBrisbaneFortitude ValleyBradfield Hwy
    +130851740-27.4617596,153.0356598 -27.4615402,153.035644524.016.670.016.671.4
    AUQueenslandBrisbaneFortitude ValleyBradfield Hwy
    +828880864-27.4615402,153.0356445 -27.4613094,153.035614 -27.4611397,153.035553 -27.4609203,153.0354004 -27.4607296,153.0351868 -27.4606895,153.0351563109.016.670.016.676.5
    AUQueenslandBrisbaneFortitude Valley
    +828880865-27.4606895,153.0351563 -27.4605103,153.0349731 -27.4603901,153.0348511 -27.4602909,153.0347595 -27.4601593,153.03472974.016.670.016.674.4
    AUQueenslandBrisbaneFortitude Valley
    +811489085-27.4601593,153.034729 -27.4600105,153.0347443 -27.4598408,153.0348969 -27.4597702,153.034973151.016.670.016.673.1
    AUQueenslandBrisbaneFortitude Valley
    +782798336-27.4597702,153.0349731 -27.4596596,153.0350816.016.670.016.671.0
    AUQueenslandBrisbaneFortitude ValleyMcLachlan St
    +782798337-27.4596596,153.03508 -27.4593391,153.035430949.016.670.016.672.9
    AUQueenslandBrisbaneFortitude ValleyMcLachlan St
    +130729659-27.4593391,153.0354309 -27.4590492,153.035736144.016.670.016.672.6
    AUQueenslandBrisbaneFortitude ValleyMcLachlan St
    -130729663-27.4590492,153.0357361 -27.4593506,153.036117650.013.890.09.725.1
    AUQueenslandBrisbaneFortitude ValleyBrunswick St
    -130729708-27.4593506,153.0361176 -27.4595604,153.03637734.013.890.09.723.5
    AUQueenslandBrisbaneFortitude ValleyBrunswick St
    -779178787-27.4595604,153.036377 -27.4598408,153.036743247.013.890.09.724.8
    AUQueenslandBrisbaneFortitude ValleyBrunswick St
    -779178786-27.4598408,153.0367432 -27.45998,153.036926323.013.890.09.722.4
    AUQueenslandBrisbaneFortitude ValleyBrunswick St
    -130729848-27.45998,153.0369263 -27.4601803,153.037185733.013.890.09.723.4
    AUQueenslandBrisbaneFortitude ValleyBrunswick St
    -130851715-27.4601803,153.0371857 -27.4603996,153.037460336.013.890.09.723.7
    AUQueenslandBrisbaneFortitude ValleyBrunswick St
    -130851714-27.4603996,153.0374603 -27.4608307,153.037994471.013.890.09.727.3
    AUQueenslandBrisbaneFortitude ValleyBrunswick St
    -130730226-27.4608307,153.0379944 -27.4615307,153.0388947118.013.890.09.7212.1
    AUQueenslandBrisbaneFortitude ValleyBrunswick St
    -130730440-27.4615307,153.0388947 -27.4622307,153.0397949118.013.890.09.7212.1
    AUQueenslandBrisbaneNew FarmBrunswick St
    17283.01002.0998.0motorway
    \ No newline at end of file diff --git a/tests/auto/qgeoroutexmlparser/tst_qgeoroutexmlparser.cpp b/tests/auto/qgeoroutexmlparser/tst_qgeoroutexmlparser.cpp new file mode 100644 index 0000000..36d679e --- /dev/null +++ b/tests/auto/qgeoroutexmlparser/tst_qgeoroutexmlparser.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QList) + +QT_USE_NAMESPACE + +class tst_QGeoRouteXmlParser : public QObject +{ + Q_OBJECT + +public: + tst_QGeoRouteXmlParser() + : start(0.0, 0.0), + end(1.0, 1.0) + { + qRegisterMetaType >(); + } + +private: + // dummy values for creating the request object + QGeoCoordinate start; + QGeoCoordinate end; + +private slots: + void test_realData1() + { + QFile f(":/route1.xml"); + if (!f.open(QIODevice::ReadOnly)) + QFAIL("could not open route1.xml"); + + QGeoRouteRequest req(start, end); + QGeoRouteXmlParser xp(req); + xp.setAutoDelete(false); + + QSignalSpy resultsSpy(&xp, SIGNAL(results(QList))); + + xp.parse(f.readAll()); + + QTRY_COMPARE(resultsSpy.count(), 1); + + QVariantList arguments = resultsSpy.first(); + + // xml contains exactly 1 route + QList results = arguments.at(0).value >(); + QCOMPARE(results.size(), 1); + QGeoRoute route = results.first(); + + QList segments; + // get all the segments on the route + segments << route.firstRouteSegment(); + while (segments.last().isValid()) + segments << segments.last().nextRouteSegment(); + + // should be 9 segments in the list (last one invalid) + QCOMPARE(segments.size(), 9); + + // check the first maneuver is correct + QGeoManeuver first = segments.at(0).maneuver(); + QCOMPARE(first.instructionText(), QStringLiteral("Head toward Logan Rd (95) on Padstow Rd (56). Go for 0.3 miles.")); + QCOMPARE(first.position(), QGeoCoordinate(-27.5752144, 153.0879669)); + + QCOMPARE(first.timeToNextInstruction(), 24); + QCOMPARE(first.distanceToNextInstruction(), 403.0); + + // check the last two maneuvers -- route1.xml has a directionless final maneuver + QGeoManeuver secondLast = segments.at(6).maneuver(); + QVERIFY(secondLast.instructionText().contains("Turn right onto Bartley St")); + QCOMPARE(secondLast.position(), QGeoCoordinate(-27.4655991, 153.0231628)); + QCOMPARE(secondLast.distanceToNextInstruction(), 181.0); + QCOMPARE(secondLast.timeToNextInstruction(), 41); + + QGeoManeuver last = segments.at(7).maneuver(); + QVERIFY(last.instructionText().contains("Arrive at Bartley St")); + QCOMPARE(last.position(), QGeoCoordinate(-27.4650097, 153.0230255)); + QCOMPARE(last.distanceToNextInstruction(), 0.0); + QCOMPARE(last.timeToNextInstruction(), 0); + } + + void test_realData2() + { + QFile f(":/route2.xml"); + if (!f.open(QIODevice::ReadOnly)) + QFAIL("could not open route2.xml"); + + QGeoRouteRequest req(start, end); + QGeoRouteXmlParser xp(req); + xp.setAutoDelete(false); + + QSignalSpy resultsSpy(&xp, SIGNAL(results(QList))); + + xp.parse(f.readAll()); + + QTRY_COMPARE(resultsSpy.count(), 1); + + QVariantList arguments = resultsSpy.first(); + + // xml contains exactly 1 route + QList results = arguments.at(0).value >(); + QCOMPARE(results.size(), 1); + QGeoRoute route = results.first(); + + QList segments; + // get all the segments on the route + segments << route.firstRouteSegment(); + while (segments.last().isValid()) + segments << segments.last().nextRouteSegment(); + + // should be 14 segments in the list (last one invalid) + QCOMPARE(segments.size(), 14); + + QCOMPARE(route.path().size(), 284); + QCOMPARE(route.path().at(57), QGeoCoordinate(-27.5530605, 153.0691223)); + QCOMPARE(route.path().at(283), QGeoCoordinate(-27.4622307, 153.0397949)); + + QVERIFY(segments.at(0).maneuver().instructionText().contains("Head toward Electronics St")); + QCOMPARE(segments.at(0).maneuver().direction(), QGeoManeuver::DirectionForward); + QVERIFY(segments.at(1).maneuver().instructionText().contains("Turn left onto Miles Platting")); + QCOMPARE(segments.at(1).maneuver().direction(), QGeoManeuver::DirectionLeft); + QVERIFY(segments.at(2).maneuver().instructionText().contains("Turn right onto Logan Rd")); + QCOMPARE(segments.at(2).maneuver().direction(), QGeoManeuver::DirectionRight); + QVERIFY(segments.at(3).maneuver().instructionText().contains("Take exit #14/M3/City")); + QCOMPARE(segments.at(3).maneuver().direction(), QGeoManeuver::DirectionLightLeft); + QVERIFY(segments.at(4).maneuver().instructionText().contains("Take exit #2/41")); + QCOMPARE(segments.at(4).maneuver().direction(), QGeoManeuver::DirectionLightLeft); + QVERIFY(segments.at(5).maneuver().instructionText().contains("Turn right onto Allen St")); + QCOMPARE(segments.at(5).maneuver().direction(), QGeoManeuver::DirectionRight); + QVERIFY(segments.at(6).maneuver().instructionText().contains("Bear right to stay on")); + QCOMPARE(segments.at(6).maneuver().direction(), QGeoManeuver::DirectionLightRight); + QVERIFY(segments.at(7).maneuver().instructionText().contains("Bear right onto Vulture St")); + QCOMPARE(segments.at(7).maneuver().direction(), QGeoManeuver::DirectionLightRight); + } +}; + +QTEST_GUILESS_MAIN(tst_QGeoRouteXmlParser) +#include "tst_qgeoroutexmlparser.moc" + diff --git a/tests/auto/qgeoroutingmanager/qgeoroutingmanager.pro b/tests/auto/qgeoroutingmanager/qgeoroutingmanager.pro new file mode 100644 index 0000000..836d29c --- /dev/null +++ b/tests/auto/qgeoroutingmanager/qgeoroutingmanager.pro @@ -0,0 +1,11 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeoroutingmanager + +HEADERS += tst_qgeoroutingmanager.h + +SOURCES += tst_qgeoroutingmanager.cpp + +CONFIG -= app_bundle + +QT += location testlib diff --git a/tests/auto/qgeoroutingmanager/tst_qgeoroutingmanager.cpp b/tests/auto/qgeoroutingmanager/tst_qgeoroutingmanager.cpp new file mode 100644 index 0000000..4dbeea5 --- /dev/null +++ b/tests/auto/qgeoroutingmanager/tst_qgeoroutingmanager.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include "tst_qgeoroutingmanager.h" + +QT_USE_NAMESPACE + + +void tst_QGeoRoutingManager::initTestCase() +{ + /* + * Set custom path since CI doesn't install test plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif + tst_QGeoRoutingManager::loadRoutingManager(); +} + +void tst_QGeoRoutingManager::cleanupTestCase() +{ + //delete qgeoroutingmanager; + delete qgeoserviceprovider; +} + +void tst_QGeoRoutingManager::init() +{ +} + +void tst_QGeoRoutingManager::cleanup() +{ +} + +void tst_QGeoRoutingManager::loadRoutingManager() +{ + QStringList providers = QGeoServiceProvider::availableServiceProviders(); + QVERIFY(providers.contains("georoute.test.plugin")); + + qgeoserviceprovider = new QGeoServiceProvider("georoute.test.plugin"); + QVERIFY(qgeoserviceprovider); + QCOMPARE(qgeoserviceprovider->error(), QGeoServiceProvider::NotSupportedError); + qgeoserviceprovider->setAllowExperimental(true); + + QCOMPARE(qgeoserviceprovider->routingFeatures(), + QGeoServiceProvider::OfflineRoutingFeature + | QGeoServiceProvider::AlternativeRoutesFeature + | QGeoServiceProvider::RouteUpdatesFeature + | QGeoServiceProvider::ExcludeAreasRoutingFeature); + QCOMPARE(qgeoserviceprovider->error(), QGeoServiceProvider::NoError); + + qgeoroutingmanager = qgeoserviceprovider->routingManager(); + QVERIFY(qgeoroutingmanager); + +} + +void tst_QGeoRoutingManager::supports() +{ + QCOMPARE(qgeoroutingmanager->supportedTravelModes(),QGeoRouteRequest::PedestrianTravel); + QCOMPARE(qgeoroutingmanager->supportedFeatureTypes(),QGeoRouteRequest::TollFeature); + QCOMPARE(qgeoroutingmanager->supportedFeatureWeights(),QGeoRouteRequest::PreferFeatureWeight); + QCOMPARE(qgeoroutingmanager->supportedRouteOptimizations(),QGeoRouteRequest::FastestRoute); + QCOMPARE(qgeoroutingmanager->supportedSegmentDetails(),QGeoRouteRequest::BasicSegmentData); + QCOMPARE(qgeoroutingmanager->supportedManeuverDetails(),QGeoRouteRequest::BasicManeuvers); +} + +void tst_QGeoRoutingManager::locale() +{ + QLocale german = QLocale(QLocale::German, QLocale::Germany); + QLocale english = QLocale(QLocale::C, QLocale::AnyCountry); + + qgeoroutingmanager->setLocale(german); + + QCOMPARE(qgeoroutingmanager->locale(), german); + + QVERIFY(qgeoroutingmanager->locale() != english); + + QLocale en_UK = QLocale(QLocale::English, QLocale::UnitedKingdom); + qgeoroutingmanager->setLocale(en_UK); + QCOMPARE(qgeoroutingmanager->measurementSystem(), en_UK.measurementSystem()); + qgeoroutingmanager->setMeasurementSystem(QLocale::MetricSystem); + QCOMPARE(qgeoroutingmanager->measurementSystem(), QLocale::MetricSystem); + QVERIFY(qgeoroutingmanager->locale().measurementSystem() != qgeoroutingmanager->measurementSystem()); +} + +void tst_QGeoRoutingManager::name() +{ + QString name = "georoute.test.plugin"; + QCOMPARE(qgeoroutingmanager->managerName(), name); +} + +void tst_QGeoRoutingManager::version() +{ + QCOMPARE(qgeoroutingmanager->managerVersion(), 100); +} + +void tst_QGeoRoutingManager::calculate() +{ + QString error = "no error"; + origin = new QGeoCoordinate(12.12 , 23.23); + destination = new QGeoCoordinate(34.34 , 89.32); + request = new QGeoRouteRequest(*origin, *destination); + + reply = qgeoroutingmanager->calculateRoute(*request); + + QCOMPARE(reply->error(), QGeoRouteReply::NoError); + QCOMPARE(reply->errorString(), error); + + delete origin; + delete destination; + delete request; + delete reply; +} + + +void tst_QGeoRoutingManager::update() +{ + QString error = "no error"; + position = new QGeoCoordinate(34.34, 89.32); + route = new QGeoRoute(); + + reply = qgeoroutingmanager->updateRoute(*route, *position); + + QCOMPARE(reply->error(), QGeoRouteReply::CommunicationError); + QCOMPARE(reply->errorString(), error); + + delete position; + delete route; + delete reply; +} + +QTEST_MAIN(tst_QGeoRoutingManager) + diff --git a/tests/auto/qgeoroutingmanager/tst_qgeoroutingmanager.h b/tests/auto/qgeoroutingmanager/tst_qgeoroutingmanager.h new file mode 100644 index 0000000..8d85d47 --- /dev/null +++ b/tests/auto/qgeoroutingmanager/tst_qgeoroutingmanager.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#ifndef TST_QGEOROUTINGMANAGER_H +#define TST_QGEOROUTINGMANAGER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QGeoRoutingManager: public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + void supports(); + void locale(); + void name(); + void version(); + void calculate(); + void update(); + +private: + QGeoServiceProvider *qgeoserviceprovider; + QGeoRoutingManager *qgeoroutingmanager; + QGeoRouteRequest *request; + QGeoRouteReply *reply; + QGeoCoordinate *origin; + QGeoCoordinate *destination; + QGeoRoute *route; + QGeoCoordinate *position; + void loadRoutingManager(); + +}; + +#endif diff --git a/tests/auto/qgeoroutingmanagerplugins/qgeoroutingmanagerengine_test.h b/tests/auto/qgeoroutingmanagerplugins/qgeoroutingmanagerengine_test.h new file mode 100644 index 0000000..cb75fec --- /dev/null +++ b/tests/auto/qgeoroutingmanagerplugins/qgeoroutingmanagerengine_test.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTINGMANAGERENGINE_TEST_H +#define QGEOROUTINGMANAGERENGINE_TEST_H + +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class QGeoRoutingManagerEngineTest: public QGeoRoutingManagerEngine + +{ +Q_OBJECT +public: + QGeoRoutingManagerEngineTest(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) : + QGeoRoutingManagerEngine(parameters) + { + Q_UNUSED(error) + Q_UNUSED(errorString) + setSupportedTravelModes(QGeoRouteRequest::PedestrianTravel); + setSupportedFeatureTypes(QGeoRouteRequest::TollFeature); + setSupportedFeatureWeights(QGeoRouteRequest::PreferFeatureWeight); + setSupportedRouteOptimizations(QGeoRouteRequest::FastestRoute); + setSupportedSegmentDetails(QGeoRouteRequest::BasicSegmentData); + setSupportedManeuverDetails(QGeoRouteRequest::BasicManeuvers); + } + + QGeoRouteReply* calculateRoute(const QGeoRouteRequest& request) + { + Q_UNUSED(request); + return new QGeoRouteReply(QGeoRouteReply::NoError,"no error"); + } + + QGeoRouteReply* updateRoute(const QGeoRoute &route, const QGeoCoordinate &position) + { + Q_UNUSED(route); + Q_UNUSED(position); + return new QGeoRouteReply(QGeoRouteReply::CommunicationError,"no error"); + + } + + +}; + +#endif diff --git a/tests/auto/qgeoroutingmanagerplugins/qgeoroutingmanagerplugins.pro b/tests/auto/qgeoroutingmanagerplugins/qgeoroutingmanagerplugins.pro new file mode 100644 index 0000000..4222f35 --- /dev/null +++ b/tests/auto/qgeoroutingmanagerplugins/qgeoroutingmanagerplugins.pro @@ -0,0 +1,15 @@ +TARGET = qtgeoservices_routingplugin +QT += location + +PLUGIN_TYPE = geoservices +PLUGIN_CLASS_NAME = RoutingTestGeoServicePlugin +PLUGIN_EXTENDS = - +load(qt_plugin) + +HEADERS += qgeoroutingmanagerengine_test.h \ + qgeoserviceproviderplugin_test.h + +SOURCES += qgeoserviceproviderplugin_test.cpp + +OTHER_FILES += \ + routing_plugin.json diff --git a/tests/auto/qgeoroutingmanagerplugins/qgeoserviceproviderplugin_test.cpp b/tests/auto/qgeoroutingmanagerplugins/qgeoserviceproviderplugin_test.cpp new file mode 100644 index 0000000..4c35a91 --- /dev/null +++ b/tests/auto/qgeoroutingmanagerplugins/qgeoserviceproviderplugin_test.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeoserviceproviderplugin_test.h" +#include "qgeoroutingmanagerengine_test.h" + +#include + +QGeoServiceProviderFactoryTest::QGeoServiceProviderFactoryTest() +{ +} + +QGeoServiceProviderFactoryTest::~QGeoServiceProviderFactoryTest() +{ +} + +QGeoRoutingManagerEngine* QGeoServiceProviderFactoryTest::createRoutingManagerEngine( + const QVariantMap ¶meters, QGeoServiceProvider::Error *error, + QString *errorString) const +{ + return new QGeoRoutingManagerEngineTest(parameters, error, errorString); +} diff --git a/tests/auto/qgeoroutingmanagerplugins/qgeoserviceproviderplugin_test.h b/tests/auto/qgeoroutingmanagerplugins/qgeoserviceproviderplugin_test.h new file mode 100644 index 0000000..e909b64 --- /dev/null +++ b/tests/auto/qgeoroutingmanagerplugins/qgeoserviceproviderplugin_test.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOSERVICEPROVIDER_TEST_H +#define QGEOSERVICEPROVIDER_TEST_H + +#include +#include + +QT_USE_NAMESPACE + +class QGeoServiceProviderFactoryTest: public QObject, public QGeoServiceProviderFactory +{ + Q_OBJECT + Q_INTERFACES(QGeoServiceProviderFactory) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0" + FILE "routing_plugin.json") + +public: + QGeoServiceProviderFactoryTest(); + ~QGeoServiceProviderFactoryTest(); + + QGeoRoutingManagerEngine* createRoutingManagerEngine(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, QString *errorString) const; + +}; + +#endif + + diff --git a/tests/auto/qgeoroutingmanagerplugins/routing_plugin.json b/tests/auto/qgeoroutingmanagerplugins/routing_plugin.json new file mode 100644 index 0000000..25905f6 --- /dev/null +++ b/tests/auto/qgeoroutingmanagerplugins/routing_plugin.json @@ -0,0 +1,12 @@ +{ + "Keys": ["georoute.test.plugin"], + "Provider": "georoute.test.plugin", + "Version": 100, + "Experimental": true, + "Features": [ + "OfflineRoutingFeature", + "RouteUpdatesFeature", + "AlternativeRoutesFeature", + "ExcludeAreasRoutingFeature" + ] +} diff --git a/tests/auto/qgeosatelliteinfo/qgeosatelliteinfo.pro b/tests/auto/qgeosatelliteinfo/qgeosatelliteinfo.pro new file mode 100644 index 0000000..447ebdc --- /dev/null +++ b/tests/auto/qgeosatelliteinfo/qgeosatelliteinfo.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeosatelliteinfo + +SOURCES += tst_qgeosatelliteinfo.cpp + +QT += testlib positioning diff --git a/tests/auto/qgeosatelliteinfo/tst_qgeosatelliteinfo.cpp b/tests/auto/qgeosatelliteinfo/tst_qgeosatelliteinfo.cpp new file mode 100644 index 0000000..e1b0ad4 --- /dev/null +++ b/tests/auto/qgeosatelliteinfo/tst_qgeosatelliteinfo.cpp @@ -0,0 +1,405 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include + +#include +#include +#include +#include + +#include +#include + +QT_USE_NAMESPACE +Q_DECLARE_METATYPE(QGeoSatelliteInfo) +Q_DECLARE_METATYPE(QGeoSatelliteInfo::Attribute) + +QByteArray tst_qgeosatelliteinfo_debug; + +void tst_qgeosatelliteinfo_messageHandler(QtMsgType type, const QMessageLogContext &, const QString &msg) +{ + switch (type) { + case QtDebugMsg : + tst_qgeosatelliteinfo_debug = msg.toLocal8Bit(); + break; + default: + break; + } +} + + +QList tst_qgeosatelliteinfo_qrealTestValues() +{ + QList values; + + if (qreal(DBL_MIN) == DBL_MIN) + values << DBL_MIN; + + values << FLT_MIN; + values << -1.0 << 0.0 << 1.0; + values << FLT_MAX; + + if (qreal(DBL_MAX) == DBL_MAX) + values << DBL_MAX; + + return values; +} + +QList tst_qgeosatelliteinfo_intTestValues() +{ + QList values; + values << INT_MIN << -100 << 0 << 100 << INT_MAX; + return values; +} + +QList tst_qgeosatelliteinfo_getAttributes() +{ + QList attributes; + attributes << QGeoSatelliteInfo::Elevation + << QGeoSatelliteInfo::Azimuth; + return attributes; +} + + +class tst_QGeoSatelliteInfo : public QObject +{ + Q_OBJECT + +private: + QGeoSatelliteInfo updateWithAttribute(QGeoSatelliteInfo::Attribute attribute, qreal value) + { + QGeoSatelliteInfo info; + info.setAttribute(attribute, value); + return info; + } + + void addTestData_update() + { + QTest::addColumn("info"); + + QList intValues = tst_qgeosatelliteinfo_intTestValues(); + + for (int i=0; i attributes = tst_qgeosatelliteinfo_getAttributes(); + QList qrealValues = tst_qgeosatelliteinfo_qrealTestValues(); + for (int i=0; i attributes = tst_qgeosatelliteinfo_getAttributes(); + for (int i=0; i("signal"); + + QList intValues = tst_qgeosatelliteinfo_intTestValues(); + for (int i=0; i("satId"); + + QList intValues = tst_qgeosatelliteinfo_intTestValues(); + for (int i=0; i(system)); + QCOMPARE(info.satelliteSystem(), static_cast(system)); + } + + void setSatelliteSystem_data() + { + QTest::addColumn("system"); + + QTest::newRow("Sat system undefined") + << int(QGeoSatelliteInfo::Undefined); + QTest::newRow("Sat system GPS") + << int(QGeoSatelliteInfo::GPS); + QTest::newRow("Sat system GLONASS") + << int(QGeoSatelliteInfo::GLONASS); + } + + void attribute() + { + QFETCH(QGeoSatelliteInfo::Attribute, attribute); + QFETCH(qreal, value); + + QGeoSatelliteInfo u; + QCOMPARE(u.attribute(attribute), qreal(-1.0)); + + u.setAttribute(attribute, value); + QCOMPARE(u.attribute(attribute), value); + u.removeAttribute(attribute); + QCOMPARE(u.attribute(attribute), qreal(-1.0)); + } + + void attribute_data() + { + QTest::addColumn("attribute"); + QTest::addColumn("value"); + + QList props; + props << QGeoSatelliteInfo::Elevation + << QGeoSatelliteInfo::Azimuth; + for (int i=0; i> inInfo; + QCOMPARE(inInfo, info); + } + + void datastream_data() + { + addTestData_update(); + } + + void debug() + { + QFETCH(QGeoSatelliteInfo, info); + QFETCH(int, nextValue); + QFETCH(QByteArray, debugString); + + qInstallMessageHandler(tst_qgeosatelliteinfo_messageHandler); + qDebug() << info << nextValue; + qInstallMessageHandler(0); + QCOMPARE(QString(tst_qgeosatelliteinfo_debug), QString(debugString)); + } + + void debug_data() + { + QTest::addColumn("info"); + QTest::addColumn("nextValue"); + QTest::addColumn("debugString"); + + QGeoSatelliteInfo info; + + QTest::newRow("uninitialized") << info << 45 + << QByteArray("QGeoSatelliteInfo(system=0, satId=-1, signal-strength=-1) 45"); + + info = QGeoSatelliteInfo(); + info.setSignalStrength(1); + QTest::newRow("with SignalStrength") << info << 60 + << QByteArray("QGeoSatelliteInfo(system=0, satId=-1, signal-strength=1) 60"); + + info = QGeoSatelliteInfo(); + info.setSatelliteIdentifier(1); + QTest::newRow("with SatelliteIdentifier") << info << -1 + << QByteArray("QGeoSatelliteInfo(system=0, satId=1, signal-strength=-1) -1"); + + info = QGeoSatelliteInfo(); + info.setSatelliteSystem(QGeoSatelliteInfo::GPS); + QTest::newRow("with System GPS") << info << 1 + << QByteArray("QGeoSatelliteInfo(system=1, satId=-1, signal-strength=-1) 1"); + + info = QGeoSatelliteInfo(); + info.setSatelliteSystem(QGeoSatelliteInfo::GLONASS); + QTest::newRow("with System GLONASS") << info << 56 + << QByteArray("QGeoSatelliteInfo(system=2, satId=-1, signal-strength=-1) 56"); + + info = QGeoSatelliteInfo(); + info.setAttribute(QGeoSatelliteInfo::Elevation, 1.1); + QTest::newRow("with Elevation") << info << 0 + << QByteArray("QGeoSatelliteInfo(system=0, satId=-1, signal-strength=-1, Elevation=1.1) 0"); + + info = QGeoSatelliteInfo(); + info.setAttribute(QGeoSatelliteInfo::Azimuth, 1.1); + QTest::newRow("with Azimuth") << info << 45 + << QByteArray("QGeoSatelliteInfo(system=0, satId=-1, signal-strength=-1, Azimuth=1.1) 45"); + } +}; + + +QTEST_APPLESS_MAIN(tst_QGeoSatelliteInfo) +#include "tst_qgeosatelliteinfo.moc" diff --git a/tests/auto/qgeosatelliteinfosource/qgeosatelliteinfosource.pro b/tests/auto/qgeosatelliteinfosource/qgeosatelliteinfosource.pro new file mode 100644 index 0000000..4ca0f21 --- /dev/null +++ b/tests/auto/qgeosatelliteinfosource/qgeosatelliteinfosource.pro @@ -0,0 +1,12 @@ +TEMPLATE = app +!no_system_tests:CONFIG += testcase +TARGET=tst_qgeosatelliteinfosource + +SOURCES += tst_qgeosatelliteinfosource.cpp \ + testqgeosatelliteinfosource.cpp \ + ../utils/qlocationtestutils.cpp + +HEADERS += testqgeosatelliteinfosource_p.h \ + ../utils/qlocationtestutils_p.h + +QT += positioning testlib diff --git a/tests/auto/qgeosatelliteinfosource/testqgeosatelliteinfosource.cpp b/tests/auto/qgeosatelliteinfosource/testqgeosatelliteinfosource.cpp new file mode 100644 index 0000000..0e75baa --- /dev/null +++ b/tests/auto/qgeosatelliteinfosource/testqgeosatelliteinfosource.cpp @@ -0,0 +1,767 @@ +/*********************f******************************************************* +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include +#include +#include +#include +#include + +#include +#include + +#include "testqgeosatelliteinfosource_p.h" +#include "../utils/qlocationtestutils_p.h" + +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QGeoSatelliteInfoSource::Error) + +#define MAX_WAITING_TIME 50000 + +// Must provide a valid source, unless testing the source +// returned by QGeoSatelliteInfoSource::createDefaultSource() on a system +// that has no default source +#define CHECK_SOURCE_VALID { \ + if (!m_source) { \ + if (m_testingDefaultSource && QGeoSatelliteInfoSource::createDefaultSource(0) == 0) \ + QSKIP("No default satellite source on this system"); \ + else \ + QFAIL("createTestSource() must return a valid source!"); \ + } \ + } + +class MySatelliteSource : public QGeoSatelliteInfoSource +{ + Q_OBJECT +public: + MySatelliteSource(QObject *parent = 0) + : QGeoSatelliteInfoSource(parent) { + } + virtual void startUpdates() {} + virtual void stopUpdates() {} + virtual void requestUpdate(int) {} + virtual int minimumUpdateInterval() const { + return 0; + } + Error error() const { return QGeoSatelliteInfoSource::NoError; } +}; + + +class DefaultSourceTest : public TestQGeoSatelliteInfoSource +{ + Q_OBJECT +protected: + QGeoSatelliteInfoSource *createTestSource() { + return QGeoSatelliteInfoSource::createDefaultSource(0); + } +}; + +TestQGeoSatelliteInfoSource::TestQGeoSatelliteInfoSource(QObject *parent) + : QObject(parent) +{ + qRegisterMetaType(); + + m_testingDefaultSource = false; +} + +TestQGeoSatelliteInfoSource *TestQGeoSatelliteInfoSource::createDefaultSourceTest() +{ + DefaultSourceTest *test = new DefaultSourceTest; + test->m_testingDefaultSource = true; + return test; +} + +void TestQGeoSatelliteInfoSource::base_initTestCase() +{ + qRegisterMetaType >(); +} + +void TestQGeoSatelliteInfoSource::base_init() +{ + m_source = createTestSource(); + m_testSlot2Called = false; +} + +void TestQGeoSatelliteInfoSource::base_cleanup() +{ + delete m_source; + m_source = 0; +} + +void TestQGeoSatelliteInfoSource::base_cleanupTestCase() +{ +} + +void TestQGeoSatelliteInfoSource::initTestCase() +{ + base_initTestCase(); +} + +void TestQGeoSatelliteInfoSource::init() +{ + base_init(); +} + +void TestQGeoSatelliteInfoSource::cleanup() +{ + base_cleanup(); +} + +void TestQGeoSatelliteInfoSource::cleanupTestCase() +{ + base_cleanupTestCase(); +} + +void TestQGeoSatelliteInfoSource::test_slot1() +{ +} + +void TestQGeoSatelliteInfoSource::test_slot2() +{ + m_testSlot2Called = true; +} + +void TestQGeoSatelliteInfoSource::constructor_withParent() +{ + QObject *parent = new QObject(); + new MySatelliteSource(parent); + delete parent; +} + +void TestQGeoSatelliteInfoSource::constructor_noParent() +{ + MySatelliteSource *obj = new MySatelliteSource(); + delete obj; +} + +void TestQGeoSatelliteInfoSource::createDefaultSource() +{ + QObject *parent = new QObject; + QGeoSatelliteInfoSource *source = QGeoSatelliteInfoSource::createDefaultSource(parent); + + // Check that default satellite source is successfully created. + if (!QGeoSatelliteInfoSource::availableSources().isEmpty()) + QVERIFY(source); + else + QVERIFY(!source); + + delete parent; +} + +void TestQGeoSatelliteInfoSource::createDefaultSource_noParent() +{ + QGeoSatelliteInfoSource *source = QGeoSatelliteInfoSource::createDefaultSource(0); + + // Check that default satellite source is successfully created. + if (!QGeoSatelliteInfoSource::availableSources().isEmpty()) + QVERIFY(source); + else + QVERIFY(!source); + + delete source; +} + +void TestQGeoSatelliteInfoSource::updateInterval() +{ + MySatelliteSource s; + QCOMPARE(s.updateInterval(), 0); +} + +void TestQGeoSatelliteInfoSource::setUpdateInterval() +{ + CHECK_SOURCE_VALID; + + QFETCH(int, interval); + QFETCH(int, expectedInterval); + + m_source->setUpdateInterval(interval); + QCOMPARE(m_source->updateInterval(), expectedInterval); +} + +void TestQGeoSatelliteInfoSource::setUpdateInterval_data() +{ + QTest::addColumn("interval"); + QTest::addColumn("expectedInterval"); + QGeoSatelliteInfoSource *source = createTestSource(); + int minUpdateInterval = source ? source->minimumUpdateInterval() : -1; + if (source) + delete source; + + QTest::newRow("0") << 0 << 0; + + if (minUpdateInterval > -1) { + QTest::newRow("INT_MIN") << INT_MIN << minUpdateInterval; + QTest::newRow("-1") << -1 << minUpdateInterval; + } + + if (minUpdateInterval > 0) { + QTest::newRow("more than minInterval") << minUpdateInterval + 1 << minUpdateInterval + 1; + QTest::newRow("equal to minInterval") << minUpdateInterval << minUpdateInterval; + } + + if (minUpdateInterval > 1) { + QTest::newRow("less then minInterval") << minUpdateInterval - 1 << minUpdateInterval; + QTest::newRow("in btw zero and minInterval") << 1 << minUpdateInterval; + } +} + +void TestQGeoSatelliteInfoSource::minimumUpdateInterval() +{ + CHECK_SOURCE_VALID; + + QVERIFY(m_source->minimumUpdateInterval() > 0); +} + +void TestQGeoSatelliteInfoSource::startUpdates_testIntervals() +{ + CHECK_SOURCE_VALID; + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy timeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->setUpdateInterval(7000); + int interval = m_source->updateInterval(); + + m_source->startUpdates(); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1), 9500); + for (int i = 0; i < 6; i++) { + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1) && (timeout.count() == 0), (interval*2)); + spyView.clear(); + spyUse.clear(); + } + m_source->stopUpdates(); +} + + +void TestQGeoSatelliteInfoSource::startUpdates_testIntervalChangesWhileRunning() +{ + // There are two ways of dealing with an interval change, and we have left it system dependent. + // The interval can be changed will running or after the next update. + // WinCE uses the first method, S60 uses the second method. + + // The minimum interval on the symbian emulator is 5000 msecs, which is why the times in + // this test are as high as they are. + + CHECK_SOURCE_VALID; + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy timeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->setUpdateInterval(0); + m_source->startUpdates(); + m_source->setUpdateInterval(0); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() > 0) && (spyUse.count() > 0), 7000); + QCOMPARE(timeout.count(), 0); + spyView.clear(); + spyUse.clear(); + + m_source->setUpdateInterval(5000); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 2) && (spyUse.count() == 2) && (timeout.count() == 0), 15000); + spyView.clear(); + spyUse.clear(); + + m_source->setUpdateInterval(10000); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 2) && (spyUse.count() == 2) && (timeout.count() == 0), 30000); + spyView.clear(); + spyUse.clear(); + + m_source->setUpdateInterval(5000); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 2) && (spyUse.count() == 2) && (timeout.count() == 0), 15000); + spyView.clear(); + spyUse.clear(); + + m_source->setUpdateInterval(5000); + + QTRY_VERIFY_WITH_TIMEOUT( (spyView.count() == 2) && (spyUse.count() == 2) && (timeout.count() == 0), 15000); + spyView.clear(); + spyUse.clear(); + + m_source->setUpdateInterval(0); + + QTRY_VERIFY_WITH_TIMEOUT( (spyView.count() > 0 ) && (spyUse.count() > 0) && (timeout.count() == 0), 7000); + spyView.clear(); + spyUse.clear(); + + m_source->setUpdateInterval(0); + + QTRY_VERIFY_WITH_TIMEOUT( (spyView.count() > 0 ) && (spyUse.count() > 0) && (timeout.count() == 0), 7000); + spyView.clear(); + spyUse.clear(); + m_source->stopUpdates(); +} + +void TestQGeoSatelliteInfoSource::startUpdates_testDefaultInterval() +{ + CHECK_SOURCE_VALID; + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy timeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->startUpdates(); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + for (int i = 0; i < 3; i++) { + QTRY_VERIFY_WITH_TIMEOUT( (spyView.count() > 0 ) && (spyUse.count() > 0) && (timeout.count() == 0), 7000); + spyView.clear(); + spyUse.clear(); + } + m_source->stopUpdates(); +} + +void TestQGeoSatelliteInfoSource::startUpdates_testZeroInterval() +{ + CHECK_SOURCE_VALID; + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy timeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->setUpdateInterval(0); + m_source->startUpdates(); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + for (int i = 0; i < 3; i++) { + QTRY_VERIFY_WITH_TIMEOUT( (spyView.count() > 0 ) && (spyUse.count() > 0) && (timeout.count() == 0), 7000); + spyView.clear(); + spyUse.clear(); + } + m_source->stopUpdates(); +} + +void TestQGeoSatelliteInfoSource::startUpdates_moreThanOnce() +{ + CHECK_SOURCE_VALID; + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->setUpdateInterval(0); + m_source->startUpdates(); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + m_source->startUpdates(); // check there is no crash + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() > 0) && (spyUse.count() > 0), MAX_WAITING_TIME); + + m_source->startUpdates(); // check there is no crash + + m_source->stopUpdates(); +} + +void TestQGeoSatelliteInfoSource::stopUpdates() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->setUpdateInterval(10000); + m_source->startUpdates(); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + for (int i = 0; i < 2; i++) { + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1), 12000); + spyView.clear(); + spyUse.clear(); + } + + m_source->stopUpdates(); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 0) && (spyUse.count() == 0), 12000); +} + +void TestQGeoSatelliteInfoSource::stopUpdates_withoutStart() +{ + CHECK_SOURCE_VALID; + + m_source->stopUpdates(); // check there is no crash +} + +void TestQGeoSatelliteInfoSource::requestUpdate() +{ + CHECK_SOURCE_VALID; + + QFETCH(int, timeout); + QSignalSpy spy(m_source, SIGNAL(requestTimeout())); + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->requestUpdate(timeout); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + // Geoclue may deliver update instantly if there is a satellite fix + QTRY_VERIFY_WITH_TIMEOUT(!spy.isEmpty() || !spyView.isEmpty(), 10); +} + +void TestQGeoSatelliteInfoSource::requestUpdate_data() +{ + QTest::addColumn("timeout"); + QTest::newRow("less than zero") << -1; + QTest::newRow("very small timeout") << 1; +} + +void TestQGeoSatelliteInfoSource::requestUpdate_validTimeout() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy spyTimeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->requestUpdate(7000); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + QTRY_VERIFY_WITH_TIMEOUT( + (spyView.count() == 1) && (spyUse.count() == 1 && (spyTimeout.count()) == 0), 7000); +} + +void TestQGeoSatelliteInfoSource::requestUpdate_defaultTimeout() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy spyTimeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->requestUpdate(0); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + QTRY_VERIFY_WITH_TIMEOUT( + (spyView.count() == 1) && (spyUse.count() == 1 && (spyTimeout.count()) == 0), + MAX_WAITING_TIME); +} + +void TestQGeoSatelliteInfoSource::requestUpdate_timeoutLessThanMinimumInterval() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyTimeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->requestUpdate(1); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + QTRY_COMPARE_WITH_TIMEOUT(spyTimeout.count(), 1, 1000); +} + +void TestQGeoSatelliteInfoSource::requestUpdate_repeatedCalls() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->requestUpdate(7000); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1), 7000); + spyView.clear(); + spyUse.clear(); + + m_source->requestUpdate(7000); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1), 7000); +} + +void TestQGeoSatelliteInfoSource::requestUpdate_overlappingCalls() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->requestUpdate(7000); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + m_source->requestUpdate(7000); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1), 7000); +} + +void TestQGeoSatelliteInfoSource::requestUpdate_overlappingCallsWithTimeout() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy spyTimeout(m_source, + SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->requestUpdate(0); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + m_source->requestUpdate(1); + + QTRY_COMPARE_WITH_TIMEOUT(spyTimeout.count(), 0, 7000); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1), 7000); +} + +void TestQGeoSatelliteInfoSource::requestUpdateAfterStartUpdates_ZeroInterval() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy spyTimeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->setUpdateInterval(0); + m_source->startUpdates(); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1), MAX_WAITING_TIME); + spyView.clear(); + spyUse.clear(); + + m_source->requestUpdate(7000); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1) + && (spyTimeout.count() == 0), 7000); + + spyView.clear(); + spyUse.clear(); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1), 12000); + + m_source->stopUpdates(); +} + +void TestQGeoSatelliteInfoSource::requestUpdateAfterStartUpdates_SmallInterval() +{ + CHECK_SOURCE_VALID; + + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy spyTimeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->setUpdateInterval(10000); + m_source->requestUpdate(7000); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + m_source->startUpdates(); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() > 0) && (spyUse.count() > 0) + && (spyTimeout.count() == 0), 7000); + + spyView.clear(); + spyUse.clear(); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() == 1) && (spyUse.count() == 1), 12000); + + m_source->stopUpdates(); +} + +void TestQGeoSatelliteInfoSource::requestUpdateBeforeStartUpdates_ZeroInterval() +{ + CHECK_SOURCE_VALID; + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy timeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->requestUpdate(7000); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + m_source->setUpdateInterval(0); + m_source->startUpdates(); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() >= 2) && (spyUse.count() >= 2) && (timeout.count() == 0), 14000); + spyView.clear(); + spyUse.clear(); + + QTest::qWait(7000); + + QCOMPARE(timeout.count(), 0); + + m_source->stopUpdates(); +} + +void TestQGeoSatelliteInfoSource::requestUpdateBeforeStartUpdates_SmallInterval() +{ + CHECK_SOURCE_VALID; + QSignalSpy spyView(m_source, + SIGNAL(satellitesInViewUpdated(QList))); + QSignalSpy spyUse(m_source, + SIGNAL(satellitesInUseUpdated(QList))); + QSignalSpy timeout(m_source, SIGNAL(requestTimeout())); + QSignalSpy errorSpy(m_source, SIGNAL(error(QGeoSatelliteInfoSource::Error))); + + m_source->requestUpdate(7000); + + if (!errorSpy.isEmpty()) + QSKIP("Error starting satellite updates."); + + m_source->setUpdateInterval(10000); + m_source->startUpdates(); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() > 0) && (spyUse.count() > 0) && (timeout.count() == 0), 7000); + spyView.clear(); + spyUse.clear(); + + QTRY_VERIFY_WITH_TIMEOUT((spyView.count() > 0) && (spyUse.count() > 0) && (timeout.count() == 0), 20000); + + m_source->stopUpdates(); +} + + + +void TestQGeoSatelliteInfoSource::removeSlotForRequestTimeout() +{ + CHECK_SOURCE_VALID; + + bool i = connect(m_source, SIGNAL(requestTimeout()), this, SLOT(test_slot1())); + QVERIFY(i==true); + i = connect(m_source, SIGNAL(requestTimeout()), this, SLOT(test_slot2())); + QVERIFY(i==true); + i = disconnect(m_source, SIGNAL(requestTimeout()), this, SLOT(test_slot1())); + QVERIFY(i==true); + + m_source->requestUpdate(-1); + QTRY_VERIFY_WITH_TIMEOUT((m_testSlot2Called == true), 1000); +} + +void TestQGeoSatelliteInfoSource::removeSlotForSatellitesInUseUpdated() +{ + CHECK_SOURCE_VALID; + + bool i = connect(m_source, SIGNAL(satellitesInUseUpdated(QList)), this, SLOT(test_slot1())); + QVERIFY(i == true); + i = connect(m_source, SIGNAL(satellitesInUseUpdated(QList)), this, SLOT(test_slot2())); + QVERIFY(i == true); + i = disconnect(m_source, SIGNAL(satellitesInUseUpdated(QList)), this, SLOT(test_slot1())); + QVERIFY(i == true); + + m_source->requestUpdate(7000); + + if (m_source->error() != QGeoSatelliteInfoSource::NoError) + QSKIP("Error starting satellite updates."); + + QTRY_VERIFY_WITH_TIMEOUT((m_testSlot2Called == true), 7000); +} + +void TestQGeoSatelliteInfoSource::removeSlotForSatellitesInViewUpdated() +{ + CHECK_SOURCE_VALID; + + bool i = connect(m_source, SIGNAL(satellitesInViewUpdated(QList)), this, SLOT(test_slot1())); + QVERIFY(i == true); + i = connect(m_source, SIGNAL(satellitesInViewUpdated(QList)), this, SLOT(test_slot2())); + QVERIFY(i == true); + i = disconnect(m_source, SIGNAL(satellitesInViewUpdated(QList)), this, SLOT(test_slot1())); + QVERIFY(i == true); + + m_source->requestUpdate(7000); + + if (m_source->error() != QGeoSatelliteInfoSource::NoError) + QSKIP("Error starting satellite updates."); + + QTRY_VERIFY_WITH_TIMEOUT((m_testSlot2Called == true), 7000); +} + +#include "testqgeosatelliteinfosource.moc" diff --git a/tests/auto/qgeosatelliteinfosource/testqgeosatelliteinfosource_p.h b/tests/auto/qgeosatelliteinfosource/testqgeosatelliteinfosource_p.h new file mode 100644 index 0000000..cf15f7c --- /dev/null +++ b/tests/auto/qgeosatelliteinfosource/testqgeosatelliteinfosource_p.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TESTQGEOSATELLITEINFOSOURCE_H +#define TESTQGEOSATELLITEINFOSOURCE_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QGeoSatelliteInfoSource; +QT_END_NAMESPACE + + +class TestQGeoSatelliteInfoSource : public QObject +{ + Q_OBJECT + +public: + TestQGeoSatelliteInfoSource(QObject *parent = 0); + + static TestQGeoSatelliteInfoSource *createDefaultSourceTest(); + +protected: + virtual QGeoSatelliteInfoSource *createTestSource() = 0; + + // MUST be called by subclasses if they override respective test slots + void base_initTestCase(); + void base_init(); + void base_cleanup(); + void base_cleanupTestCase(); + +public slots: + void test_slot1(); + void test_slot2(); + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void cleanupTestCase(); + + void constructor_withParent(); + void constructor_noParent(); + + void updateInterval(); + + void setUpdateInterval(); + void setUpdateInterval_data(); + + void minimumUpdateInterval(); + + void createDefaultSource(); + void createDefaultSource_noParent(); + + void startUpdates_testIntervals(); + void startUpdates_testIntervalChangesWhileRunning(); + void startUpdates_testDefaultInterval(); + void startUpdates_testZeroInterval(); + void startUpdates_moreThanOnce(); + void stopUpdates(); + void stopUpdates_withoutStart(); + + void requestUpdate(); + void requestUpdate_data(); + + void requestUpdate_validTimeout(); + void requestUpdate_defaultTimeout(); + void requestUpdate_timeoutLessThanMinimumInterval(); + void requestUpdate_repeatedCalls(); + void requestUpdate_overlappingCalls(); + void requestUpdate_overlappingCallsWithTimeout(); + + void requestUpdateAfterStartUpdates_ZeroInterval(); + void requestUpdateAfterStartUpdates_SmallInterval(); + void requestUpdateBeforeStartUpdates_ZeroInterval(); + void requestUpdateBeforeStartUpdates_SmallInterval(); + + void removeSlotForRequestTimeout(); + void removeSlotForSatellitesInUseUpdated(); + void removeSlotForSatellitesInViewUpdated(); + +private: + QGeoSatelliteInfoSource *m_source; + bool m_testingDefaultSource; + bool m_testSlot2Called; +}; + +#endif // #ifndef TESTQGEOSATELLITEINFOSOURCE_H diff --git a/tests/auto/qgeosatelliteinfosource/tst_qgeosatelliteinfosource.cpp b/tests/auto/qgeosatelliteinfosource/tst_qgeosatelliteinfosource.cpp new file mode 100644 index 0000000..db8099a --- /dev/null +++ b/tests/auto/qgeosatelliteinfosource/tst_qgeosatelliteinfosource.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "testqgeosatelliteinfosource_p.h" +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + TestQGeoSatelliteInfoSource *test = TestQGeoSatelliteInfoSource::createDefaultSourceTest(); + return QTest::qExec(test, argc, argv); +} diff --git a/tests/auto/qgeoserviceprovider/qgeoserviceprovider.pro b/tests/auto/qgeoserviceprovider/qgeoserviceprovider.pro new file mode 100644 index 0000000..5dfe2dd --- /dev/null +++ b/tests/auto/qgeoserviceprovider/qgeoserviceprovider.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG+=testcase +TARGET=tst_qgeoserviceprovider + +SOURCES += tst_qgeoserviceprovider.cpp + +CONFIG -= app_bundle + +QT += testlib location diff --git a/tests/auto/qgeoserviceprovider/tst_qgeoserviceprovider.cpp b/tests/auto/qgeoserviceprovider/tst_qgeoserviceprovider.cpp new file mode 100644 index 0000000..e33dab3 --- /dev/null +++ b/tests/auto/qgeoserviceprovider/tst_qgeoserviceprovider.cpp @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QGeoServiceProvider : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void tst_availableServiceProvider(); + void tst_features_data(); + void tst_features(); + void tst_misc(); + void tst_nokiaRename(); +}; + +void tst_QGeoServiceProvider::initTestCase() +{ + /* + * Set custom path since CI doesn't install test plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif +} + +void tst_QGeoServiceProvider::tst_availableServiceProvider() +{ + const QStringList provider = QGeoServiceProvider::availableServiceProviders(); + + // Currently provided plugins + if (provider.count() != 7) + qWarning() << provider; + QCOMPARE(provider.count(), 7); + // these providers are deployed + QVERIFY(provider.contains(QStringLiteral("mapbox"))); + QVERIFY(provider.contains(QStringLiteral("here"))); + QVERIFY(provider.contains(QStringLiteral("osm"))); + // these providers exist for unit tests only + QVERIFY(provider.contains(QStringLiteral("geocode.test.plugin"))); + QVERIFY(provider.contains(QStringLiteral("georoute.test.plugin"))); + QVERIFY(provider.contains(QStringLiteral("qmlgeo.test.plugin"))); + QVERIFY(provider.contains(QStringLiteral("test.places.unsupported"))); + +} + +Q_DECLARE_METATYPE(QGeoServiceProvider::MappingFeatures) +Q_DECLARE_METATYPE(QGeoServiceProvider::GeocodingFeatures) +Q_DECLARE_METATYPE(QGeoServiceProvider::RoutingFeatures) +Q_DECLARE_METATYPE(QGeoServiceProvider::PlacesFeatures) + +void tst_QGeoServiceProvider::tst_features_data() +{ + QTest::addColumn("providerName"); + QTest::addColumn("mappingFeatures"); + QTest::addColumn("codingFeatures"); + QTest::addColumn("routingFeatures"); + QTest::addColumn("placeFeatures"); + + QTest::newRow("invalid") << QString("non-existing-provider-name") + << QGeoServiceProvider::MappingFeatures(QGeoServiceProvider::NoMappingFeatures) + << QGeoServiceProvider::GeocodingFeatures(QGeoServiceProvider::NoGeocodingFeatures) + << QGeoServiceProvider::RoutingFeatures(QGeoServiceProvider::NoRoutingFeatures) + << QGeoServiceProvider::PlacesFeatures(QGeoServiceProvider::NoPlacesFeatures); + + QTest::newRow("mapbox") << QString("mapbox") + << QGeoServiceProvider::MappingFeatures(QGeoServiceProvider::OnlineMappingFeature) + << QGeoServiceProvider::GeocodingFeatures(QGeoServiceProvider::NoGeocodingFeatures) + << QGeoServiceProvider::RoutingFeatures(QGeoServiceProvider::NoRoutingFeatures) + << QGeoServiceProvider::PlacesFeatures(QGeoServiceProvider::NoPlacesFeatures); + + QTest::newRow("here") << QString("here") + << QGeoServiceProvider::MappingFeatures(QGeoServiceProvider::OnlineMappingFeature) + << QGeoServiceProvider::GeocodingFeatures(QGeoServiceProvider::OnlineGeocodingFeature + | QGeoServiceProvider::ReverseGeocodingFeature) + << QGeoServiceProvider::RoutingFeatures(QGeoServiceProvider::OnlineRoutingFeature + | QGeoServiceProvider::RouteUpdatesFeature + | QGeoServiceProvider::AlternativeRoutesFeature + | QGeoServiceProvider::ExcludeAreasRoutingFeature) + << QGeoServiceProvider::PlacesFeatures(QGeoServiceProvider::OnlinePlacesFeature + | QGeoServiceProvider::PlaceRecommendationsFeature + | QGeoServiceProvider::SearchSuggestionsFeature + | QGeoServiceProvider::LocalizedPlacesFeature); + + QTest::newRow("osm") << QString("osm") + << QGeoServiceProvider::MappingFeatures(QGeoServiceProvider::OnlineMappingFeature) + << QGeoServiceProvider::GeocodingFeatures(QGeoServiceProvider::OnlineGeocodingFeature + | QGeoServiceProvider::ReverseGeocodingFeature) + << QGeoServiceProvider::RoutingFeatures(QGeoServiceProvider::OnlineRoutingFeature) + << QGeoServiceProvider::PlacesFeatures(QGeoServiceProvider::OnlinePlacesFeature); +} + +void tst_QGeoServiceProvider::tst_features() +{ + QFETCH(QString, providerName); + QFETCH(QGeoServiceProvider::MappingFeatures, mappingFeatures); + QFETCH(QGeoServiceProvider::GeocodingFeatures, codingFeatures); + QFETCH(QGeoServiceProvider::RoutingFeatures, routingFeatures); + QFETCH(QGeoServiceProvider::PlacesFeatures, placeFeatures); + + QGeoServiceProvider provider(providerName); + QCOMPARE(provider.mappingFeatures(), mappingFeatures); + QCOMPARE(provider.geocodingFeatures(), codingFeatures); + QCOMPARE(provider.routingFeatures(), routingFeatures); + QCOMPARE(provider.placesFeatures(), placeFeatures); + + if (provider.mappingFeatures() == QGeoServiceProvider::NoMappingFeatures) { + QVERIFY(provider.mappingManager() == Q_NULLPTR); + } else { + // some plugins require token/access parameter + // they return 0 but set QGeoServiceProvider::MissingRequiredParameterError + if (provider.mappingManager() != Q_NULLPTR) + QCOMPARE(provider.error(), QGeoServiceProvider::NoError); + else + QCOMPARE(provider.error(), QGeoServiceProvider::MissingRequiredParameterError); + } + + if (provider.geocodingFeatures() == QGeoServiceProvider::NoGeocodingFeatures) { + QVERIFY(provider.geocodingManager() == Q_NULLPTR); + } else { + if (provider.geocodingManager() != Q_NULLPTR) + QVERIFY(provider.geocodingManager() != Q_NULLPTR); //pointless but we want a VERIFY here + else + QCOMPARE(provider.error(), QGeoServiceProvider::MissingRequiredParameterError); + } + + if (provider.routingFeatures() == QGeoServiceProvider::NoRoutingFeatures) { + QVERIFY(provider.routingManager() == Q_NULLPTR); + } else { + if (provider.routingManager() != Q_NULLPTR) + QCOMPARE(provider.error(), QGeoServiceProvider::NoError); + else + QCOMPARE(provider.error(), QGeoServiceProvider::MissingRequiredParameterError); + } + + if (provider.placesFeatures() == QGeoServiceProvider::NoPlacesFeatures) { + QVERIFY(provider.placeManager() == Q_NULLPTR); + } else { + if (provider.placeManager() != Q_NULLPTR) + QCOMPARE(provider.error(), QGeoServiceProvider::NoError); + else + QCOMPARE(provider.error(), QGeoServiceProvider::MissingRequiredParameterError); + } +} + +void tst_QGeoServiceProvider::tst_misc() +{ + const QStringList provider = QGeoServiceProvider::availableServiceProviders(); + QVERIFY(provider.contains(QStringLiteral("osm"))); + QVERIFY(provider.contains(QStringLiteral("geocode.test.plugin"))); + + QGeoServiceProvider test_experimental( + QStringLiteral("geocode.test.plugin"), QVariantMap(), true); + QGeoServiceProvider test_noexperimental( + QStringLiteral("geocode.test.plugin"), QVariantMap(), false); + QCOMPARE(test_experimental.error(), QGeoServiceProvider::NoError); + QCOMPARE(test_noexperimental.error(), QGeoServiceProvider::NotSupportedError); + + QGeoServiceProvider osm_experimental( + QStringLiteral("osm"), QVariantMap(), true); + QGeoServiceProvider osm_noexperimental( + QStringLiteral("osm"), QVariantMap(), false); + QCOMPARE(osm_experimental.error(), QGeoServiceProvider::NoError); + QCOMPARE(osm_noexperimental.error(), QGeoServiceProvider::NoError); +} + +void tst_QGeoServiceProvider::tst_nokiaRename() +{ + // The "nokia" plugin was renamed to "here". + // It remains available under the name "nokia" for now + // but is not advertised via QGeoServiceProvider::availableServiceProviders() + + QVERIFY(!QGeoServiceProvider::availableServiceProviders().contains("nokia")); + QGeoServiceProvider provider(QStringLiteral("nokia")); + QCOMPARE(provider.error(), QGeoServiceProvider::NoError); + +} + +QTEST_GUILESS_MAIN(tst_QGeoServiceProvider) + +#include "tst_qgeoserviceprovider.moc" diff --git a/tests/auto/qgeoshape/qgeoshape.pro b/tests/auto/qgeoshape/qgeoshape.pro new file mode 100644 index 0000000..dd55138 --- /dev/null +++ b/tests/auto/qgeoshape/qgeoshape.pro @@ -0,0 +1,5 @@ +load(testcase) +TARGET = tst_qgeoshape +QT += testlib positioning +SOURCES = \ + tst_qgeoshape.cpp diff --git a/tests/auto/qgeoshape/tst_qgeoshape.cpp b/tests/auto/qgeoshape/tst_qgeoshape.cpp new file mode 100644 index 0000000..72611ff --- /dev/null +++ b/tests/auto/qgeoshape/tst_qgeoshape.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +QString tst_qgeoshape_debug; + +void tst_qgeoshape_messageHandler(QtMsgType type, const QMessageLogContext&, + const QString &msg) +{ + switch (type) { + case QtDebugMsg : + tst_qgeoshape_debug = msg; + break; + default: + break; + } +} + +class tst_qgeoshape : public QObject +{ + Q_OBJECT + +private slots: + void testArea(); + void debug_data(); + void debug(); + void conversions(); +}; + +void tst_qgeoshape::testArea() +{ + QGeoShape area; + QVERIFY(!area.isValid()); + QVERIFY(area.isEmpty()); + QCOMPARE(area.type(), QGeoShape::UnknownType); + QVERIFY(!area.contains(QGeoCoordinate())); + + // QGeoShape never constructs a QGeoShapePrivate. Hence d_ptr is always 0. + + QGeoShape area2; + + QCOMPARE(area, area2); + + area = area2; + + QCOMPARE(area, area2); + + QGeoShape area3(area2); + + QCOMPARE(area2, area3); +} + +void tst_qgeoshape::debug_data() +{ + QTest::addColumn("shape"); + QTest::addColumn("nextValue"); + QTest::addColumn("debugString"); + + QTest::newRow("uninitialized") << QGeoShape() << 45 + << QString("QGeoShape(Unknown) 45"); + QTest::newRow("uninitialized") << QGeoShape(QGeoRectangle()) << 45 + << QString("QGeoShape(Rectangle) 45"); + QTest::newRow("uninitialized") << QGeoShape(QGeoCircle()) << 45 + << QString("QGeoShape(Circle) 45"); +} + + +void tst_qgeoshape::debug() +{ + QFETCH(QGeoShape, shape); + QFETCH(int, nextValue); + QFETCH(QString, debugString); + + qInstallMessageHandler(tst_qgeoshape_messageHandler); + qDebug() << shape << nextValue; + qInstallMessageHandler(0); + QCOMPARE(tst_qgeoshape_debug, debugString); +} + +void tst_qgeoshape::conversions() +{ + QVariant varShape = QVariant::fromValue(QGeoShape()); + QVariant varRect = QVariant::fromValue(QGeoRectangle( + QGeoCoordinate(1, 1), + QGeoCoordinate(2, 2))); + QVariant varCircle = QVariant::fromValue(QGeoCircle(QGeoCoordinate(3, 3), 1000)); + + QVERIFY(varShape.canConvert()); + QVERIFY(varShape.canConvert()); + QVERIFY(varShape.canConvert()); + QVERIFY(!varRect.canConvert()); + QVERIFY(varRect.canConvert()); + QVERIFY(varRect.canConvert()); + QVERIFY(varCircle.canConvert()); + QVERIFY(!varCircle.canConvert()); + QVERIFY(varCircle.canConvert()); +} + +QTEST_MAIN(tst_qgeoshape) +#include "tst_qgeoshape.moc" diff --git a/tests/auto/qgeotiledmap/qgeotiledmap.pro b/tests/auto/qgeotiledmap/qgeotiledmap.pro new file mode 100644 index 0000000..b485022 --- /dev/null +++ b/tests/auto/qgeotiledmap/qgeotiledmap.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qgeotiledmap +INCLUDEPATH += ../geotestplugin + +SOURCES += tst_qgeotiledmap.cpp + +QT += location-private positioning-private testlib +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/qgeotiledmap/tst_qgeotiledmap.cpp b/tests/auto/qgeotiledmap/tst_qgeotiledmap.cpp new file mode 100644 index 0000000..e7026cf --- /dev/null +++ b/tests/auto/qgeotiledmap/tst_qgeotiledmap.cpp @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeotiledmap_test.h" +#include "qgeotilefetcher_test.h" +#include "qgeotiledmappingmanagerengine_test.h" +#include +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QGeoTiledMap::PrefetchStyle) + +class FetchTileCounter: public QObject +{ + Q_OBJECT +public Q_SLOTS: + void tileFetched(const QGeoTileSpec& spec) { + m_tiles << spec; + } +public: + QSet m_tiles; +}; + +class tst_QGeoTiledMap : public QObject +{ + Q_OBJECT + +public: + tst_QGeoTiledMap(); + ~tst_QGeoTiledMap(); + +private: + void waitForFetch(int count); + +private Q_SLOTS: + void initTestCase(); + void fetchTiles(); + void fetchTiles_data(); + +private: + QScopedPointer m_map; + QScopedPointer m_tilesCounter; + QGeoTileFetcherTest *m_fetcher; + +}; + +tst_QGeoTiledMap::tst_QGeoTiledMap(): + m_fetcher(0) +{ +} + +tst_QGeoTiledMap::~tst_QGeoTiledMap() +{ +} + +void tst_QGeoTiledMap::initTestCase() +{ + // Set custom path since CI doesn't install test plugins +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif + QVariantMap parameters; + parameters["tileSize"] = 16; + parameters["maxZoomLevel"] = 8; + parameters["finishRequestImmediately"] = true; + QGeoServiceProvider *provider = new QGeoServiceProvider("qmlgeo.test.plugin",parameters); + provider->setAllowExperimental(true); + QGeoMappingManager *mappingManager = provider->mappingManager(); + QVERIFY2(provider->error() == QGeoServiceProvider::NoError, "Could not load plugin: " + provider->errorString().toLatin1()); + m_map.reset(static_cast(mappingManager->createMap(this))); + QVERIFY(m_map); + m_map->setSize(QSize(16, 16)); + m_fetcher = static_cast(m_map->m_engine->tileFetcher()); + m_tilesCounter.reset(new FetchTileCounter()); + connect(m_fetcher, SIGNAL(tileFetched(const QGeoTileSpec&)), m_tilesCounter.data(), SLOT(tileFetched(const QGeoTileSpec&))); +} + +void tst_QGeoTiledMap::fetchTiles() +{ + QFETCH(double, zoomLevel); + QFETCH(int, visibleCount); + QFETCH(int, prefetchCount); + QFETCH(QGeoTiledMap::PrefetchStyle, style); + QFETCH(int, nearestNeighbourLayer); + + m_map->setPrefetchStyle(style); + + QGeoCameraData camera; + camera.setCenter(QGeoProjection::mercatorToCoord(QDoubleVector2D( 0.5 , 0.5 ))); + + //prev_visible + camera.setZoomLevel(zoomLevel-1); + m_map->clearData(); + m_tilesCounter->m_tiles.clear(); + m_map->setCameraData(camera); + waitForFetch(visibleCount); + QSet prev_visible = m_tilesCounter->m_tiles; + + //visible + prefetch + camera.setZoomLevel(zoomLevel); + m_map->clearData(); + m_tilesCounter->m_tiles.clear(); + m_map->setCameraData(camera); + waitForFetch(visibleCount); + QSet visible = m_tilesCounter->m_tiles; + m_map->clearData(); + m_tilesCounter->m_tiles.clear(); + m_map->prefetchData(); + waitForFetch(prefetchCount); + QSet prefetched = m_tilesCounter->m_tiles; + + //next visible + camera.setZoomLevel(zoomLevel + 1); + m_map->clearData(); + m_tilesCounter->m_tiles.clear(); + m_map->setCameraData(camera); + waitForFetch(visibleCount); + QSet next_visible = m_tilesCounter->m_tiles; + + QVERIFY2(visibleCount == visible.size(), "visible count incorrect"); + QVERIFY2(prefetchCount == prefetched.size(), "prefetch count incorrect"); + QSetIterator i(visible); + while (i.hasNext()) + QVERIFY2(prefetched.contains(i.next()),"visible tile missing from prefetched tiles"); + + //for zoomLevels wihtout fractions more tiles are fetched for current zoomlevel due to ViewExpansion + if (qCeil(zoomLevel) != zoomLevel && style == QGeoTiledMap::PrefetchNeighbourLayer && nearestNeighbourLayer < zoomLevel) + QVERIFY2(prefetched == prev_visible + visible, "wrongly prefetched tiles"); + + if (qCeil(zoomLevel) != zoomLevel && style == QGeoTiledMap::PrefetchNeighbourLayer && nearestNeighbourLayer > zoomLevel) + QVERIFY2(prefetched == next_visible + visible, "wrongly prefetched tiles"); + + if (qCeil(zoomLevel) != zoomLevel && style == QGeoTiledMap::PrefetchTwoNeighbourLayers) + QVERIFY2(prefetched == prev_visible + visible + next_visible, "wrongly prefetched tiles"); +} + +void tst_QGeoTiledMap::fetchTiles_data() +{ + QTest::addColumn("zoomLevel"); + QTest::addColumn("visibleCount"); + QTest::addColumn("prefetchCount"); + QTest::addColumn("style"); + QTest::addColumn("nearestNeighbourLayer"); + QTest::newRow("zoomLevel: 4 , visible count: 4 : prefetch count: 16") << 4.0 << 4 << 4 + 16 << QGeoTiledMap::PrefetchNeighbourLayer << 3; + QTest::newRow("zoomLevel: 4.06 , visible count: 4 : prefetch count: 4") << 4.06 << 4 << 4 + 4 << QGeoTiledMap::PrefetchNeighbourLayer << 3; + QTest::newRow("zoomLevel: 4.1 , visible count: 4 : prefetch count: 4") << 4.1 << 4 << 4 + 4 << QGeoTiledMap::PrefetchNeighbourLayer << 3; + QTest::newRow("zoomLevel: 4.5 , visible count: 4 : prefetch count: 4") << 4.5 << 4 << 4 + 4 << QGeoTiledMap::PrefetchNeighbourLayer << 3; + QTest::newRow("zoomLevel: 4.6 , visible count: 4 : prefetch count: 4") << 4.6 << 4 << 4 + 4 << QGeoTiledMap::PrefetchNeighbourLayer << 5; + QTest::newRow("zoomLevel: 4.9 , visible count: 4 : prefetch count: 4") << 4.9 << 4 <<4 + 4 << QGeoTiledMap::PrefetchNeighbourLayer << 5; + QTest::newRow("zoomLevel: 4 , visible count: 4 : prefetch count: 4") << 4.0 << 4 << 16 + 4 + 4 << QGeoTiledMap::PrefetchTwoNeighbourLayers << 3; + QTest::newRow("zoomLevel: 4.1 , visible count: 4 : prefetch count: 4") << 4.1 << 4 << 4 + 4 + 4 << QGeoTiledMap::PrefetchTwoNeighbourLayers << 3; + QTest::newRow("zoomLevel: 4.6 ,visible count: 4 : prefetch count: 4") << 4.6 << 4 << 4 + 4 + 4 << QGeoTiledMap::PrefetchTwoNeighbourLayers << 5; +} + +void tst_QGeoTiledMap::waitForFetch(int count) +{ + int timeout = 0; + while (m_tilesCounter->m_tiles.count() < count && timeout < count) { + //250ms for each tile fetch + QTest::qWait(250); + timeout++; + } +} + +QTEST_MAIN(tst_QGeoTiledMap) + +#include "tst_qgeotiledmap.moc" diff --git a/tests/auto/qgeotiledmapscene/qgeotiledmapscene.pro b/tests/auto/qgeotiledmapscene/qgeotiledmapscene.pro new file mode 100644 index 0000000..c3ec6e9 --- /dev/null +++ b/tests/auto/qgeotiledmapscene/qgeotiledmapscene.pro @@ -0,0 +1,8 @@ +CONFIG += testcase +TARGET = tst_qgeotiledmapscene + +INCLUDEPATH += ../../../src/location/maps + +SOURCES += tst_qgeotiledmapscene.cpp + +QT += location positioning-private testlib diff --git a/tests/auto/qgeotiledmapscene/tst_qgeotiledmapscene.cpp b/tests/auto/qgeotiledmapscene/tst_qgeotiledmapscene.cpp new file mode 100644 index 0000000..3d43ebd --- /dev/null +++ b/tests/auto/qgeotiledmapscene/tst_qgeotiledmapscene.cpp @@ -0,0 +1,384 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location/maps + +#include "qgeotilespec_p.h" +#include "qgeotiledmapscene_p.h" +#include "qgeocameratiles_p.h" +#include "qgeocameradata_p.h" +#include "qabstractgeotilecache_p.h" + +#include +#include + +#include + +#include +#include +#include + +#include + +QT_USE_NAMESPACE + +class tst_QGeoTiledMapScene : public QObject +{ + Q_OBJECT + + private: + void row(QString name, double screenX, double screenY, double cameraCenterX, double cameraCenterY, + double zoom, int tileSize, int screenWidth, int screenHeight, double mercatorX, double mercatorY){ + + // expected behaviour of wrapping + if (mercatorX <= 0.0) + mercatorX += 1.0; + else if (mercatorX > 1.0) + mercatorX -= 1.0; + + QTest::newRow(qPrintable(name)) + << screenX << screenY + << cameraCenterX << cameraCenterY + << zoom << tileSize + << screenWidth << screenHeight + << mercatorX + << mercatorY; + } + + void screenPositions(QString name, double cameraCenterX, double cameraCenterY, double zoom, + int tileSize, int screenWidth, int screenHeight) + { + double screenX; + double screenY; + double mercatorX; + double mercatorY; + + double halfLength = 1 / (std::pow(2.0, zoom) * 2); + double scaleX = screenWidth / tileSize; + double scaleY = screenHeight / tileSize; + double scaledHalfLengthX = halfLength * scaleX; + double scaledHalfLengthY = halfLength * scaleY; + + // top left + screenX = 0.0; + screenY = 1.0 * screenHeight; + mercatorX = cameraCenterX - scaledHalfLengthX; + mercatorY = cameraCenterY + scaledHalfLengthY; + row (name + QString("_topLeftScreen"), screenX, screenY, cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight, mercatorX, mercatorY); + + // top + screenX = 0.5 * screenWidth; + screenY = 1.0 * screenHeight; + mercatorX = cameraCenterX; + mercatorY = cameraCenterY + scaledHalfLengthY; + row (name + QString("_topScreen"), screenX, screenY, cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight, mercatorX, mercatorY); + + // top right + screenX = 1.0 * screenWidth; + screenY = 1.0 * screenHeight; + mercatorX = cameraCenterX + scaledHalfLengthX; + mercatorY = cameraCenterY + scaledHalfLengthY; + row (name + QString("_topRightScreen"), screenX, screenY, cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight, mercatorX, mercatorY); + + // left + screenX = 0.0 * screenWidth; + screenY = 0.5 * screenHeight; + mercatorX = cameraCenterX - scaledHalfLengthX; + mercatorY = cameraCenterY; + row (name + QString("_leftScreen"), screenX, screenY, cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight, mercatorX, mercatorY); + + // center + screenX = 0.5 * screenWidth; + screenY = 0.5 * screenHeight; + mercatorX = cameraCenterX; + mercatorY = cameraCenterY; + row (name + QString("_centerScreen"), screenX, screenY, cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight, mercatorX, mercatorY); + + // right + screenX = 1.0 * screenWidth; + screenY = 0.5 * screenHeight; + mercatorX = cameraCenterX + scaledHalfLengthX; + mercatorY = cameraCenterY; + row (name + QString("_rightScreen"), screenX, screenY, cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight, mercatorX, mercatorY); + + // bottom left + screenX = 0.0; + screenY = 0.0; + mercatorX = cameraCenterX - scaledHalfLengthX; + mercatorY = cameraCenterY - scaledHalfLengthY; + row (name + QString("_bottomLeftrScreen"), screenX, screenY, cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight, mercatorX, mercatorY); + + // bottom + screenX = 0.5 * screenWidth; + screenY = 0.0; + mercatorX = cameraCenterX; + mercatorY = cameraCenterY - scaledHalfLengthY; + row (name + QString("_bottomScreen"), screenX, screenY, cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight, mercatorX, mercatorY); + + // bottom right + screenX = 1.0 * screenWidth; + screenY = 0.0; + mercatorX = cameraCenterX + scaledHalfLengthX; + mercatorY = cameraCenterY - scaledHalfLengthY; + row (name + QString("_bottomRightScreen"), screenX, screenY, cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight, mercatorX, mercatorY); + + } + + void screenCameraPositions(QString name, double zoom, int tileSize, + int screenWidth, int screenHeight) + { + double cameraCenterX; + double cameraCenterY; + + // top left + cameraCenterX = 0; + cameraCenterY = 1.0; + screenPositions(name + QString("_topLeftCamera"), cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight); + + // top + cameraCenterX = 0.5; + cameraCenterY = 1.0; + screenPositions(name + QString("_topCamera"), cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight); + // top right + cameraCenterX = 1.0; + cameraCenterY = 1.0; + screenPositions(name + QString("_topRightCamera"), cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight); + // left + cameraCenterX = 0.0; + cameraCenterY = 0.5; + screenPositions(name + QString("_leftCamera"), cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight); + // middle + cameraCenterX = 0.5; + cameraCenterY = 0.5; + screenPositions(name + QString("_middleCamera"), cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight); + // right + cameraCenterX = 1.0; + cameraCenterY = 0.5; + screenPositions(name + QString("_rightCamera"), cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight); + // bottom left + cameraCenterX = 0.0; + cameraCenterY = 0.0; + screenPositions(name + QString("_bottomLeftCamera"), cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight); + // bottom + cameraCenterX = 0.5; + cameraCenterY = 0.0; + screenPositions(name + QString("_bottomCamera"), cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight); + // bottom right + cameraCenterX = 1.0; + cameraCenterY = 0.0; + screenPositions(name + QString("_bottomRightCamera"), cameraCenterX, cameraCenterY, + zoom, tileSize, screenWidth, screenHeight); + } + + void populateScreenMercatorData(){ + QTest::addColumn("screenX"); + QTest::addColumn("screenY"); + QTest::addColumn("cameraCenterX"); + QTest::addColumn("cameraCenterY"); + QTest::addColumn("zoom"); + QTest::addColumn("tileSize"); + QTest::addColumn("screenWidth"); + QTest::addColumn("screenHeight"); + QTest::addColumn("mercatorX"); + QTest::addColumn("mercatorY"); + + int tileSize; + double zoom; + int screenWidth; + int screenHeight; + QString name; + tileSize = 16; + zoom = 4.0; + + /* + ScreenWidth = t + ScreenHeight = t + */ + screenWidth = tileSize; + screenHeight = tileSize; + name = QString("_(t x t)"); + screenCameraPositions(name, zoom, tileSize, screenWidth, screenHeight); + + /* + ScreenWidth = t * 2 + ScreenHeight = t + */ + screenWidth = tileSize * 2; + screenHeight = tileSize; + name = QString("_(2t x t)"); + screenCameraPositions(name, zoom, tileSize, screenWidth, screenHeight); + + /* + ScreenWidth = t + ScreenHeight = t * 2 + */ + screenWidth = tileSize; + screenHeight = tileSize * 2; + name = QString("_(2t x t)"); + screenCameraPositions(name, zoom, tileSize, screenWidth, screenHeight); + + /* + Screen Width = t * 2 + Screen Height = t * 2 + */ + screenWidth = tileSize * 2; + screenHeight = tileSize * 2; + name = QString("_(2t x 2t)"); + screenCameraPositions(name, zoom, tileSize, screenWidth, screenHeight); + } + + private slots: + + void useVerticalLock(){ + QGeoCameraData camera; + camera.setZoomLevel(4.0); + camera.setCenter(QGeoProjection::mercatorToCoord(QDoubleVector2D(0.0, 0.0))); + + QGeoCameraTiles ct; + ct.setTileSize(16); + ct.setCameraData(camera); + ct.setScreenSize(QSize(16,16)); + + QGeoTiledMapScene mapScene; + mapScene.setTileSize(16); + mapScene.setScreenSize(QSize(16,16*32)); + mapScene.setCameraData(camera); + QVERIFY(!mapScene.verticalLock()); + mapScene.setUseVerticalLock(true); + mapScene.setVisibleTiles(ct.createTiles()); + QVERIFY(mapScene.verticalLock()); + + // Test the case when setting vertical lock has no effect + QGeoTiledMapScene mapScene2; + mapScene2.setTileSize(16); + mapScene2.setScreenSize(QSize(16,16)); + mapScene2.setCameraData(camera); + QVERIFY(!mapScene2.verticalLock()); + mapScene2.setUseVerticalLock(true); + mapScene2.setVisibleTiles(ct.createTiles()); + QVERIFY(!mapScene2.verticalLock()); + } + + void screenToMercatorPositions(){ + QFETCH(double, screenX); + QFETCH(double, screenY); + QFETCH(double, cameraCenterX); + QFETCH(double, cameraCenterY); + QFETCH(double, zoom); + QFETCH(int, tileSize); + QFETCH(int, screenWidth); + QFETCH(int, screenHeight); + QFETCH(double, mercatorX); + QFETCH(double, mercatorY); + + QGeoCameraData camera; + camera.setZoomLevel(zoom); + camera.setCenter(QGeoProjection::mercatorToCoord(QDoubleVector2D(cameraCenterX, cameraCenterY))); + + QGeoCameraTiles ct; + ct.setTileSize(tileSize); + ct.setCameraData(camera); + ct.setScreenSize(QSize(screenWidth,screenHeight)); + + QGeoTiledMapScene mapGeometry; + mapGeometry.setTileSize(tileSize); + mapGeometry.setScreenSize(QSize(screenWidth,screenHeight)); + mapGeometry.setCameraData(camera); + mapGeometry.setVisibleTiles(ct.createTiles()); + + QDoubleVector2D point(screenX,screenY); + QDoubleVector2D mecartorPos = mapGeometry.itemPositionToMercator(point); + + QCOMPARE(mecartorPos.x(),mercatorX); + QCOMPARE(mecartorPos.y(),mercatorY); + } + + void screenToMercatorPositions_data() + { + populateScreenMercatorData(); + } + + void mercatorToScreenPositions(){ + QFETCH(double, screenX); + QFETCH(double, screenY); + QFETCH(double, cameraCenterX); + QFETCH(double, cameraCenterY); + QFETCH(double, zoom); + QFETCH(int, tileSize); + QFETCH(int, screenWidth); + QFETCH(int, screenHeight); + QFETCH(double, mercatorX); + QFETCH(double, mercatorY); + + QGeoCameraData camera; + camera.setZoomLevel(zoom); + camera.setCenter(QGeoProjection::mercatorToCoord(QDoubleVector2D(cameraCenterX, cameraCenterY))); + + QGeoCameraTiles ct; + ct.setTileSize(tileSize); + ct.setCameraData(camera); + ct.setScreenSize(QSize(screenWidth,screenHeight)); + + QGeoTiledMapScene mapGeometry; + mapGeometry.setTileSize(tileSize); + mapGeometry.setScreenSize(QSize(screenWidth,screenHeight)); + mapGeometry.setCameraData(camera); + mapGeometry.setVisibleTiles(ct.createTiles()); + + QDoubleVector2D mercatorPos(mercatorX, mercatorY); + QPointF point = mapGeometry.mercatorToItemPosition(mercatorPos).toPointF(); + + QCOMPARE(point.x(), screenX); + QCOMPARE(point.y(), screenY); + } + + void mercatorToScreenPositions_data(){ + populateScreenMercatorData(); + } + +}; + +QTEST_GUILESS_MAIN(tst_QGeoTiledMapScene) +#include "tst_qgeotiledmapscene.moc" diff --git a/tests/auto/qgeotilespec/qgeotilespec.pro b/tests/auto/qgeotilespec/qgeotilespec.pro new file mode 100644 index 0000000..eb48aea --- /dev/null +++ b/tests/auto/qgeotilespec/qgeotilespec.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qgeotilespec + +INCLUDEPATH += ../../../src/location/maps + +SOURCES += tst_qgeotilespec.cpp + +QT += location testlib diff --git a/tests/auto/qgeotilespec/tst_qgeotilespec.cpp b/tests/auto/qgeotilespec/tst_qgeotilespec.cpp new file mode 100644 index 0000000..84df268 --- /dev/null +++ b/tests/auto/qgeotilespec/tst_qgeotilespec.cpp @@ -0,0 +1,319 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qgeotilespec_p.h" + +QT_USE_NAMESPACE + +class tst_QGeoTileSpec : public QObject +{ + Q_OBJECT + +public: + tst_QGeoTileSpec(); + +private: + void populateGeoTileSpecData(); + +private Q_SLOTS: + void constructorTest_data(); + void constructorTest(); + void pluginTest(); + void zoomTest(); + void xTest(); + void yTest(); + void mapIdTest(); + void assignsOperatorTest_data(); + void assignsOperatorTest(); + void equalsOperatorTest_data(); + void equalsOperatorTest(); + void lessThanOperatorTest_data(); + void lessThanOperatorTest(); + void qHashTest_data(); + void qHashTest(); +}; + +tst_QGeoTileSpec::tst_QGeoTileSpec() +{ +} + +void tst_QGeoTileSpec::populateGeoTileSpecData(){ + QTest::addColumn("plugin"); + QTest::addColumn("mapId"); + QTest::addColumn("zoom"); + QTest::addColumn("x"); + QTest::addColumn("y"); + QTest::newRow("zeros") << QString() << 0 << 0 << 0 << 0; + QTest::newRow("valid") << QString("geo plugin") << 455 << 1 << 20 << 50; + QTest::newRow("negative values") << QString("geo plugin negative") << -350 << 2 << -20 << -50; +} + +void tst_QGeoTileSpec::constructorTest_data() +{ + populateGeoTileSpecData(); +} + +void tst_QGeoTileSpec::constructorTest() +{ + QFETCH(QString,plugin); + QFETCH(int,zoom); + QFETCH(int,mapId); + QFETCH(int,x); + QFETCH(int,y); + + // test constructor copy with default values + QGeoTileSpec testObj; + QGeoTileSpec testObj2(testObj); + QCOMPARE(testObj.plugin(), testObj2.plugin()); + QCOMPARE(testObj.mapId(), testObj2.mapId()); + QCOMPARE(testObj.zoom(), testObj2.zoom()); + QCOMPARE(testObj.x(), testObj2.x()); + QCOMPARE(testObj.y(), testObj2.y()); + + // test second construct + QGeoTileSpec testObj3(plugin, mapId, zoom, x, y); + QCOMPARE(testObj3.plugin(), plugin); + QCOMPARE(testObj3.mapId(), mapId); + QCOMPARE(testObj3.zoom(), zoom); + QCOMPARE(testObj3.x(), x); + QCOMPARE(testObj3.y(), y); +} + +void tst_QGeoTileSpec::pluginTest() +{ + QGeoTileSpec tileSpec; + QCOMPARE(tileSpec.plugin(), QString()); + + QGeoTileSpec tileSpec2(QString("plugin test"),1,10,10,5); + QCOMPARE(tileSpec2.plugin(), QString("plugin test")); +} + +void tst_QGeoTileSpec::zoomTest() +{ + QGeoTileSpec tileSpec; + QVERIFY(tileSpec.zoom() == -1); + tileSpec.setZoom(1); + QVERIFY(tileSpec.zoom() == 1); + + QGeoTileSpec tileSpec2 = tileSpec; + QVERIFY(tileSpec2.zoom() == 1); + tileSpec.setZoom(2); + QVERIFY(tileSpec2.zoom() == 1); +} + +void tst_QGeoTileSpec::xTest() +{ + QGeoTileSpec tileSpec; + QVERIFY(tileSpec.x() == -1); + tileSpec.setX(10); + QVERIFY(tileSpec.x() == 10); + + QGeoTileSpec tileSpec2 = tileSpec; + QVERIFY(tileSpec2.x() == 10); + tileSpec.setX(30); + QVERIFY(tileSpec2.x() == 10); +} + +void tst_QGeoTileSpec::yTest() +{ + QGeoTileSpec tileSpec; + QVERIFY(tileSpec.y() == -1); + tileSpec.setY(20); + QVERIFY(tileSpec.y() == 20); + + QGeoTileSpec tileSpec2 = tileSpec; + QVERIFY(tileSpec2.y() == 20); + tileSpec.setY(40); + QVERIFY(tileSpec2.y() == 20); +} + +void tst_QGeoTileSpec::mapIdTest() +{ + QGeoTileSpec tileSpec; + QVERIFY(tileSpec.mapId() == 0); + tileSpec.setMapId(1); + QVERIFY(tileSpec.mapId() == 1); + + QGeoTileSpec tileSpec2 = tileSpec; + QVERIFY(tileSpec2.mapId() == 1); + tileSpec.setMapId(5); + QVERIFY(tileSpec2.mapId() == 1); +} + +void tst_QGeoTileSpec::assignsOperatorTest_data() +{ + populateGeoTileSpecData(); +} + + +void tst_QGeoTileSpec::assignsOperatorTest() +{ + QFETCH(QString,plugin); + QFETCH(int,mapId); + QFETCH(int,zoom); + QFETCH(int,x); + QFETCH(int,y); + + QGeoTileSpec testObj(plugin, mapId, zoom, x, y); + QGeoTileSpec testObj2; + testObj2 = testObj; + // test the correctness of the asignment operator + QVERIFY2(testObj2.plugin() == plugin, "Plugin not copied correctly"); + QVERIFY2(testObj2.zoom() == zoom, "Zoom not copied correctly"); + QVERIFY2(testObj2.mapId() == mapId, "Map Id not copied correctly"); + QVERIFY2(testObj2.x() == x, "X not copied correctly"); + QVERIFY2(testObj2.y() == y, "Y not copied correctly"); + // verify that values have not changed after an assignment + QVERIFY2(testObj.plugin() == testObj2.plugin(), "Plugin not copied correctly"); + QVERIFY2(testObj.zoom() == testObj2.zoom(), "Zoom not copied correctly"); + QVERIFY2(testObj.mapId() == testObj2.mapId(), "Map Id not copied correctly"); + QVERIFY2(testObj.x() == testObj2.x(), "X not copied correctly"); + QVERIFY2(testObj.y() == testObj2.y(), "Y not copied correctly"); +} + + +void tst_QGeoTileSpec::equalsOperatorTest_data() +{ + populateGeoTileSpecData(); +} + +void tst_QGeoTileSpec::equalsOperatorTest() +{ + QFETCH(QString,plugin); + QFETCH(int,mapId); + QFETCH(int,zoom); + QFETCH(int,x); + QFETCH(int,y); + + QGeoTileSpec testObj(plugin, mapId, zoom, x, y); + QGeoTileSpec testObj2(plugin, mapId, zoom, x, y); + QVERIFY2(testObj == testObj2, "Equals operator is not correct"); + + // test QGeoTileSpec pairs where they differ in one field + testObj2.setZoom(zoom+1); + QVERIFY2(!(testObj == testObj2), "Equals operator is not correct"); + testObj2 = testObj; + testObj2.setMapId(mapId+1); + QVERIFY2(!(testObj == testObj2), "Equals operator is not correct"); + testObj2 = testObj; + testObj2.setX(x+1); + QVERIFY2(!(testObj == testObj2), "Equals operator is not correct"); + testObj2 = testObj; + testObj2.setY(y+1); + QVERIFY2(!(testObj == testObj2), "Equals operator is not correct"); +} + +void tst_QGeoTileSpec::lessThanOperatorTest_data() +{ + populateGeoTileSpecData(); +} + +void tst_QGeoTileSpec::lessThanOperatorTest() +{ + QFETCH(QString,plugin); + QFETCH(int,mapId); + QFETCH(int,zoom); + QFETCH(int,x); + QFETCH(int,y); + + QGeoTileSpec testObj(plugin, mapId, zoom, x, y); + QGeoTileSpec testObj2(testObj); + QVERIFY(!(testObj < testObj2)); + + testObj2.setMapId(mapId-1); + QVERIFY2(testObj2 < testObj, "Less than operator is not correct for mapId"); + testObj2 = testObj; + testObj2.setZoom(zoom-1); + QVERIFY2(testObj2 < testObj, "Less than operator is not correct for zoom"); + testObj2 = testObj; + testObj2.setX(x-1); + QVERIFY2(testObj2 < testObj, "Less than operator is not correct for x"); + testObj2 = testObj; + testObj2.setY(y-1); + QVERIFY2(testObj2 < testObj, "Less than operator is not correct for y"); + + // less than comparisons are done in the order: plugin -> mapId -> zoom -> x -> y + // the test below checks if the order is correct + QGeoTileSpec testObj3(plugin + QString('a'), mapId-1, zoom-1, x-1, y-1); + QVERIFY2(testObj < testObj3, "Order of less than operator is not correct"); + QGeoTileSpec testObj4(plugin, mapId+1, zoom-1, x-1, y-1); + QVERIFY2(testObj < testObj4, "Order of less than operator is not correct"); + QGeoTileSpec testObj5(plugin, mapId, zoom+1, x-1, y-1); + QVERIFY2(testObj < testObj5, "Order of less than operator is not correct"); + QGeoTileSpec testObj6(plugin, mapId, zoom, x+1, y-1); + QVERIFY2(testObj < testObj6, "Order of less than operator is not correct"); + QGeoTileSpec testObj7(plugin, mapId, zoom, x, y+1); + QVERIFY2(testObj < testObj7, "Order of less than operator is not correct"); + + QGeoTileSpec testObj8(plugin, mapId-1, zoom+1, x+1, y+1); + QVERIFY2(testObj8 < testObj, "Order of less than operator is not correct"); + QGeoTileSpec testObj9(plugin, mapId, zoom-1, x+1, y+1); + QVERIFY2(testObj9 < testObj, "Order of less than operator is not correct"); + QGeoTileSpec testObj10(plugin, mapId, zoom, x-1, y+1); + QVERIFY2(testObj10 < testObj, "Order of less than operator is not correct"); +} + +void tst_QGeoTileSpec::qHashTest_data(){ + populateGeoTileSpecData(); +} + +void tst_QGeoTileSpec::qHashTest() +{ + QGeoTileSpec testObj; + unsigned int hash1 = qHash(testObj); + QGeoTileSpec testObj2; + testObj2 = testObj; + unsigned int hash2 = qHash(testObj2); + QCOMPARE(hash1, hash2); + + QFETCH(QString,plugin); + QFETCH(int,mapId); + QFETCH(int,zoom); + QFETCH(int,x); + QFETCH(int,y); + + QGeoTileSpec testObj3(plugin, mapId, zoom, x, y); + unsigned int hash3 = qHash(testObj3); + QVERIFY(hash1 != hash3); + + testObj2.setMapId(testObj3.mapId()+1); + testObj2.setZoom(testObj3.zoom()+1); + testObj2.setX(testObj3.x()*5); + testObj2.setY(testObj3.y()*10); + hash2 = qHash(testObj2); + QVERIFY(hash2 != hash3); +} + +QTEST_APPLESS_MAIN(tst_QGeoTileSpec) + +#include "tst_qgeotilespec.moc" + + diff --git a/tests/auto/qmlinterface/data/TestAddress.qml b/tests/auto/qmlinterface/data/TestAddress.qml new file mode 100644 index 0000000..8d646a6 --- /dev/null +++ b/tests/auto/qmlinterface/data/TestAddress.qml @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtPositioning 5.5 + +Address { + city: "Brisbane" + country: "Australia" + countryCode: "AU" + postalCode: "4000" + state: "Queensland" + street: "123 Fake Street" +} diff --git a/tests/auto/qmlinterface/data/TestCategory.qml b/tests/auto/qmlinterface/data/TestCategory.qml new file mode 100644 index 0000000..d6ac2e4 --- /dev/null +++ b/tests/auto/qmlinterface/data/TestCategory.qml @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtLocation 5.3 + +Category { + name: "Test category" + categoryId: "test-category-id" +} diff --git a/tests/auto/qmlinterface/data/TestContactDetail.qml b/tests/auto/qmlinterface/data/TestContactDetail.qml new file mode 100644 index 0000000..7d76ff2 --- /dev/null +++ b/tests/auto/qmlinterface/data/TestContactDetail.qml @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtLocation 5.3 + +ContactDetail { + label: "Test Contact Detail" + value: "Test contact detail value" +} diff --git a/tests/auto/qmlinterface/data/TestIcon.qml b/tests/auto/qmlinterface/data/TestIcon.qml new file mode 100644 index 0000000..6df8077 --- /dev/null +++ b/tests/auto/qmlinterface/data/TestIcon.qml @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtLocation 5.3 + +Icon { + Component.onCompleted: { + parameters.singleUrl = "http://www.example.com/test-icon.png" + } +} diff --git a/tests/auto/qmlinterface/data/TestLocation.qml b/tests/auto/qmlinterface/data/TestLocation.qml new file mode 100644 index 0000000..6c3bb27 --- /dev/null +++ b/tests/auto/qmlinterface/data/TestLocation.qml @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtPositioning 5.5 + +Location { + address: TestAddress { } +// TODO:unsupported syntax for now +// boundingBox { +// center { +// longitude: 10.0 +// latitude: 20.0 +// altitude: 30.0 +// } +// height: 30.0 +// width: 40.0 +// } + boundingBox : QtPositioning.rectangle(QtPositioning.coordinate(20,10, 30),40.0,30) + coordinate { + longitude: 10.0 + latitude: 20.0 + altitude: 30.0 + } +} diff --git a/tests/auto/qmlinterface/data/TestPlace.qml b/tests/auto/qmlinterface/data/TestPlace.qml new file mode 100644 index 0000000..7b42bf3 --- /dev/null +++ b/tests/auto/qmlinterface/data/TestPlace.qml @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtLocation 5.6 + +Place { + name: "Test Place" + placeId: "test-place-id" + attribution: "Place data by Foo" + categories: [ + Category { + name: "Test category 1" + categoryId: "test-category-id-1" + }, + Category { + name: "Test category 2" + categoryId: "test-category-id-2" + } + ] + location: TestLocation { } + ratings: TestRatings { } + icon: TestIcon { } + supplier: TestSupplier { } + visibility: Place.PrivateVisibility +} diff --git a/tests/auto/qmlinterface/data/TestPlaceAttribute.qml b/tests/auto/qmlinterface/data/TestPlaceAttribute.qml new file mode 100644 index 0000000..07cec51 --- /dev/null +++ b/tests/auto/qmlinterface/data/TestPlaceAttribute.qml @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtLocation 5.3 + +PlaceAttribute { + label: "Test Attribute" + text: "Test attribute text" +} diff --git a/tests/auto/qmlinterface/data/TestRatings.qml b/tests/auto/qmlinterface/data/TestRatings.qml new file mode 100644 index 0000000..6e115fe --- /dev/null +++ b/tests/auto/qmlinterface/data/TestRatings.qml @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtLocation 5.3 + +Ratings { + average: 3.5 + maximum: 5.0 + count: 10 +} diff --git a/tests/auto/qmlinterface/data/TestSupplier.qml b/tests/auto/qmlinterface/data/TestSupplier.qml new file mode 100644 index 0000000..32b4b0f --- /dev/null +++ b/tests/auto/qmlinterface/data/TestSupplier.qml @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtLocation 5.3 + +Supplier { + name: "Test supplier" + supplierId: "test-supplier-id" + url: "http://www.example.com/test-supplier" + icon: TestIcon { } +} diff --git a/tests/auto/qmlinterface/data/TestUser.qml b/tests/auto/qmlinterface/data/TestUser.qml new file mode 100644 index 0000000..062d4b2 --- /dev/null +++ b/tests/auto/qmlinterface/data/TestUser.qml @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtLocation 5.3 + +User { + name: "Test User" + userId: "test-user-id" +} diff --git a/tests/auto/qmlinterface/qmlinterface.pro b/tests/auto/qmlinterface/qmlinterface.pro new file mode 100644 index 0000000..9f1acad --- /dev/null +++ b/tests/auto/qmlinterface/qmlinterface.pro @@ -0,0 +1,26 @@ +QT += location qml testlib + +#QT -= gui + +TARGET = tst_qmlinterface +CONFIG += testcase +CONFIG -= app_bundle + +TEMPLATE = app + + +SOURCES += tst_qmlinterface.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" + +OTHER_FILES += \ + data/TestCategory.qml \ + data/TestAddress.qml \ + data/TestLocation.qml \ + data/TestPlace.qml \ + data/TestIcon.qml \ + data/TestRatings.qml \ + data/TestSupplier.qml \ + data/TestUser.qml \ + data/TestPlaceAttribute.qml \ + data/TestContactDetail.qml + diff --git a/tests/auto/qmlinterface/tst_qmlinterface.cpp b/tests/auto/qmlinterface/tst_qmlinterface.cpp new file mode 100644 index 0000000..11fc3c1 --- /dev/null +++ b/tests/auto/qmlinterface/tst_qmlinterface.cpp @@ -0,0 +1,353 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class tst_qmlinterface : public QObject +{ + Q_OBJECT + +public: + tst_qmlinterface(); + +private Q_SLOTS: + void testAddress(); + void testLocation(); + void testCategory(); + void testIcon(); + void testRatings(); + void testSupplier(); + void testUser(); + void testPlaceAttribute(); + void testContactDetail(); + void testPlace(); + +private: + QGeoCoordinate m_coordinate; + QGeoAddress m_address; + QGeoRectangle m_rectangle; + QGeoLocation m_location; + QPlaceCategory m_category; + QPlaceIcon m_icon; + QPlaceRatings m_ratings; + QPlaceSupplier m_supplier; + QPlaceUser m_user; + QPlaceAttribute m_placeAttribute; + QPlaceContactDetail m_contactDetail; + QList m_categories; + QPlace m_place; +}; + +tst_qmlinterface::tst_qmlinterface() +{ + m_coordinate.setLongitude(10.0); + m_coordinate.setLatitude(20.0); + m_coordinate.setAltitude(30.0); + + m_address.setCity(QStringLiteral("Brisbane")); + m_address.setCountry(QStringLiteral("Australia")); + m_address.setCountryCode(QStringLiteral("AU")); + m_address.setPostalCode(QStringLiteral("4000")); + m_address.setState(QStringLiteral("Queensland")); + m_address.setStreet(QStringLiteral("123 Fake Street")); + + m_rectangle.setCenter(m_coordinate); + m_rectangle.setHeight(30.0); + m_rectangle.setWidth(40.0); + + m_location.setAddress(m_address); + m_location.setBoundingBox(m_rectangle); + m_location.setCoordinate(m_coordinate); + + m_category.setName(QStringLiteral("Test category")); + m_category.setCategoryId(QStringLiteral("test-category-id")); + + QVariantMap iconParams; + iconParams.insert(QPlaceIcon::SingleUrl, QUrl(QStringLiteral("http://www.example.com/test-icon.png"))); + m_icon.setParameters(iconParams); + + m_ratings.setAverage(3.5); + m_ratings.setMaximum(5.0); + m_ratings.setCount(10); + + m_supplier.setName(QStringLiteral("Test supplier")); + m_supplier.setUrl(QUrl(QStringLiteral("http://www.example.com/test-supplier"))); + m_supplier.setSupplierId(QStringLiteral("test-supplier-id")); + m_supplier.setIcon(m_icon); + + m_user.setName(QStringLiteral("Test User")); + m_user.setUserId(QStringLiteral("test-user-id")); + + m_placeAttribute.setLabel(QStringLiteral("Test Attribute")); + m_placeAttribute.setText(QStringLiteral("Test attribute text")); + + m_contactDetail.setLabel(QStringLiteral("Test Contact Detail")); + m_contactDetail.setValue(QStringLiteral("Test contact detail value")); + + QPlaceCategory category; + category.setName(QStringLiteral("Test category 1")); + category.setCategoryId(QStringLiteral("test-category-id-1")); + m_categories.append(category); + category.setName(QStringLiteral("Test category 2")); + category.setCategoryId(QStringLiteral("test-category-id-2")); + m_categories.append(category); + + m_place.setName(QStringLiteral("Test Place")); + m_place.setPlaceId(QStringLiteral("test-place-id")); + m_place.setAttribution(QStringLiteral("Place data by Foo")); + m_place.setCategories(m_categories); + m_place.setLocation(m_location); + m_place.setRatings(m_ratings); + m_place.setIcon(m_icon); + m_place.setSupplier(m_supplier); + m_place.setVisibility(QLocation::PrivateVisibility); +} + +void tst_qmlinterface::testAddress() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestAddress.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QGeoAddress address = qmlObject->property("address").value(); + + QCOMPARE(address, m_address); + + qmlObject->setProperty("address", QVariant::fromValue(QGeoAddress())); + + QVERIFY(qmlObject->property("city").toString().isEmpty()); + QVERIFY(qmlObject->property("country").toString().isEmpty()); + QVERIFY(qmlObject->property("countryCode").toString().isEmpty()); + QVERIFY(qmlObject->property("postalCode").toString().isEmpty()); + QVERIFY(qmlObject->property("state").toString().isEmpty()); + QVERIFY(qmlObject->property("street").toString().isEmpty()); + + delete qmlObject; +} + +void tst_qmlinterface::testLocation() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestLocation.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QGeoLocation location = qmlObject->property("location").value(); + + QCOMPARE(location, m_location); + + qmlObject->setProperty("location", QVariant::fromValue(QGeoLocation())); + + QCOMPARE(qmlObject->property("address").value(), QGeoAddress()); + QCOMPARE(qmlObject->property("boundingBox").value(), QGeoRectangle()); + QCOMPARE(qmlObject->property("coordinate").value(), QGeoCoordinate()); + + delete qmlObject; +} + +void tst_qmlinterface::testCategory() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestCategory.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QPlaceCategory category = qmlObject->property("category").value(); + + QCOMPARE(category, m_category); + + qmlObject->setProperty("category", QVariant::fromValue(QPlaceCategory())); + + QVERIFY(qmlObject->property("name").toString().isEmpty()); + QVERIFY(qmlObject->property("categoryId").toString().isEmpty()); + + delete qmlObject; +} + +void tst_qmlinterface::testIcon() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestIcon.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QPlaceIcon icon = qmlObject->property("icon").value(); + + QCOMPARE(icon, m_icon); + + qmlObject->setProperty("icon", QVariant::fromValue(QPlaceIcon())); + + QVERIFY(!qmlObject->property("fullUrl").toUrl().isValid()); + + delete qmlObject; +} + +void tst_qmlinterface::testRatings() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestRatings.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QPlaceRatings ratings = qmlObject->property("ratings").value(); + + QCOMPARE(ratings, m_ratings); + + qmlObject->setProperty("ratings", QVariant::fromValue(QPlaceRatings())); + + QCOMPARE(qmlObject->property("average").value(), 0.0); + QCOMPARE(qmlObject->property("maximum").value(), 0.0); + QCOMPARE(qmlObject->property("average").toInt(), 0); + + delete qmlObject; +} + +void tst_qmlinterface::testSupplier() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestSupplier.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QPlaceSupplier supplier = qmlObject->property("supplier").value(); + + QCOMPARE(supplier, m_supplier); + + qmlObject->setProperty("supplier", QVariant::fromValue(QPlaceSupplier())); + + QVERIFY(qmlObject->property("name").toString().isEmpty()); + QVERIFY(!qmlObject->property("url").toUrl().isValid()); + QVERIFY(qmlObject->property("supplierId").toString().isEmpty()); + QCOMPARE(qmlObject->property("icon").value(), QPlaceIcon()); + + delete qmlObject; +} + +void tst_qmlinterface::testUser() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestUser.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QPlaceUser user = qmlObject->property("user").value(); + + QCOMPARE(user, m_user); + + qmlObject->setProperty("user", QVariant::fromValue(QPlaceUser())); + + QVERIFY(qmlObject->property("name").toString().isEmpty()); + QVERIFY(qmlObject->property("userId").toString().isEmpty()); + + delete qmlObject; +} + +void tst_qmlinterface::testPlaceAttribute() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestPlaceAttribute.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QPlaceAttribute placeAttribute = qmlObject->property("attribute").value(); + + QCOMPARE(placeAttribute, m_placeAttribute); + + qmlObject->setProperty("attribute", QVariant::fromValue(QPlaceAttribute())); + + QVERIFY(qmlObject->property("label").toString().isEmpty()); + QVERIFY(qmlObject->property("text").toString().isEmpty()); + + delete qmlObject; +} + +void tst_qmlinterface::testContactDetail() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestContactDetail.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QPlaceContactDetail contactDetail = qmlObject->property("contactDetail").value(); + + QCOMPARE(contactDetail, m_contactDetail); + + qmlObject->setProperty("contactDetail", QVariant::fromValue(QPlaceContactDetail())); + + QVERIFY(qmlObject->property("label").toString().isEmpty()); + QVERIFY(qmlObject->property("value").toString().isEmpty()); + + delete qmlObject; +} + +void tst_qmlinterface::testPlace() +{ + QQmlEngine engine; + QQmlComponent component(&engine, SRCDIR "data/TestPlace.qml"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QObject *qmlObject = component.create(); + + QPlace place = qmlObject->property("place").value(); + + QCOMPARE(place, m_place); + + qmlObject->setProperty("place", QVariant::fromValue(QPlace())); + + QVERIFY(qmlObject->property("name").toString().isEmpty()); + QVERIFY(qmlObject->property("placeId").toString().isEmpty()); + QVERIFY(qmlObject->property("attribution").toString().isEmpty()); + QQmlListReference categories(qmlObject, "categories", &engine); + QCOMPARE(categories.count(), 0); + QCOMPARE(qmlObject->property("location").value(), QGeoLocation()); + QCOMPARE(qmlObject->property("ratings").value(), QPlaceRatings()); + QCOMPARE(qmlObject->property("icon").value(), QPlaceIcon()); + QCOMPARE(qmlObject->property("supplier").value(), QPlaceSupplier()); + + delete qmlObject; +} + +QTEST_MAIN(tst_qmlinterface) + +#include "tst_qmlinterface.moc" diff --git a/tests/auto/qnmeapositioninfosource/dummynmeapositioninfosource/dummynmeapositioninfosource.pro b/tests/auto/qnmeapositioninfosource/dummynmeapositioninfosource/dummynmeapositioninfosource.pro new file mode 100644 index 0000000..261992a --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/dummynmeapositioninfosource/dummynmeapositioninfosource.pro @@ -0,0 +1,23 @@ +TEMPLATE = app +CONFIG+=testcase +QT += network positioning testlib +TARGET = tst_dummynmeapositioninfosource + +INCLUDEPATH += .. + +HEADERS += ../../utils/qlocationtestutils_p.h \ + ../../qgeopositioninfosource/testqgeopositioninfosource_p.h \ + ../qnmeapositioninfosourceproxyfactory.h + +SOURCES += ../../utils/qlocationtestutils.cpp \ + ../../qgeopositioninfosource/testqgeopositioninfosource.cpp \ + ../qnmeapositioninfosourceproxyfactory.cpp \ + tst_dummynmeapositioninfosource.cpp + +# This test relies on a working local QTcpSocket(Server). When the CI is under +# heavy load the socket code cannot establish a connection which leads to flaky +# test results. We make this test insiginficant as there is currently no known +# solution to this problem. On the positive side QNmeaPositionInfoSource +# does not have a platform specific implementation. Other platforms should provide +# a close enough test approximation. +win32:CONFIG+=insignificant_test diff --git a/tests/auto/qnmeapositioninfosource/dummynmeapositioninfosource/tst_dummynmeapositioninfosource.cpp b/tests/auto/qnmeapositioninfosource/dummynmeapositioninfosource/tst_dummynmeapositioninfosource.cpp new file mode 100644 index 0000000..4468f91 --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/dummynmeapositioninfosource/tst_dummynmeapositioninfosource.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include "qnmeapositioninfosourceproxyfactory.h" +#include "../qgeopositioninfosource/testqgeopositioninfosource_p.h" +#include "../utils/qlocationtestutils_p.h" + +#include +#include +#include + +Q_DECLARE_METATYPE(QNmeaPositionInfoSource::UpdateMode) +Q_DECLARE_METATYPE(QGeoPositionInfo) + +class DummyNmeaPositionInfoSource : public QNmeaPositionInfoSource +{ + Q_OBJECT + +public: + DummyNmeaPositionInfoSource(QNmeaPositionInfoSource::UpdateMode mode, QObject *parent = 0); + +protected: + virtual bool parsePosInfoFromNmeaData(const char *data, + int size, + QGeoPositionInfo *posInfo, + bool *hasFix); + +private: + int callCount; +}; + +DummyNmeaPositionInfoSource::DummyNmeaPositionInfoSource(QNmeaPositionInfoSource::UpdateMode mode, QObject *parent) : + QNmeaPositionInfoSource(mode, parent), + callCount(0) +{ +} + +bool DummyNmeaPositionInfoSource::parsePosInfoFromNmeaData(const char* data, + int size, + QGeoPositionInfo *posInfo, + bool *hasFix) +{ + Q_UNUSED(data); + Q_UNUSED(size); + + posInfo->setCoordinate(QGeoCoordinate(callCount * 1.0, callCount * 1.0, callCount * 1.0)); + posInfo->setTimestamp(QDateTime::currentDateTime().toUTC()); + *hasFix = true; + ++callCount; + + return true; +} + +class tst_DummyNmeaPositionInfoSource : public QObject +{ + Q_OBJECT + +public: + tst_DummyNmeaPositionInfoSource(); + +private slots: + void initTestCase(); + void testOverloadedParseFunction(); +}; + + +tst_DummyNmeaPositionInfoSource::tst_DummyNmeaPositionInfoSource() {} + +void tst_DummyNmeaPositionInfoSource::initTestCase() +{ + qRegisterMetaType(); +} + +void tst_DummyNmeaPositionInfoSource::testOverloadedParseFunction() +{ + DummyNmeaPositionInfoSource source(QNmeaPositionInfoSource::RealTimeMode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spy(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + + QGeoPositionInfo pos; + + proxy->source()->startUpdates(); + + proxy->feedBytes(QString("The parser converts\n").toLatin1()); + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 1), 10000); + pos = spy.at(0).at(0).value(); + + QVERIFY((pos.coordinate().latitude() == 0.0) + && (pos.coordinate().longitude() == 0.0) + && (pos.coordinate().altitude() == 0.0)); + + spy.clear(); + + proxy->feedBytes(QString("any data it receives\n").toLatin1()); + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 1), 10000); + pos = spy.at(0).at(0).value(); + + QVERIFY((pos.coordinate().latitude() == 1.0) + && (pos.coordinate().longitude() == 1.0) + && (pos.coordinate().altitude() == 1.0)); + + spy.clear(); + + proxy->feedBytes(QString("into positions\n").toLatin1()); + + QTRY_VERIFY_WITH_TIMEOUT((spy.count() == 1), 10000); + pos = spy.at(0).at(0).value(); + + QVERIFY((pos.coordinate().latitude() == 2.0) + && (pos.coordinate().longitude() == 2.0) + && (pos.coordinate().altitude() == 2.0)); + + spy.clear(); +} + +#include "tst_dummynmeapositioninfosource.moc" + +QTEST_GUILESS_MAIN(tst_DummyNmeaPositionInfoSource); diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource.pro b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource.pro new file mode 100644 index 0000000..8c168d5 --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource.pro @@ -0,0 +1,8 @@ +TEMPLATE = subdirs +SUBDIRS += \ + dummynmeapositioninfosource \ + qnmeapositioninfosource_realtime \ + qnmeapositioninfosource_simulation \ + qnmeapositioninfosource_realtime_generic \ + qnmeapositioninfosource_simulation_generic + diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime/qnmeapositioninfosource_realtime.pro b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime/qnmeapositioninfosource_realtime.pro new file mode 100644 index 0000000..2b93cc2 --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime/qnmeapositioninfosource_realtime.pro @@ -0,0 +1,25 @@ +TEMPLATE = app +CONFIG+=testcase +QT += network positioning testlib +TARGET = tst_qnmeapositioninfosource_realtime + +INCLUDEPATH += .. + +HEADERS += ../../utils/qlocationtestutils_p.h \ + ../../qgeopositioninfosource/testqgeopositioninfosource_p.h \ + ../qnmeapositioninfosourceproxyfactory.h \ + ../tst_qnmeapositioninfosource.h + +SOURCES += ../../utils/qlocationtestutils.cpp \ + ../../qgeopositioninfosource/testqgeopositioninfosource.cpp \ + ../qnmeapositioninfosourceproxyfactory.cpp \ + ../tst_qnmeapositioninfosource.cpp \ + tst_qnmeapositioninfosource_realtime.cpp + +# This test relies on a working local QTcpSocket(Server). When the CI is under +# heavy load the socket code cannot establish a connection which leads to flaky +# test results. We make this test insiginficant as there is currently no known +# solution to this problem. On the positive side QNmeaPositionInfoSource +# does not have a platform specific implementation. Other platforms should provide +# a close enough test approximation. +win32:CONFIG+=insignificant_test diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime/tst_qnmeapositioninfosource_realtime.cpp b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime/tst_qnmeapositioninfosource_realtime.cpp new file mode 100644 index 0000000..423f657 --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime/tst_qnmeapositioninfosource_realtime.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include "tst_qnmeapositioninfosource.h" + +class tst_QNmeaPositionInfoSource_RealTime : public tst_QNmeaPositionInfoSource +{ + Q_OBJECT + +public: + tst_QNmeaPositionInfoSource_RealTime() + : tst_QNmeaPositionInfoSource(QNmeaPositionInfoSource::RealTimeMode) {} +}; + +#include "tst_qnmeapositioninfosource_realtime.moc" + +QTEST_GUILESS_MAIN(tst_QNmeaPositionInfoSource_RealTime); diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime_generic/qnmeapositioninfosource_realtime_generic.pro b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime_generic/qnmeapositioninfosource_realtime_generic.pro new file mode 100644 index 0000000..068289b --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime_generic/qnmeapositioninfosource_realtime_generic.pro @@ -0,0 +1,28 @@ +TEMPLATE = app +CONFIG+=testcase +testcase.timeout = 400 # this test is slow +QT += network positioning testlib +TARGET = tst_qnmeapositioninfosource_realtime_generic + +INCLUDEPATH += .. + +HEADERS += ../../utils/qlocationtestutils_p.h \ + ../../qgeopositioninfosource/testqgeopositioninfosource_p.h \ + ../qnmeapositioninfosourceproxyfactory.h \ + ../tst_qnmeapositioninfosource.h + +SOURCES += ../../utils/qlocationtestutils.cpp \ + ../../qgeopositioninfosource/testqgeopositioninfosource.cpp \ + ../qnmeapositioninfosourceproxyfactory.cpp \ + ../tst_qnmeapositioninfosource.cpp \ + tst_qnmeapositioninfosource_realtime_generic.cpp + +CONFIG -= app_bundle + +# This test relies on a working local QTcpSocket(Server). When the CI is under +# heavy load the socket code cannot establish a connection which leads to flaky +# test results. We make this test insiginficant as there is currently no known +# solution to this problem. On the positive side QNmeaPositionInfoSource +# does not have a platform specific implementation. Other platforms should provide +# a close enough test approximation. +win32:CONFIG+=insignificant_test diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime_generic/tst_qnmeapositioninfosource_realtime_generic.cpp b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime_generic/tst_qnmeapositioninfosource_realtime_generic.cpp new file mode 100644 index 0000000..cedcd56 --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_realtime_generic/tst_qnmeapositioninfosource_realtime_generic.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include "tst_qnmeapositioninfosource.h" + +class tst_QNmeaPositionInfoSource_RealTime_Generic : public TestQGeoPositionInfoSource +{ + Q_OBJECT + +public: + tst_QNmeaPositionInfoSource_RealTime_Generic() + { + m_factory = new QNmeaPositionInfoSourceProxyFactory; + /* + * Set custom path since CI doesn't install test plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#endif + } + + ~tst_QNmeaPositionInfoSource_RealTime_Generic() + { + delete m_factory; + } + +protected: + QGeoPositionInfoSource *createTestSource() + { + QNmeaPositionInfoSource *source = new QNmeaPositionInfoSource(QNmeaPositionInfoSource::RealTimeMode); + QNmeaPositionInfoSourceProxy *proxy = static_cast(m_factory->createProxy(source)); + Feeder *feeder = new Feeder(source); + feeder->start(proxy); + return source; + } + +private: + QNmeaPositionInfoSourceProxyFactory *m_factory; +}; + +#include "tst_qnmeapositioninfosource_realtime_generic.moc" + +QTEST_GUILESS_MAIN(tst_QNmeaPositionInfoSource_RealTime_Generic); diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation/qnmeapositioninfosource_simulation.pro b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation/qnmeapositioninfosource_simulation.pro new file mode 100644 index 0000000..1214d7e --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation/qnmeapositioninfosource_simulation.pro @@ -0,0 +1,25 @@ +TEMPLATE = app +CONFIG+=testcase +QT += network positioning testlib +TARGET = tst_qnmeapositioninfosource_simulation + +INCLUDEPATH += .. + +HEADERS += ../../utils/qlocationtestutils_p.h \ + ../../qgeopositioninfosource/testqgeopositioninfosource_p.h \ + ../qnmeapositioninfosourceproxyfactory.h \ + ../tst_qnmeapositioninfosource.h + +SOURCES += ../../utils/qlocationtestutils.cpp \ + ../../qgeopositioninfosource/testqgeopositioninfosource.cpp \ + ../qnmeapositioninfosourceproxyfactory.cpp \ + ../tst_qnmeapositioninfosource.cpp \ + tst_qnmeapositioninfosource_simulation.cpp + +# This test relies on a working local QTcpSocket(Server). When the CI is under +# heavy load the socket code cannot establish a connection which leads to flaky +# test results. We make this test insiginficant as there is currently no known +# solution to this problem. On the positive side QNmeaPositionInfoSource +# does not have a platform specific implementation. Other platforms should provide +# a close enough test approximation. +win32:CONFIG+=insignificant_test diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation/tst_qnmeapositioninfosource_simulation.cpp b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation/tst_qnmeapositioninfosource_simulation.cpp new file mode 100644 index 0000000..ffe5e1d --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation/tst_qnmeapositioninfosource_simulation.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include "tst_qnmeapositioninfosource.h" + +class tst_QNmeaPositionInfoSource_Simulation : public tst_QNmeaPositionInfoSource +{ + Q_OBJECT +public: + tst_QNmeaPositionInfoSource_Simulation() + : tst_QNmeaPositionInfoSource(QNmeaPositionInfoSource::SimulationMode) {} +}; + +#include "tst_qnmeapositioninfosource_simulation.moc" + +QTEST_GUILESS_MAIN(tst_QNmeaPositionInfoSource_Simulation); diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation_generic/qnmeapositioninfosource_simulation_generic.pro b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation_generic/qnmeapositioninfosource_simulation_generic.pro new file mode 100644 index 0000000..726a746 --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation_generic/qnmeapositioninfosource_simulation_generic.pro @@ -0,0 +1,28 @@ +TEMPLATE = app +CONFIG+=testcase +testcase.timeout = 400 # this test is slow +QT += network positioning testlib +TARGET = tst_qnmeapositioninfosource_simulation_generic + +INCLUDEPATH += .. + +HEADERS += ../../utils/qlocationtestutils_p.h \ + ../../qgeopositioninfosource/testqgeopositioninfosource_p.h \ + ../qnmeapositioninfosourceproxyfactory.h \ + ../tst_qnmeapositioninfosource.h + +SOURCES += ../../utils/qlocationtestutils.cpp \ + ../../qgeopositioninfosource/testqgeopositioninfosource.cpp \ + ../qnmeapositioninfosourceproxyfactory.cpp \ + ../tst_qnmeapositioninfosource.cpp \ + tst_qnmeapositioninfosource_simulation_generic.cpp + +CONFIG -= app_bundle + +# This test relies on a working local QTcpSocket(Server). When the CI is under +# heavy load the socket code cannot establish a connection which leads to flaky +# test results. We make this test insiginficant as there is currently no known +# solution to this problem. On the positive side QNmeaPositionInfoSource +# does not have a platform specific implementation. Other platforms should provide +# a close enough test approximation. +win32:CONFIG+=insignificant_test diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation_generic/tst_qnmeapositioninfosource_simulation_generic.cpp b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation_generic/tst_qnmeapositioninfosource_simulation_generic.cpp new file mode 100644 index 0000000..0d8f03c --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosource_simulation_generic/tst_qnmeapositioninfosource_simulation_generic.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include "tst_qnmeapositioninfosource.h" + +class tst_QNmeaPositionInfoSource_Simulation_Generic : public TestQGeoPositionInfoSource +{ + Q_OBJECT +public: + tst_QNmeaPositionInfoSource_Simulation_Generic() + { + /* + * Set custom path since CI doesn't install test plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#endif + } + +protected: + QGeoPositionInfoSource *createTestSource() + { + QNmeaPositionInfoSource *source = new QNmeaPositionInfoSource(QNmeaPositionInfoSource::SimulationMode); + source->setDevice(new UnlimitedNmeaStream(source)); + return source; + } +}; + +#include "tst_qnmeapositioninfosource_simulation_generic.moc" + +QTEST_GUILESS_MAIN(tst_QNmeaPositionInfoSource_Simulation_Generic); diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosourceproxyfactory.cpp b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosourceproxyfactory.cpp new file mode 100644 index 0000000..756d027 --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosourceproxyfactory.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnmeapositioninfosourceproxyfactory.h" +#include "../utils/qlocationtestutils_p.h" + +#include +#include +#include + + +QNmeaPositionInfoSourceProxy::QNmeaPositionInfoSourceProxy(QNmeaPositionInfoSource *source, QIODevice *outDevice) + : m_source(source), + m_outDevice(outDevice) +{ +} + +QNmeaPositionInfoSourceProxy::~QNmeaPositionInfoSourceProxy() +{ + m_outDevice->close(); + delete m_outDevice; +} + +QGeoPositionInfoSource *QNmeaPositionInfoSourceProxy::source() const +{ + return m_source; +} + +void QNmeaPositionInfoSourceProxy::feedUpdate(const QDateTime &dt) +{ + m_outDevice->write(QLocationTestUtils::createRmcSentence(dt).toLatin1()); +} + +void QNmeaPositionInfoSourceProxy::feedBytes(const QByteArray &bytes) +{ + m_outDevice->write(bytes); +} + + +QNmeaPositionInfoSourceProxyFactory::QNmeaPositionInfoSourceProxyFactory() + : m_server(new QTcpServer(this)) +{ + bool b = m_server->listen(QHostAddress::LocalHost); + Q_ASSERT(b); +} + +QNmeaPositionInfoSourceProxy *QNmeaPositionInfoSourceProxyFactory::createProxy(QNmeaPositionInfoSource *source) +{ + QTcpSocket *client = new QTcpSocket; + client->connectToHost(m_server->serverAddress(), m_server->serverPort()); + qDebug() << "listening on" << m_server->serverAddress() << m_server->serverPort(); + bool b = m_server->waitForNewConnection(15000); + if (!b) + qWarning() << "Server didin't receive new connection"; + b = client->waitForConnected(); + if (!b) + qWarning() << "Client could not connect to server"; + + //QNmeaPositionInfoSource *source = new QNmeaPositionInfoSource(m_mode); + QIODevice *device = m_server->nextPendingConnection(); + if (!device) + qWarning() << "Missing pending connection. Test is going to fail."; + else + qWarning() << "Received pending connection:" << device << b; + source->setDevice(device); + Q_ASSERT(source->device() != 0); + QNmeaPositionInfoSourceProxy *proxy = new QNmeaPositionInfoSourceProxy(source, client); + proxy->setParent(source); + return proxy; +} diff --git a/tests/auto/qnmeapositioninfosource/qnmeapositioninfosourceproxyfactory.h b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosourceproxyfactory.h new file mode 100644 index 0000000..d740f9b --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/qnmeapositioninfosourceproxyfactory.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNMEAPOSITIONINFOSOURCEPROXYFACTORY_H +#define QNMEAPOSITIONINFOSOURCEPROXYFACTORY_H + +#include + +#include + +QT_BEGIN_NAMESPACE +class QTcpServer; +class QIODevice; +class QNmeaPositionInfoSource; +QT_END_NAMESPACE + +class QNmeaPositionInfoSourceProxy : public QObject +{ + Q_OBJECT +public: + QNmeaPositionInfoSourceProxy(QNmeaPositionInfoSource *source, QIODevice *outDevice); + ~QNmeaPositionInfoSourceProxy(); + + QGeoPositionInfoSource *source() const; + + void feedUpdate(const QDateTime &dt); + + void feedBytes(const QByteArray &bytes); + + int updateIntervalErrorMargin() const { return 50; } + +private: + QNmeaPositionInfoSource *m_source; + QIODevice *m_outDevice; +}; + +class QNmeaPositionInfoSourceProxyFactory : public QObject +{ + Q_OBJECT +public: + QNmeaPositionInfoSourceProxyFactory(); + + // proxy is created as child of source + QNmeaPositionInfoSourceProxy *createProxy(QNmeaPositionInfoSource *source); + +private: + QTcpServer *m_server; +}; + +#endif diff --git a/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.cpp b/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.cpp new file mode 100644 index 0000000..8305dc1 --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.cpp @@ -0,0 +1,573 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jolla Ltd. +** Contact: Aaron McCarthy +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//TESTED_COMPONENT=src/location + +#include "tst_qnmeapositioninfosource.h" + +#include + +#ifdef Q_OS_WIN + +// Windows seems to require longer timeouts and step length +// We override the standard QTestCase related macros + +#ifdef QTRY_COMPARE_WITH_TIMEOUT +#undef QTRY_COMPARE_WITH_TIMEOUT +#endif +#define QTRY_COMPARE_WITH_TIMEOUT(__expr, __expected, __timeout) \ +do { \ + const int __step = 100; \ + const int __timeoutValue = __timeout; \ + if ((__expr) != (__expected)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeoutValue && ((__expr) != (__expected)); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + QCOMPARE(__expr, __expected); \ +} while (0) + +#ifdef QTRY_COMPARE +#undef QTRY_COMPARE +#endif +#define QTRY_COMPARE(__expr, __expected) QTRY_COMPARE_WITH_TIMEOUT(__expr, __expected, 10000) + +#endif + +tst_QNmeaPositionInfoSource::tst_QNmeaPositionInfoSource(QNmeaPositionInfoSource::UpdateMode mode, QObject *parent) + : QObject(parent), + m_mode(mode) +{ +} + +void tst_QNmeaPositionInfoSource::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); +} + +void tst_QNmeaPositionInfoSource::constructor() +{ + QObject o; + QNmeaPositionInfoSource source(m_mode, &o); + QCOMPARE(source.updateMode(), m_mode); + QCOMPARE(source.parent(), &o); +} + +void tst_QNmeaPositionInfoSource::supportedPositioningMethods() +{ + QNmeaPositionInfoSource source(m_mode); + QCOMPARE(source.supportedPositioningMethods(), QNmeaPositionInfoSource::SatellitePositioningMethods); +} + +void tst_QNmeaPositionInfoSource::minimumUpdateInterval() +{ + QNmeaPositionInfoSource source(m_mode); + QCOMPARE(source.minimumUpdateInterval(), 100); +} + +void tst_QNmeaPositionInfoSource::userEquivalentRangeError() +{ + QNmeaPositionInfoSource source(m_mode); + QVERIFY(qIsNaN(source.userEquivalentRangeError())); + source.setUserEquivalentRangeError(5.1); + QVERIFY(qFuzzyCompare(source.userEquivalentRangeError(), 5.1)); +} + +void tst_QNmeaPositionInfoSource::setUpdateInterval_delayedUpdate() +{ + // If an update interval is set, and an update is not available at a + // particular interval, the source should emit the next update as soon + // as it becomes available + + QNmeaPositionInfoSource source(m_mode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + proxy->source()->setUpdateInterval(500); + proxy->source()->startUpdates(); + + QTest::qWait(600); + QDateTime now = QDateTime::currentDateTime(); + proxy->feedUpdate(now); + QTRY_COMPARE(spyUpdate.count(), 1); + + // should have gotten the update immediately, and not have needed to + // wait until the next interval + QVERIFY(now.time().msecsTo(QDateTime::currentDateTime().time()) < 200); +} + +void tst_QNmeaPositionInfoSource::lastKnownPosition() +{ + QNmeaPositionInfoSource source(m_mode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QCOMPARE(proxy->source()->lastKnownPosition(), QGeoPositionInfo()); + + // source may need requestUpdate() or startUpdates() to be called to + // trigger reading of data channel + QSignalSpy spyTimeout(proxy->source(), SIGNAL(updateTimeout())); + proxy->source()->requestUpdate(proxy->source()->minimumUpdateInterval()); + QTRY_COMPARE(spyTimeout.count(), 1); + + // If an update is received and startUpdates() or requestUpdate() hasn't + // been called, it should still be available through lastKnownPosition() + QDateTime dt = QDateTime::currentDateTime().toUTC(); + proxy->feedUpdate(dt); + QTRY_COMPARE(proxy->source()->lastKnownPosition().timestamp(), dt); + + QList dateTimes = createDateTimes(5); + for (int i=0; isource()->requestUpdate(); + proxy->feedUpdate(dateTimes[i]); + QTRY_COMPARE(proxy->source()->lastKnownPosition().timestamp(), dateTimes[i]); + } + + proxy->source()->startUpdates(); + dateTimes = createDateTimes(5); + for (int i=0; ifeedUpdate(dateTimes[i]); + QTRY_COMPARE(proxy->source()->lastKnownPosition().timestamp(), dateTimes[i]); + } +} + +void tst_QNmeaPositionInfoSource::beginWithBufferedData() +{ + // In SimulationMode, data stored in the QIODevice is read when + // startUpdates() or requestUpdate() is called. + // In RealTimeMode, all existing data in the QIODevice is ignored - + // only new data will be read. + + QFETCH(QList, dateTimes); + QFETCH(UpdateTriggerMethod, trigger); + + QByteArray bytes; + for (int i=0; i().timestamp(), dateTimes[i]); + } else if (trigger == RequestUpdatesMethod) { + QTRY_COMPARE(spy.count(), 1); + QCOMPARE(spy.at(0).at(0).value().timestamp(), dateTimes.first()); + } + } +} + +void tst_QNmeaPositionInfoSource::beginWithBufferedData_data() +{ + QTest::addColumn >("dateTimes"); + QTest::addColumn("trigger"); + + QList dateTimes; + dateTimes << QDateTime::currentDateTime().toUTC(); + + QTest::newRow("startUpdates(), 1 update in buffer") << dateTimes << StartUpdatesMethod; + QTest::newRow("requestUpdate(), 1 update in buffer") << dateTimes << RequestUpdatesMethod; + + for (int i=1; i<3; i++) + dateTimes << dateTimes[0].addDays(i); + QTest::newRow("startUpdates(), multiple updates in buffer") << dateTimes << StartUpdatesMethod; + QTest::newRow("requestUpdate(), multiple updates in buffer") << dateTimes << RequestUpdatesMethod; +} + +void tst_QNmeaPositionInfoSource::startUpdates() +{ + QFETCH(QList, dateTimes); + + QNmeaPositionInfoSource source(m_mode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + proxy->source()->startUpdates(); + + for (int i=0; ifeedUpdate(dateTimes[i]); + QTRY_COMPARE(spyUpdate.count(), dateTimes.count()); +} + +void tst_QNmeaPositionInfoSource::startUpdates_data() +{ + QTest::addColumn >("dateTimes"); + + QTest::newRow("1 update") << createDateTimes(1); + QTest::newRow("2 updates") << createDateTimes(2); + QTest::newRow("10 updates") << createDateTimes(10); +} + +void tst_QNmeaPositionInfoSource::startUpdates_withTimeout() +{ + QNmeaPositionInfoSource source(m_mode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(proxy->source(), SIGNAL(updateTimeout())); + + proxy->source()->setUpdateInterval(1000); + proxy->source()->startUpdates(); + + QDateTime dt = QDateTime::currentDateTime().toUTC(); + + if (m_mode == QNmeaPositionInfoSource::SimulationMode) { + // the first sentence primes the simulation + proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt).toLatin1()); + proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addMSecs(10)).toLatin1()); + proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addMSecs(1100)).toLatin1()); + proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addMSecs(2200)).toLatin1()); + proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addSecs(9)).toLatin1()); + + QTime t; + t.start(); + + for (int j = 1; j < 4; ++j) { + QTRY_COMPARE(spyUpdate.count(), j); + QCOMPARE(spyTimeout.count(), 0); + int time = t.elapsed(); + QVERIFY((time > j*1000 - 300) && (time < j*1000 + 300)); + } + + spyUpdate.clear(); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() == 0) && (spyTimeout.count() == 1), 7500); + spyTimeout.clear(); + + QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() == 1) && (spyTimeout.count() == 0), 7500); + + } else { + // dt + 900 + QTRY_VERIFY(spyUpdate.count() == 0 && spyTimeout.count() == 0); + + proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addSecs(1)).toLatin1()); + // dt + 1200 + QTRY_VERIFY(spyUpdate.count() == 1 && spyTimeout.count() == 0); + spyUpdate.clear(); + + // dt + 1900 + QTRY_VERIFY(spyUpdate.count() == 0 && spyTimeout.count() == 0); + proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addSecs(2)).toLatin1()); + + // dt + 2200 + QTRY_VERIFY(spyUpdate.count() == 1 && spyTimeout.count() == 0); + spyUpdate.clear(); + + // dt + 2900 + QTRY_VERIFY(spyUpdate.count() == 0 && spyTimeout.count() == 0); + proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addSecs(3)).toLatin1()); + + // dt + 3200 + QTRY_VERIFY(spyUpdate.count() == 1 && spyTimeout.count() == 0); + spyUpdate.clear(); + + // dt + 6900 + QTRY_VERIFY(spyUpdate.count() == 0 && spyTimeout.count() == 1); + spyTimeout.clear(); + proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addSecs(7)).toLatin1()); + + // dt + 7200 + QTRY_VERIFY(spyUpdate.count() == 1 && spyTimeout.count() == 0); + spyUpdate.clear(); + } +} + +void tst_QNmeaPositionInfoSource::startUpdates_expectLatestUpdateOnly() +{ + // If startUpdates() is called and an interval has been set, if multiple' + // updates are in the buffer, only the latest update should be emitted + + QNmeaPositionInfoSource source(m_mode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + proxy->source()->setUpdateInterval(500); + proxy->source()->startUpdates(); + + QList dateTimes = createDateTimes(3); + for (int i=0; ifeedUpdate(dateTimes[i]); + + QTRY_COMPARE(spyUpdate.count(), 1); + QCOMPARE(spyUpdate[0][0].value().timestamp(), dateTimes.last()); +} + +void tst_QNmeaPositionInfoSource::startUpdates_waitForValidDateTime() +{ + // Tests that the class does not emit an update until it receives a + // sentences with a valid date *and* time. All sentences before this + // should be ignored, and any sentences received after this that do + // not have a date should use the known date. + + QFETCH(QByteArray, bytes); + QFETCH(QList, dateTimes); + QFETCH(QList, expectHorizontalAccuracy); + QFETCH(QList, expectVerticalAccuracy); + + QNmeaPositionInfoSource source(m_mode); + source.setUserEquivalentRangeError(5.1); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spy(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + proxy->source()->startUpdates(); + + proxy->feedBytes(bytes); + QTRY_COMPARE(spy.count(), dateTimes.count()); + + for (int i=0; i(); + + QCOMPARE(pInfo.timestamp(), dateTimes[i]); + + // Generated GGA/GSA sentences have hard coded HDOP of 3.5, which corrisponds to a + // horizontal accuracy of 35.7, for the user equivalent range error of 5.1 set above. + QCOMPARE(pInfo.hasAttribute(QGeoPositionInfo::HorizontalAccuracy), + expectHorizontalAccuracy[i]); + if (pInfo.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) + QVERIFY(qFuzzyCompare(pInfo.attribute(QGeoPositionInfo::HorizontalAccuracy), 35.7)); + + // Generate GSA sentences have hard coded VDOP of 4.0, which corrisponds to a vertical + // accuracy of 40.8, for the user equivalent range error of 5.1 set above. + QCOMPARE(pInfo.hasAttribute(QGeoPositionInfo::VerticalAccuracy), + expectVerticalAccuracy[i]); + if (pInfo.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) + QVERIFY(qFuzzyCompare(pInfo.attribute(QGeoPositionInfo::VerticalAccuracy), 40.8)); + } +} + +void tst_QNmeaPositionInfoSource::startUpdates_waitForValidDateTime_data() +{ + QTest::addColumn("bytes"); + QTest::addColumn >("dateTimes"); + QTest::addColumn >("expectHorizontalAccuracy"); + QTest::addColumn >("expectVerticalAccuracy"); + + QDateTime dt = QDateTime::currentDateTime().toUTC(); + QByteArray bytes; + + // should only receive RMC sentence and the GGA sentence *after* it + bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(1).time()).toLatin1(); + bytes += QLocationTestUtils::createRmcSentence(dt.addSecs(2)).toLatin1(); + bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(3).time()).toLatin1(); + QTest::newRow("Feed GGA,RMC,GGA; expect RMC, second GGA only") + << bytes << (QList() << dt.addSecs(2) << dt.addSecs(3)) + << (QList() << true << true) + << (QList() << false << false); + + // should not receive ZDA (has no coordinates) but should get the GGA + // sentence after it since it got the date/time from ZDA + bytes.clear(); + bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(1).time()).toLatin1(); + bytes += QLocationTestUtils::createZdaSentence(dt.addSecs(2)).toLatin1(); + bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(3).time()).toLatin1(); + QTest::newRow("Feed GGA,ZDA,GGA; expect second GGA only") + << bytes << (QList() << dt.addSecs(3)) + << (QList() << true) + << (QList() << false); + + // Feed ZDA,GGA,GSA,GGA; expect vertical accuracy from second GGA. + bytes.clear(); + bytes += QLocationTestUtils::createZdaSentence(dt.addSecs(1)).toLatin1(); + bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(2).time()).toLatin1(); + bytes += QLocationTestUtils::createGsaSentence().toLatin1(); + bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(3).time()).toLatin1(); + QTest::newRow("Feed ZDA,GGA,GSA,GGA; expect vertical accuracy from second GGA") + << bytes << (QList() << dt.addSecs(2) << dt.addSecs(3)) + << (QList() << true << true) + << (QList() << false << true); + + if (m_mode == QNmeaPositionInfoSource::SimulationMode) { + // In sim m_mode, should ignore sentence with a date/time before the known date/time + // (in real time m_mode, everything is passed on regardless) + bytes.clear(); + bytes += QLocationTestUtils::createRmcSentence(dt.addSecs(1)).toLatin1(); + bytes += QLocationTestUtils::createRmcSentence(dt.addSecs(-2)).toLatin1(); + bytes += QLocationTestUtils::createRmcSentence(dt.addSecs(2)).toLatin1(); + QTest::newRow("Feed good RMC, RMC with bad date/time, good RMC; expect first and third RMC only") + << bytes << (QList() << dt.addSecs(1) << dt.addSecs(2)) + << (QList() << false << false) + << (QList() << false << false); + } +} + +void tst_QNmeaPositionInfoSource::requestUpdate_waitForValidDateTime() +{ + QFETCH(QByteArray, bytes); + QFETCH(QList, dateTimes); + + QNmeaPositionInfoSource source(m_mode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spy(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + proxy->source()->requestUpdate(); + + proxy->feedBytes(bytes); + QTRY_COMPARE(spy.count(), 1); + QCOMPARE(spy[0][0].value().timestamp(), dateTimes[0]); +} + +void tst_QNmeaPositionInfoSource::requestUpdate_waitForValidDateTime_data() +{ + startUpdates_waitForValidDateTime_data(); +} + +void tst_QNmeaPositionInfoSource::requestUpdate() +{ + QNmeaPositionInfoSource source(m_mode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(proxy->source(), SIGNAL(updateTimeout())); + QDateTime dt; + + proxy->source()->requestUpdate(100); + QTRY_COMPARE(spyTimeout.count(), 1); + spyTimeout.clear(); + + dt = QDateTime::currentDateTime().toUTC(); + proxy->feedUpdate(dt); + proxy->source()->requestUpdate(); + QTRY_COMPARE(spyUpdate.count(), 1); + QCOMPARE(spyUpdate[0][0].value().timestamp(), dt); + QCOMPARE(spyTimeout.count(), 0); + spyUpdate.clear(); + + // delay the update and expect it to be emitted after 300ms + dt = QDateTime::currentDateTime().toUTC(); + proxy->source()->requestUpdate(1000); + QTest::qWait(300); + proxy->feedUpdate(dt); + QTRY_COMPARE(spyUpdate.count(), 1); + QCOMPARE(spyUpdate[0][0].value().timestamp(), dt); + QCOMPARE(spyTimeout.count(), 0); + spyUpdate.clear(); + + // delay the update and expect updateTimeout() to be emitted + dt = QDateTime::currentDateTime().toUTC(); + proxy->source()->requestUpdate(500); + QTest::qWait(1000); + proxy->feedUpdate(dt); + QCOMPARE(spyTimeout.count(), 1); + QCOMPARE(spyUpdate.count(), 0); + spyUpdate.clear(); +} + +void tst_QNmeaPositionInfoSource::requestUpdate_after_start() +{ + QNmeaPositionInfoSource source(m_mode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + QSignalSpy spyTimeout(proxy->source(), SIGNAL(updateTimeout())); + + // Start updates with 500ms interval and requestUpdate() with 100ms + // timeout. Feed an update, and it should be emitted immediately due to + // the requestUpdate(). The update should not be emitted again after that + // (i.e. the startUpdates() interval should not cause it to be re-emitted). + + QDateTime dt = QDateTime::currentDateTime().toUTC(); + proxy->source()->setUpdateInterval(500); + proxy->source()->startUpdates(); + proxy->source()->requestUpdate(100); + proxy->feedUpdate(dt); + QTRY_COMPARE(spyUpdate.count(), 1); + QCOMPARE(spyUpdate[0][0].value().timestamp(), dt); + QCOMPARE(spyTimeout.count(), 0); + spyUpdate.clear(); + + // Update has been emitted for requestUpdate(), shouldn't be emitted for startUpdates() + QTRY_COMPARE_WITH_TIMEOUT(spyUpdate.count(), 0, 1000); +} + +void tst_QNmeaPositionInfoSource::testWithBadNmea() +{ + QFETCH(QByteArray, bytes); + QFETCH(QList, dateTimes); + QFETCH(UpdateTriggerMethod, trigger); + + QNmeaPositionInfoSource source(m_mode); + QNmeaPositionInfoSourceProxyFactory factory; + QNmeaPositionInfoSourceProxy *proxy = static_cast(factory.createProxy(&source)); + + QSignalSpy spy(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + if (trigger == StartUpdatesMethod) + proxy->source()->startUpdates(); + else + proxy->source()->requestUpdate(); + + proxy->feedBytes(bytes); + QTRY_COMPARE(spy.count(), dateTimes.count()); + for (int i=0; i().timestamp(), dateTimes[i]); +} + +void tst_QNmeaPositionInfoSource::testWithBadNmea_data() +{ + QTest::addColumn("bytes"); + QTest::addColumn >("dateTimes"); + QTest::addColumn("trigger"); + + QDateTime firstDateTime = QDateTime::currentDateTime().toUTC(); + QByteArray bad = QLocationTestUtils::createRmcSentence(firstDateTime.addSecs(1)).toLatin1(); + bad = bad.mid(bad.length()/2); + QDateTime lastDateTime = firstDateTime.addSecs(2); + + QByteArray bytes; + bytes += QLocationTestUtils::createRmcSentence(firstDateTime).toLatin1(); + bytes += bad; + bytes += QLocationTestUtils::createRmcSentence(lastDateTime).toLatin1(); + QTest::newRow("requestUpdate(), bad second sentence") << bytes + << (QList() << firstDateTime) << RequestUpdatesMethod; + QTest::newRow("startUpdates(), bad second sentence") << bytes + << (QList() << firstDateTime << lastDateTime) << StartUpdatesMethod; +} diff --git a/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.h b/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.h new file mode 100644 index 0000000..844dfba --- /dev/null +++ b/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.h @@ -0,0 +1,179 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnmeapositioninfosourceproxyfactory.h" +#include "../qgeopositioninfosource/testqgeopositioninfosource_p.h" +#include "../utils/qlocationtestutils_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE +Q_DECLARE_METATYPE(QNmeaPositionInfoSource::UpdateMode) +Q_DECLARE_METATYPE(QGeoPositionInfo) +Q_DECLARE_METATYPE(QList) + +class tst_QNmeaPositionInfoSource : public QObject +{ + Q_OBJECT + +public: + enum UpdateTriggerMethod + { + StartUpdatesMethod, + RequestUpdatesMethod + }; + + tst_QNmeaPositionInfoSource(QNmeaPositionInfoSource::UpdateMode mode, QObject *parent = 0); + +private: + QList createDateTimes(int count) const + { + QList dateTimes; + QDateTime dt = QDateTime::currentDateTime().toUTC(); + int interval = 100; + for (int i=0; isetInterval(proxy->source()->minimumUpdateInterval()*2); + timer->start(); + } + +public slots: + void timeout() + { + m_proxy->feedBytes(QLocationTestUtils::createRmcSentence(QDateTime::currentDateTime()).toLatin1()); + } + +private: + QNmeaPositionInfoSourceProxy *m_proxy; +}; + +//--------------------------------------------------- + + +class UnlimitedNmeaStream : public QIODevice +{ + Q_OBJECT + +public: + UnlimitedNmeaStream(QObject *parent) : QIODevice(parent) {} + +protected: + qint64 readData(char *data, qint64 maxSize) + { + QByteArray bytes = QLocationTestUtils::createRmcSentence(QDateTime::currentDateTime()).toLatin1(); + qint64 sz = qMin(qint64(bytes.size()), maxSize); + memcpy(data, bytes.constData(), sz); + return sz; + } + + qint64 writeData(const char *, qint64) + { + return -1; + } + + qint64 bytesAvailable() const + { + return 1024 + QIODevice::bytesAvailable(); + } +}; diff --git a/tests/auto/qplace/qplace.pro b/tests/auto/qplace/qplace.pro new file mode 100644 index 0000000..290c831 --- /dev/null +++ b/tests/auto/qplace/qplace.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplace + +SOURCES += tst_qplace.cpp + +QT += location testlib diff --git a/tests/auto/qplace/tst_qplace.cpp b/tests/auto/qplace/tst_qplace.cpp new file mode 100644 index 0000000..bc86599 --- /dev/null +++ b/tests/auto/qplace/tst_qplace.cpp @@ -0,0 +1,681 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_Place : public QObject +{ + Q_OBJECT + +public: + tst_Place(); + +private Q_SLOTS: + void constructorTest(); + void categoriesTest(); + void detailsFetchedTest(); + void locationTest(); + void ratingTest(); + void supplierTest(); + void imageContentTest(); + void reviewContentTest(); + void editorialContentTest(); + void totalContentCountTest(); + void totalContentCountTest_data(); + void nameTest(); + void placeIdTest(); + void attributionTest(); + void contactDetailsTest(); + void primaryPhoneTest(); + void primaryFaxTest(); + void primaryEmailTest(); + void primaryWebsiteTest(); + void operatorsTest(); + void extendedAttributeTest(); + void visibilityTest(); + void isEmptyTest(); +}; + +tst_Place::tst_Place() +{ +} + +void tst_Place::constructorTest() +{ + QPlace testObj; + testObj.setPlaceId("testId"); + QPlaceAttribute paymentMethods; + paymentMethods.setLabel("Payment methods"); + paymentMethods.setText("Visa"); + testObj.setExtendedAttribute(QStringLiteral("paymentMethods"), paymentMethods); + QGeoLocation loc; + loc.setCoordinate(QGeoCoordinate(10,20)); + testObj.setLocation(loc); + QPlace *testObjPtr = new QPlace(testObj); + + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + + delete testObjPtr; +} + +void tst_Place::nameTest() +{ + QPlace testObj; + QVERIFY2(testObj.name() == QString(), "Wrong default value"); + testObj.setName("testText"); + QVERIFY2(testObj.name() == "testText", "Wrong value returned"); +} + +void tst_Place::placeIdTest() +{ + QPlace testObj; + QVERIFY2(testObj.placeId() == QString(), "Wrong default value"); + testObj.setPlaceId("testText"); + QVERIFY2(testObj.placeId() == "testText", "Wrong value returned"); +} + +void tst_Place::totalContentCountTest() +{ + QFETCH(QPlaceContent::Type, contentType); + QPlace testObj; + QVERIFY2(testObj.totalContentCount(contentType) == 0, "Wrong default value"); + testObj.setTotalContentCount(contentType, 50); + QVERIFY2(testObj.totalContentCount(contentType) == 50, "Wrong value returned"); + + testObj.setTotalContentCount(contentType,0); + QVERIFY2(testObj.totalContentCount(contentType) == 0, "Wrong value returned"); +} + +void tst_Place::totalContentCountTest_data() +{ + QTest::addColumn("contentType"); + QTest::newRow("Image content") << QPlaceContent::ImageType; + QTest::newRow("Editoral content") << QPlaceContent::EditorialType; + QTest::newRow("Review content") << QPlaceContent::ReviewType; +} + +void tst_Place::ratingTest() +{ + QPlace testObj; + QVERIFY2(testObj.ratings() == QPlaceRatings(), "Wrong default value"); + QPlaceRatings obj; + obj.setCount(10); + testObj.setRatings(obj); + QVERIFY2(testObj.ratings() == obj, "Wrong value returned"); +} + +void tst_Place::locationTest() +{ + QPlace testObj; + QVERIFY2(testObj.location() == QGeoLocation(), "Wrong default value"); + QGeoLocation obj; + obj.setCoordinate(QGeoCoordinate(10,20)); + testObj.setLocation(obj); + QVERIFY2(testObj.location() == obj, "Wrong value returned"); +} + +void tst_Place::detailsFetchedTest() +{ + QPlace testPlace; + QVERIFY2(testPlace.detailsFetched() == false, "Wrong default value"); + testPlace.setDetailsFetched(true); + QVERIFY2(testPlace.detailsFetched() == true, "Wrong value returned"); + testPlace.setDetailsFetched(false); + QVERIFY2(testPlace.detailsFetched() == false, "Wrong value returned"); +} + +void tst_Place::imageContentTest() +{ + QPlace place; + QVERIFY2(place.content(QPlaceContent::ImageType).count() ==0,"Wrong default value"); + + QPlaceImage dummyImage; + dummyImage.setUrl(QUrl("www.dummy.one")); + + QPlaceImage dummyImage2; + dummyImage2.setUrl(QUrl("www.dummy.two")); + + QPlaceImage dummyImage3; + dummyImage3.setUrl(QUrl("www.dummy.three")); + + QPlaceContent::Collection imageCollection; + imageCollection.insert(0,dummyImage); + imageCollection.insert(1, dummyImage2); + imageCollection.insert(2, dummyImage3); + + place.setContent(QPlaceContent::ImageType, imageCollection); + QPlaceContent::Collection retrievedCollection = place.content(QPlaceContent::ImageType); + + QCOMPARE(retrievedCollection.count(), 3); + QCOMPARE(QPlaceImage(retrievedCollection.value(0)), dummyImage); + QCOMPARE(QPlaceImage(retrievedCollection.value(1)), dummyImage2); + QCOMPARE(QPlaceImage(retrievedCollection.value(2)), dummyImage3); + + //replace the second and insert a sixth image + //indexes 4 and 5 are "missing" + QPlaceImage dummyImage2New; + dummyImage2.setUrl(QUrl("www.dummy.two.new")); + + QPlaceImage dummyImage6; + dummyImage6.setUrl(QUrl("www.dummy.six")); + + imageCollection.clear(); + imageCollection.insert(1, dummyImage2New); + imageCollection.insert(5, dummyImage6); + place.insertContent(QPlaceContent::ImageType, imageCollection); + + retrievedCollection = place.content(QPlaceContent::ImageType); + QCOMPARE(retrievedCollection.count(), 4); + QCOMPARE(QPlaceImage(retrievedCollection.value(0)), dummyImage); + QCOMPARE(QPlaceImage(retrievedCollection.value(1)), dummyImage2New); + QCOMPARE(QPlaceImage(retrievedCollection.value(2)), dummyImage3); + QCOMPARE(QPlaceImage(retrievedCollection.value(3)), QPlaceImage()); + QCOMPARE(QPlaceImage(retrievedCollection.value(4)), QPlaceImage()); + QCOMPARE(QPlaceImage(retrievedCollection.value(5)), dummyImage6); +} + +void tst_Place::reviewContentTest() +{ + QPlace place; + QVERIFY2(place.content(QPlaceContent::ReviewType).count() ==0,"Wrong default value"); + + QPlaceReview dummyReview; + dummyReview.setTitle(QStringLiteral("Review 1")); + + QPlaceReview dummyReview2; + dummyReview2.setTitle(QStringLiteral("Review 2")); + + QPlaceReview dummyReview3; + dummyReview3.setTitle(QStringLiteral("Review 3")); + + QPlaceContent::Collection reviewCollection; + reviewCollection.insert(0,dummyReview); + reviewCollection.insert(1, dummyReview2); + reviewCollection.insert(2, dummyReview3); + + place.setContent(QPlaceContent::ReviewType, reviewCollection); + QPlaceContent::Collection retrievedCollection = place.content(QPlaceContent::ReviewType); + + QCOMPARE(retrievedCollection.count(), 3); + QCOMPARE(QPlaceReview(retrievedCollection.value(0)), dummyReview); + QCOMPARE(QPlaceReview(retrievedCollection.value(1)), dummyReview2); + QCOMPARE(QPlaceReview(retrievedCollection.value(2)), dummyReview3); + + //replace the second and insert a sixth review + //indexes 4 and 5 are "missing" + QPlaceReview dummyReview2New; + dummyReview2.setTitle(QStringLiteral("Review 2 new")); + + QPlaceReview dummyReview6; + dummyReview6.setTitle(QStringLiteral("Review 6")); + + reviewCollection.clear(); + reviewCollection.insert(1, dummyReview2New); + reviewCollection.insert(5, dummyReview6); + place.insertContent(QPlaceContent::ReviewType, reviewCollection); + + retrievedCollection = place.content(QPlaceContent::ReviewType); + QCOMPARE(retrievedCollection.count(), 4); + QCOMPARE(QPlaceReview(retrievedCollection.value(0)), dummyReview); + QCOMPARE(QPlaceReview(retrievedCollection.value(1)), dummyReview2New); + QCOMPARE(QPlaceReview(retrievedCollection.value(2)), dummyReview3); + QCOMPARE(QPlaceReview(retrievedCollection.value(3)), QPlaceReview()); + QCOMPARE(QPlaceReview(retrievedCollection.value(4)), QPlaceReview()); + QCOMPARE(QPlaceReview(retrievedCollection.value(5)), dummyReview6); +} + +void tst_Place::editorialContentTest() +{ + QPlace place; + QVERIFY2(place.content(QPlaceContent::EditorialType).count() == 0, "Wrong default value"); + + QPlaceEditorial dummyEditorial; + dummyEditorial.setTitle(QStringLiteral("Editorial 1")); + + QPlaceEditorial dummyEditorial2; + dummyEditorial2.setTitle(QStringLiteral("Editorial 2")); + + QPlaceEditorial dummyEditorial3; + dummyEditorial3.setTitle(QStringLiteral("Editorial 3")); + + QPlaceContent::Collection editorialCollection; + editorialCollection.insert(0,dummyEditorial); + editorialCollection.insert(1, dummyEditorial2); + editorialCollection.insert(2, dummyEditorial3); + + place.setContent(QPlaceContent::EditorialType, editorialCollection); + QPlaceContent::Collection retrievedCollection = place.content(QPlaceContent::EditorialType); + + QCOMPARE(retrievedCollection.count(), 3); + QCOMPARE(QPlaceEditorial(retrievedCollection.value(0)), dummyEditorial); + QCOMPARE(QPlaceEditorial(retrievedCollection.value(1)), dummyEditorial2); + QCOMPARE(QPlaceEditorial(retrievedCollection.value(2)), dummyEditorial3); + + //replace the second and insert a sixth editorial + //indexes 4 and 5 are "missing" + QPlaceEditorial dummyEditorial2New; + dummyEditorial2.setTitle(QStringLiteral("Editorial 2 new")); + + QPlaceEditorial dummyEditorial6; + dummyEditorial6.setTitle(QStringLiteral("Editorial 6")); + + editorialCollection.clear(); + editorialCollection.insert(1, dummyEditorial2New); + editorialCollection.insert(5, dummyEditorial6); + place.insertContent(QPlaceContent::EditorialType, editorialCollection); + + retrievedCollection = place.content(QPlaceContent::EditorialType); + QCOMPARE(retrievedCollection.count(), 4); + QCOMPARE(QPlaceEditorial(retrievedCollection.value(0)), dummyEditorial); + QCOMPARE(QPlaceEditorial(retrievedCollection.value(1)), dummyEditorial2New); + QCOMPARE(QPlaceEditorial(retrievedCollection.value(2)), dummyEditorial3); + QCOMPARE(QPlaceEditorial(retrievedCollection.value(3)), QPlaceEditorial()); + QCOMPARE(QPlaceEditorial(retrievedCollection.value(4)), QPlaceEditorial()); + QCOMPARE(QPlaceEditorial(retrievedCollection.value(5)), dummyEditorial6); +} + +void tst_Place::categoriesTest() +{ + QPlace place; + QVERIFY(place.categories().isEmpty()); + + //set a single category + QPlaceCategory cat1; + cat1.setName("cat1"); + + place.setCategory(cat1); + QCOMPARE(place.categories().count(), 1); + QCOMPARE(place.categories().at(0), cat1); + + //set multiple categories + QPlaceCategory cat2; + cat2.setName("cat2"); + + QPlaceCategory cat3; + cat3.setName("cat3"); + + QList categories; + categories << cat2 << cat3; + + place.setCategories(categories); + QCOMPARE(place.categories().count(), 2); + QVERIFY(place.categories().contains(cat2)); + QVERIFY(place.categories().contains(cat3)); + + //set a single category again while there are multiple categories already assigned. + place.setCategory(cat1); + QCOMPARE(place.categories().count(), 1); + QCOMPARE(place.categories().at(0), cat1); + + //set an empty list of categories + place.setCategories(QList()); + QVERIFY(place.categories().isEmpty()); +} + +void tst_Place::supplierTest() +{ + QPlace testObj; + QCOMPARE(testObj.supplier(), QPlaceSupplier()); + + QPlaceSupplier sup; + sup.setName("testName1"); + sup.setSupplierId("testId"); + + testObj.setSupplier(sup); + + QCOMPARE(testObj.supplier(), sup); +} + +void tst_Place::attributionTest() +{ + QPlace testPlace; + QVERIFY(testPlace.attribution().isEmpty()); + testPlace.setAttribution(QStringLiteral("attribution")); + QCOMPARE(testPlace.attribution(), QStringLiteral("attribution")); + testPlace.setAttribution(QString()); + QVERIFY(testPlace.attribution().isEmpty()); +} + +void tst_Place::contactDetailsTest() +{ + QPlaceContactDetail phone1; + phone1.setLabel("Phone1"); + phone1.setValue("555-5555"); + + QPlaceContactDetail phone2; + phone2.setLabel("Phone2"); + phone2.setValue("555-5556"); + + QList phones; + phones << phone1 << phone2; + + + QPlaceContactDetail email; + email.setLabel("Email"); + email.setValue("email@email.com"); + + QPlace place; + place.setContactDetails(QPlaceContactDetail::Phone,phones); + QCOMPARE(place.contactTypes().count(), 1); + QVERIFY(place.contactTypes().contains(QPlaceContactDetail::Phone)); + QCOMPARE(place.contactDetails(QPlaceContactDetail::Phone), phones); + + place.appendContactDetail(QPlaceContactDetail::Email, email); + QCOMPARE(place.contactTypes().count(), 2); + QVERIFY(place.contactTypes().contains(QPlaceContactDetail::Phone)); + QVERIFY(place.contactTypes().contains(QPlaceContactDetail::Email)); + QCOMPARE(place.contactDetails(QPlaceContactDetail::Phone), phones); + QCOMPARE(place.contactDetails(QPlaceContactDetail::Email).count(), 1); + QCOMPARE(place.contactDetails(QPlaceContactDetail::Email).at(0), email); + + place.removeContactDetails(QPlaceContactDetail::Phone); + QCOMPARE(place.contactTypes().count(), 1); + QVERIFY(!place.contactTypes().contains(QPlaceContactDetail::Phone)); + QVERIFY(place.contactDetails(QPlaceContactDetail::Phone).isEmpty()); + QVERIFY(place.contactTypes().contains(QPlaceContactDetail::Email)); + QCOMPARE(place.contactDetails(QPlaceContactDetail::Email).count(), 1); + QCOMPARE(place.contactDetails(QPlaceContactDetail::Email).at(0), email); + + place.removeContactDetails(QPlaceContactDetail::Email); + QVERIFY(place.contactTypes().isEmpty()); + QVERIFY(place.contactDetails(QPlaceContactDetail::Email).isEmpty()); +} + +void tst_Place::primaryPhoneTest() +{ + QPlace place; + QVERIFY2(place.primaryPhone().isEmpty(), "Wrong default value"); + + QPlaceContactDetail contactDetail; + contactDetail.setLabel(QStringLiteral("Phone")); + contactDetail.setValue(QStringLiteral("555-5555")); + place.appendContactDetail(QPlaceContactDetail::Phone, contactDetail); + + QCOMPARE(place.primaryPhone(), QString("555-5555")); + + //try clearing the primary phone number + place.setContactDetails(QPlaceContactDetail::Phone, QList()); + QCOMPARE(place.primaryPhone(), QString()); +} + +void tst_Place::primaryEmailTest() +{ + QPlace place; + QVERIFY2(place.primaryEmail().isEmpty(), "Wrong default value"); + + QPlaceContactDetail contactDetail; + contactDetail.setLabel(QStringLiteral("Email")); + contactDetail.setValue(QStringLiteral("test@test.com")); + place.appendContactDetail(QPlaceContactDetail::Email, contactDetail); + + QCOMPARE(place.primaryEmail(), QStringLiteral("test@test.com")); + + //try clearing the primary email address + place.setContactDetails(QPlaceContactDetail::Email, QList()); + QCOMPARE(place.primaryEmail(), QString()); +} + +void tst_Place::primaryFaxTest() +{ + QPlace place; + QVERIFY2(place.primaryFax().isEmpty(), "Wrong default value"); + + QPlaceContactDetail contactDetail; + contactDetail.setLabel(QStringLiteral("Fax")); + contactDetail.setValue(QStringLiteral("555-5555")); + place.appendContactDetail(QPlaceContactDetail::Fax, contactDetail); + + QCOMPARE(place.primaryFax(), QStringLiteral("555-5555")); + + //try clearing the primary fax number + place.setContactDetails(QPlaceContactDetail::Fax, QList()); + QCOMPARE(place.primaryFax(), QString()); +} + +void tst_Place::primaryWebsiteTest() +{ + QPlace place; + QVERIFY2(place.primaryWebsite().isEmpty(), "Wrong default value"); + + QPlaceContactDetail contactDetail; + contactDetail.setLabel(QStringLiteral("Website")); + contactDetail.setValue(QStringLiteral("www.example.com")); + place.appendContactDetail(QPlaceContactDetail::Website, contactDetail); + + QCOMPARE(place.primaryWebsite(), QUrl("www.example.com")); + + //try clearing the primary website number + place.setContactDetails(QPlaceContactDetail::Website, QList()); + QCOMPARE(place.primaryWebsite(), QUrl()); +} + +void tst_Place::operatorsTest() +{ + QPlace testObj; + testObj.setPlaceId("testId"); + QPlaceAttribute paymentMethods; + paymentMethods.setLabel("Payment methods"); + paymentMethods.setText("Visa"); + testObj.setExtendedAttribute(QStringLiteral("paymentMethods"), paymentMethods); + QGeoLocation loc; + loc.setCoordinate(QGeoCoordinate(10,20)); + testObj.setLocation(loc); + + QPlace testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setPlaceId("342-456"); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +void tst_Place::extendedAttributeTest() +{ + QPlace place; + QVERIFY(place.extendedAttributeTypes().isEmpty()); + QPlaceAttribute smoking; + smoking.setLabel(QStringLiteral("Public Smoking")); + smoking.setText(QStringLiteral("No")); + + //test setting of an attribue + place.setExtendedAttribute(QStringLiteral("smoking"), smoking); + + QVERIFY(place.extendedAttributeTypes().contains(QStringLiteral("smoking"))); + QCOMPARE(place.extendedAttributeTypes().count(), 1); + + QCOMPARE(place.extendedAttribute(QStringLiteral("smoking")).label(), QStringLiteral("Public Smoking")); + QCOMPARE(place.extendedAttribute(QStringLiteral("smoking")).text(), QStringLiteral("No")); + + QPlaceAttribute shelter; + shelter.setLabel(QStringLiteral("Outdoor shelter")); + shelter.setText(QStringLiteral("Yes")); + + //test setting another new attribute + place.setExtendedAttribute("shelter", shelter); + QVERIFY(place.extendedAttributeTypes().contains(QStringLiteral("shelter"))); + QVERIFY(place.extendedAttributeTypes().contains(QStringLiteral("smoking"))); + QCOMPARE(place.extendedAttributeTypes().count(), 2); + QCOMPARE(place.extendedAttribute(QStringLiteral("shelter")).label(), QStringLiteral("Outdoor shelter")); + QCOMPARE(place.extendedAttribute(QStringLiteral("shelter")).text(), QStringLiteral("Yes")); + + //test overwriting an attribute + shelter.setText(QStringLiteral("No")); + place.setExtendedAttribute(QStringLiteral("shelter"), shelter); + + QVERIFY(place.extendedAttributeTypes().contains(QStringLiteral("shelter"))); + QVERIFY(place.extendedAttributeTypes().contains(QStringLiteral("smoking"))); + QCOMPARE(place.extendedAttributeTypes().count(), 2); + QCOMPARE(place.extendedAttribute(QStringLiteral("shelter")).text(), QStringLiteral("No")); + + //test clearing of attributes by setting them to the default attribute + foreach (const QString &attributeType, place.extendedAttributeTypes()) + place.setExtendedAttribute(attributeType, QPlaceAttribute()); + + QCOMPARE(place.extendedAttributeTypes().count(), 0); + + //test removing of attributes + place.setExtendedAttribute(QStringLiteral("smoking"), smoking); + QVERIFY(!place.extendedAttributeTypes().isEmpty()); + place.removeExtendedAttribute(QStringLiteral("smoking")); + QVERIFY(place.extendedAttributeTypes().isEmpty()); +} +void tst_Place::visibilityTest() +{ + QPlace place; + + QCOMPARE(place.visibility(), QLocation::UnspecifiedVisibility); + + place.setVisibility(QLocation::DeviceVisibility); + + QCOMPARE(place.visibility(), QLocation::DeviceVisibility); +} + +void tst_Place::isEmptyTest() +{ + QGeoLocation location; + location.setCoordinate(QGeoCoordinate(6.788697, 51.224679)); + QVERIFY(!location.isEmpty()); + + QPlaceCategory category; + + QPlaceRatings ratings; + ratings.setCount(1); + QVERIFY(!ratings.isEmpty()); + + QPlaceSupplier supplier; + supplier.setName(QStringLiteral("Foo & Bar Imports")); + QVERIFY(!supplier.isEmpty()); + + QPlaceIcon icon; + QVariantMap iconParametersMap; + iconParametersMap.insert(QStringLiteral("Para"), QStringLiteral("meter")); + icon.setParameters(iconParametersMap); + QVERIFY(!icon.isEmpty()); + + QPlaceContent content; + QPlaceContent::Collection contentCollection; + contentCollection.insert(42, content); + + QPlaceAttribute attribute; + attribute.setLabel(QStringLiteral("noodle")); + + QPlaceContactDetail contactDetail; + + + QPlace place; + + // default constructed + QVERIFY(place.isEmpty()); + + // categories + place.setCategory(category); + QVERIFY(!place.isEmpty()); + place.categories().clear(); + place = QPlace(); + + // location + place.setLocation(location); + QVERIFY(!place.isEmpty()); + place.setLocation(QGeoLocation()); + QVERIFY(place.isEmpty()); + + // ratings + place.setRatings(ratings); + QVERIFY(!place.isEmpty()); + place.setRatings(QPlaceRatings()); + QVERIFY(place.isEmpty()); + + // supplier + place.setSupplier(supplier); + QVERIFY(!place.isEmpty()); + place.setSupplier(QPlaceSupplier()); + QVERIFY(place.isEmpty()); + + // attribution + place.setAttribution(QStringLiteral("attr")); + QVERIFY(!place.isEmpty()); + place.setAttribution(QString()); + QVERIFY(place.isEmpty()); + + // icon + place.setIcon(icon); + QVERIFY(!place.isEmpty()); + place.setIcon(QPlaceIcon()); + QVERIFY(place.isEmpty()); + + // content + place.insertContent(QPlaceContent::EditorialType, contentCollection); + QVERIFY(!place.isEmpty()); + place = QPlace(); + + // name + place.setName(QStringLiteral("Naniwa")); + QVERIFY(!place.isEmpty()); + place.setName(QString()); + QVERIFY(place.isEmpty()); + + // placeId + place.setPlaceId(QStringLiteral("naniwa")); + QVERIFY(!place.isEmpty()); + place.setPlaceId(QString()); + QVERIFY(place.isEmpty()); + + // extendedAttributes + place.setExtendedAttribute(QStringLiteral("part"), attribute); + QVERIFY(!place.isEmpty()); + place.removeExtendedAttribute(QStringLiteral("part")); + QVERIFY(place.isEmpty()); + + // extendedAttributes + place.setDetailsFetched(true); + QVERIFY(place.isEmpty()); + + // contact detail + place.appendContactDetail(QStringLiteral("phone"), contactDetail); + QVERIFY(!place.isEmpty()); + place.removeContactDetails(QStringLiteral("phone")); + QVERIFY(place.isEmpty()); + + // visibility + place.setVisibility(QLocation::DeviceVisibility); + QVERIFY(!place.isEmpty()); + place.setVisibility(QLocation::UnspecifiedVisibility); + QVERIFY(place.isEmpty()); +} + +QTEST_APPLESS_MAIN(tst_Place) + +#include "tst_qplace.moc" diff --git a/tests/auto/qplaceattribute/qplaceattribute.pro b/tests/auto/qplaceattribute/qplaceattribute.pro new file mode 100644 index 0000000..4fc27e5 --- /dev/null +++ b/tests/auto/qplaceattribute/qplaceattribute.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplaceattribute + +SOURCES += tst_qplaceattribute.cpp + +QT += location testlib diff --git a/tests/auto/qplaceattribute/tst_qplaceattribute.cpp b/tests/auto/qplaceattribute/tst_qplaceattribute.cpp new file mode 100644 index 0000000..1f229ab --- /dev/null +++ b/tests/auto/qplaceattribute/tst_qplaceattribute.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class tst_QPlaceAttribute : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceAttribute(); + +private Q_SLOTS: + void constructorTest(); + void operatorsTest(); + void isEmptyTest(); +}; + +tst_QPlaceAttribute::tst_QPlaceAttribute() +{ +} + +void tst_QPlaceAttribute::constructorTest() +{ + QPlaceAttribute testObj; + + QPlaceAttribute *testObjPtr = new QPlaceAttribute(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(testObjPtr->label() == QString(), "Copy constructor - wrong label"); + QVERIFY2(testObjPtr->text() == QString(), "Copy constructor - wrong text"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + delete testObjPtr; +} + +void tst_QPlaceAttribute::operatorsTest() +{ + QPlaceAttribute testObj; + testObj.setLabel(QStringLiteral("label")); + QPlaceAttribute testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setText(QStringLiteral("text")); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +void tst_QPlaceAttribute::isEmptyTest() +{ + QPlaceAttribute attribute; + + QVERIFY(attribute.isEmpty()); + + attribute.setLabel(QStringLiteral("label")); + QVERIFY(!attribute.isEmpty()); + attribute.setLabel(QString()); + QVERIFY(attribute.isEmpty()); + + attribute.setText(QStringLiteral("text")); + QVERIFY(!attribute.isEmpty()); + attribute.setText(QString()); + QVERIFY(attribute.isEmpty()); +} + +QTEST_APPLESS_MAIN(tst_QPlaceAttribute); + +#include "tst_qplaceattribute.moc" diff --git a/tests/auto/qplacecategory/qplacecategory.pro b/tests/auto/qplacecategory/qplacecategory.pro new file mode 100644 index 0000000..43686ec --- /dev/null +++ b/tests/auto/qplacecategory/qplacecategory.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacecategory + +SOURCES += tst_qplacecategory.cpp + +QT += location testlib diff --git a/tests/auto/qplacecategory/tst_qplacecategory.cpp b/tests/auto/qplacecategory/tst_qplacecategory.cpp new file mode 100644 index 0000000..8a84d64 --- /dev/null +++ b/tests/auto/qplacecategory/tst_qplacecategory.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include +#include + +QT_USE_NAMESPACE + +class tst_QPlaceCategory : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceCategory(); + +private Q_SLOTS: + void constructorTest(); + void categoryIdTest(); + void nameTest(); + void visibilityTest(); + void operatorsTest(); + void isEmptyTest(); +}; + +tst_QPlaceCategory::tst_QPlaceCategory() +{ +} + +void tst_QPlaceCategory::constructorTest() +{ + QPlaceCategory testObj; + Q_UNUSED(testObj); + + testObj.setCategoryId("testId"); + QPlaceCategory *testObjPtr = new QPlaceCategory(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + delete testObjPtr; +} + +void tst_QPlaceCategory::categoryIdTest() +{ + QPlaceCategory testObj; + QVERIFY2(testObj.categoryId() == QString(), "Wrong default value"); + testObj.setCategoryId("testText"); + QVERIFY2(testObj.categoryId() == "testText", "Wrong value returned"); +} + +void tst_QPlaceCategory::nameTest() +{ + QPlaceCategory testObj; + QVERIFY2(testObj.name() == QString(), "Wrong default value"); + testObj.setName("testText"); + QVERIFY2(testObj.name() == "testText", "Wrong value returned"); +} + +void tst_QPlaceCategory::visibilityTest() +{ + QPlaceCategory category; + + QCOMPARE(category.visibility(), QLocation::UnspecifiedVisibility); + + category.setVisibility(QLocation::DeviceVisibility); + + QCOMPARE(category.visibility(), QLocation::DeviceVisibility); +} + +void tst_QPlaceCategory::operatorsTest() +{ + QPlaceCategory testObj; + testObj.setName("testValue"); + QPlaceCategory testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setCategoryId("a3rfg"); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +void tst_QPlaceCategory::isEmptyTest() +{ + QPlaceIcon icon; + QVariantMap parameters; + parameters.insert(QStringLiteral("para"), QStringLiteral("meter")); + icon.setParameters(parameters); + QVERIFY(!icon.isEmpty()); + + QPlaceCategory category; + + QVERIFY(category.isEmpty()); + + category.setName(QStringLiteral("name")); + QVERIFY(!category.isEmpty()); + category.setName(QString()); + QVERIFY(category.isEmpty()); + + category.setCategoryId(QStringLiteral("id")); + QVERIFY(!category.isEmpty()); + category.setCategoryId(QString()); + QVERIFY(category.isEmpty()); + + category.setVisibility(QLocation::PublicVisibility); + QVERIFY(!category.isEmpty()); + category.setVisibility(QLocation::UnspecifiedVisibility); + QVERIFY(category.isEmpty()); + + category.setIcon(icon); + QVERIFY(!category.isEmpty()); + category.setIcon(QPlaceIcon()); + QVERIFY(category.isEmpty()); +} + +QTEST_APPLESS_MAIN(tst_QPlaceCategory); + +#include "tst_qplacecategory.moc" diff --git a/tests/auto/qplacecontactdetail/qplacecontactdetail.pro b/tests/auto/qplacecontactdetail/qplacecontactdetail.pro new file mode 100644 index 0000000..ab49471 --- /dev/null +++ b/tests/auto/qplacecontactdetail/qplacecontactdetail.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacecontactdetail + +SOURCES += tst_qplacecontactdetail.cpp + +QT += location testlib diff --git a/tests/auto/qplacecontactdetail/tst_qplacecontactdetail.cpp b/tests/auto/qplacecontactdetail/tst_qplacecontactdetail.cpp new file mode 100644 index 0000000..370bb5e --- /dev/null +++ b/tests/auto/qplacecontactdetail/tst_qplacecontactdetail.cpp @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class tst_QPlaceContactDetail : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceContactDetail(); + +private Q_SLOTS: + void constructorTest(); + void labelTest(); + void valueTest(); + void clearTest(); + void operatorsTest(); + void operatorsTest_data(); +}; + +tst_QPlaceContactDetail::tst_QPlaceContactDetail() +{ +} + +void tst_QPlaceContactDetail::constructorTest() +{ + QPlaceContactDetail detail; + QVERIFY(detail.label().isEmpty()); + QVERIFY(detail.value().isEmpty()); + + detail.setLabel(QStringLiteral("Emergency Services")); + detail.setValue(QStringLiteral("0118 999")); + + QPlaceContactDetail detail2(detail); + QCOMPARE(detail2.label(), QStringLiteral("Emergency Services")); + QCOMPARE(detail2.value(), QStringLiteral("0118 999")); +} + +void tst_QPlaceContactDetail::labelTest() +{ + QPlaceContactDetail detail; + detail.setLabel(QStringLiteral("home")); + QCOMPARE(detail.label(), QStringLiteral("home")); + detail.setLabel(QString()); + QVERIFY(detail.label().isEmpty()); +} + +void tst_QPlaceContactDetail::valueTest() +{ + QPlaceContactDetail detail; + detail.setValue(QStringLiteral("555-5555")); + QCOMPARE(detail.value(), QStringLiteral("555-5555")); + detail.setValue(QString()); + QVERIFY(detail.value().isEmpty()); +} + +void tst_QPlaceContactDetail::clearTest() +{ + QPlaceContactDetail detail; + detail.setLabel(QStringLiteral("Ghostbusters")); + detail.setValue(QStringLiteral("555-2368")); + detail.clear(); + QVERIFY(detail.label().isEmpty()); + QVERIFY(detail.value().isEmpty()); +} + +void tst_QPlaceContactDetail::operatorsTest() +{ + QPlaceContactDetail detail1; + detail1.setLabel(QStringLiteral("Kramer")); + detail1.setValue(QStringLiteral("555-filk")); + + QPlaceContactDetail detail2; + detail2.setLabel(QStringLiteral("Kramer")); + detail2.setValue(QStringLiteral("555-filk")); + + QVERIFY(detail1 == detail2); + QVERIFY(!(detail1 != detail2)); + QVERIFY(detail2 == detail1); + QVERIFY(!(detail2 != detail1)); + + QPlaceContactDetail detail3; + QVERIFY(!(detail1 == detail3)); + QVERIFY(detail1 != detail3); + QVERIFY(!(detail1 == detail3)); + QVERIFY(detail1 != detail3); + + detail3 = detail1; + QVERIFY(detail1 == detail3); + QVERIFY(!(detail1 != detail3)); + QVERIFY(detail3 == detail1); + QVERIFY(!(detail3 != detail1)); + + QFETCH(QString, field); + if (field == QStringLiteral("label")) + detail3.setLabel(QStringLiteral("Cosmo")); + else if (field == QStringLiteral("value")) + detail3.setValue(QStringLiteral("555-5555")); + + QVERIFY(!(detail1 == detail3)); + QVERIFY(detail1 != detail3); + QVERIFY(!(detail3 == detail1)); + QVERIFY(detail3 != detail1); +} + +void tst_QPlaceContactDetail::operatorsTest_data() +{ + QTest::addColumn("field"); + QTest::newRow("label") << "label"; + QTest::newRow("value") << "value"; +} + +QTEST_APPLESS_MAIN(tst_QPlaceContactDetail) + +#include "tst_qplacecontactdetail.moc" diff --git a/tests/auto/qplacecontentrequest/qplacecontentrequest.pro b/tests/auto/qplacecontentrequest/qplacecontentrequest.pro new file mode 100644 index 0000000..6e27c4e --- /dev/null +++ b/tests/auto/qplacecontentrequest/qplacecontentrequest.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacecontentrequest +SOURCES += tst_qplacecontentrequest.cpp + +QT += location testlib diff --git a/tests/auto/qplacecontentrequest/tst_qplacecontentrequest.cpp b/tests/auto/qplacecontentrequest/tst_qplacecontentrequest.cpp new file mode 100644 index 0000000..46bfb9e --- /dev/null +++ b/tests/auto/qplacecontentrequest/tst_qplacecontentrequest.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class tst_QPlaceContentRequest : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceContentRequest(); + +private Q_SLOTS: + void contentTest(); + void clearTest(); +}; + +tst_QPlaceContentRequest::tst_QPlaceContentRequest() +{ +} + +void tst_QPlaceContentRequest::contentTest() +{ + QPlaceContentRequest req; + QCOMPARE(req.limit(), -1); + QCOMPARE(req.contentType(), QPlaceContent::NoType); + + //check that we can set the request fields + req.setLimit(100); + req.setContentType(QPlaceContent::ImageType); + QCOMPARE(req.limit(), 100); + QCOMPARE(req.contentType(), QPlaceContent::ImageType); + + //check that we assignment works correctly + QPlaceContentRequest otherReq; + otherReq.setLimit(10); + otherReq.setContentType(QPlaceContent::ReviewType); + req = otherReq; + QCOMPARE(req.limit(), 10); + QCOMPARE(req.contentType(), QPlaceContent::ReviewType); + QCOMPARE(req, otherReq); + + //check that comparison will fail if one the fields are different + req.setLimit(9000); + QVERIFY(req != otherReq); +} + +void tst_QPlaceContentRequest::clearTest() +{ + QPlaceContentRequest req; + req.setContentType(QPlaceContent::ReviewType); + req.setLimit(9000); + req.clear(); + QVERIFY(req.contentType() == QPlaceContent::NoType); + QVERIFY(req.limit() == -1); +} + +QTEST_APPLESS_MAIN(tst_QPlaceContentRequest) + +#include "tst_qplacecontentrequest.moc" diff --git a/tests/auto/qplacedetailsreply/qplacedetailsreply.pro b/tests/auto/qplacedetailsreply/qplacedetailsreply.pro new file mode 100644 index 0000000..05daa2f --- /dev/null +++ b/tests/auto/qplacedetailsreply/qplacedetailsreply.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacedetailsreply + +SOURCES += tst_qplacedetailsreply.cpp + +QT += location testlib diff --git a/tests/auto/qplacedetailsreply/tst_qplacedetailsreply.cpp b/tests/auto/qplacedetailsreply/tst_qplacedetailsreply.cpp new file mode 100644 index 0000000..5ea509b --- /dev/null +++ b/tests/auto/qplacedetailsreply/tst_qplacedetailsreply.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class TestDetailsReply : public QPlaceDetailsReply +{ + Q_OBJECT +public: + TestDetailsReply(QObject *parent) : QPlaceDetailsReply(parent){} + ~TestDetailsReply() {} + void setPlace(const QPlace &place) { QPlaceDetailsReply::setPlace(place); } +}; + +class tst_QPlaceDetailsReply : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceDetailsReply(); + +private Q_SLOTS: + void constructorTest(); + void typeTest(); + void placeTest(); +}; + +tst_QPlaceDetailsReply::tst_QPlaceDetailsReply() +{ +} + +void tst_QPlaceDetailsReply::constructorTest() +{ + TestDetailsReply *reply = new TestDetailsReply(this); + QVERIFY(reply->parent() == this); + delete reply; +} + +void tst_QPlaceDetailsReply::typeTest() +{ + TestDetailsReply *reply = new TestDetailsReply(this); + QCOMPARE(reply->type(), QPlaceReply::DetailsReply); + delete reply; +} + +void tst_QPlaceDetailsReply::placeTest() +{ + TestDetailsReply *reply = new TestDetailsReply(this); + QPlace place; + place.setName(QStringLiteral("Gotham City")); + reply->setPlace(place); + + QCOMPARE(reply->place(), place); + delete reply; +} + +QTEST_APPLESS_MAIN(tst_QPlaceDetailsReply) + +#include "tst_qplacedetailsreply.moc" diff --git a/tests/auto/qplaceeditorial/qplaceeditorial.pro b/tests/auto/qplaceeditorial/qplaceeditorial.pro new file mode 100644 index 0000000..703fa8b --- /dev/null +++ b/tests/auto/qplaceeditorial/qplaceeditorial.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplaceeditorial + +SOURCES += tst_qplaceeditorial.cpp + +QT += location testlib diff --git a/tests/auto/qplaceeditorial/tst_qplaceeditorial.cpp b/tests/auto/qplaceeditorial/tst_qplaceeditorial.cpp new file mode 100644 index 0000000..1c20bd2 --- /dev/null +++ b/tests/auto/qplaceeditorial/tst_qplaceeditorial.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include +#include +#include + +#include "../utils/qlocationtestutils_p.h" + +QT_USE_NAMESPACE + +class tst_QPlaceEditorial : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceEditorial(); + + //needed for QLocationTestUtils::testConversion + QPlaceEditorial initialSubObject(); + bool checkType(const QPlaceContent &); + void detach(QPlaceContent *); + void setSubClassProperty(QPlaceEditorial *); + +private Q_SLOTS: + void constructorTest(); + void supplierTest(); + void textTest(); + void titleTest(); + void languageTest(); + void operatorsTest(); + void conversionTest(); +}; + +tst_QPlaceEditorial::tst_QPlaceEditorial() +{ +} + +QPlaceEditorial tst_QPlaceEditorial::initialSubObject() +{ + QPlaceUser user; + user.setName("user 1"); + user.setUserId("0001"); + + QPlaceSupplier supplier; + supplier.setName("supplier"); + supplier.setSupplierId("1"); + + QPlaceEditorial editorial; + editorial.setTitle("title"); + editorial.setText("text"); + editorial.setLanguage("en"); + editorial.setUser(user); + editorial.setSupplier(supplier); + editorial.setAttribution("attribution"); + + return editorial; +} + +bool tst_QPlaceEditorial::checkType(const QPlaceContent &content) +{ + return content.type() == QPlaceContent::EditorialType; +} + +void tst_QPlaceEditorial::detach(QPlaceContent *content) +{ + content->setAttribution("attribution"); +} + +void tst_QPlaceEditorial::setSubClassProperty(QPlaceEditorial * editorial) +{ + editorial->setTitle("new title"); +} +void tst_QPlaceEditorial::constructorTest() +{ + QPlaceEditorial testObj; + testObj.setText("testId"); + QPlaceEditorial *testObjPtr = new QPlaceEditorial(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + delete testObjPtr; +} + +void tst_QPlaceEditorial::supplierTest() +{ + QPlaceEditorial testObj; + QVERIFY2(testObj.supplier().supplierId() == QString(), "Wrong default value"); + QPlaceSupplier sup; + sup.setName("testName1"); + sup.setSupplierId("testId"); + testObj.setSupplier(sup); + QVERIFY2(testObj.supplier() == sup, "Wrong value returned"); +} + +void tst_QPlaceEditorial::textTest() +{ + QPlaceEditorial testObj; + QVERIFY2(testObj.text() == QString(), "Wrong default value"); + testObj.setText("testText"); + QVERIFY2(testObj.text() == "testText", "Wrong value returned"); +} + +void tst_QPlaceEditorial::titleTest() +{ + QPlaceEditorial testObj; + QVERIFY2(testObj.title() == QString(), "Wrong default value"); + testObj.setTitle("testText"); + QVERIFY2(testObj.title() == "testText", "Wrong value returned"); +} + +void tst_QPlaceEditorial::languageTest() +{ + QPlaceEditorial testObj; + QVERIFY2(testObj.language() == QString(), "Wrong default value"); + testObj.setLanguage("testText"); + QVERIFY2(testObj.language() == "testText", "Wrong value returned"); +} + +void tst_QPlaceEditorial::operatorsTest() +{ + QPlaceEditorial testObj; + testObj.setLanguage("testValue"); + QPlaceEditorial testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setText("testValue2"); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +void tst_QPlaceEditorial::conversionTest() +{ + QLocationTestUtils::testConversion(this); +} +QTEST_APPLESS_MAIN(tst_QPlaceEditorial); + +#include "tst_qplaceeditorial.moc" diff --git a/tests/auto/qplaceimage/qplaceimage.pro b/tests/auto/qplaceimage/qplaceimage.pro new file mode 100644 index 0000000..b6a2805 --- /dev/null +++ b/tests/auto/qplaceimage/qplaceimage.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplaceimage + +SOURCES += tst_qplaceimage.cpp + +QT += location testlib diff --git a/tests/auto/qplaceimage/tst_qplaceimage.cpp b/tests/auto/qplaceimage/tst_qplaceimage.cpp new file mode 100644 index 0000000..aa3f9df --- /dev/null +++ b/tests/auto/qplaceimage/tst_qplaceimage.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#include "../utils/qlocationtestutils_p.h" + +QT_USE_NAMESPACE + +class tst_QPlaceImage : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceImage(); + + //needed for QLocationTestUtils::testConversion + QPlaceImage initialSubObject(); + bool checkType(const QPlaceContent &); + void detach(QPlaceContent *); + void setSubClassProperty(QPlaceImage *); + +private Q_SLOTS: + void constructorTest(); + void supplierTest(); + void idTest(); + void mimeTypeTest(); + void attributionTest(); + void operatorsTest(); + void conversionTest(); +}; + +tst_QPlaceImage::tst_QPlaceImage() +{ +} + +QPlaceImage tst_QPlaceImage::initialSubObject() +{ + QPlaceUser user; + user.setName("user 1"); + user.setUserId("0001"); + + QPlaceSupplier supplier; + supplier.setName("supplier"); + supplier.setSupplierId("1"); + + QPlaceImage image; + image.setUrl(QUrl(QStringLiteral("file:///opt/icon/img.png"))); + image.setImageId("0001"); + image.setMimeType("image/png"); + image.setUser(user); + image.setSupplier(supplier); + image.setAttribution("attribution"); + + return image; +} + +bool tst_QPlaceImage::checkType(const QPlaceContent &content) +{ + return content.type() == QPlaceContent::ImageType; +} + +void tst_QPlaceImage::detach(QPlaceContent *content) +{ + content->setAttribution("attribution"); +} + +void tst_QPlaceImage::setSubClassProperty(QPlaceImage *image) +{ + image->setImageId("0002"); +} + +void tst_QPlaceImage::constructorTest() +{ + QPlaceImage testObj; + testObj.setImageId("testId"); + QPlaceImage *testObjPtr = new QPlaceImage(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + delete testObjPtr; +} + +void tst_QPlaceImage::supplierTest() +{ + QPlaceImage testObj; + QVERIFY2(testObj.supplier().supplierId() == QString(), "Wrong default value"); + QPlaceSupplier sup; + sup.setName("testName1"); + sup.setSupplierId("testId"); + testObj.setSupplier(sup); + QVERIFY2(testObj.supplier() == sup, "Wrong value returned"); +} + +void tst_QPlaceImage::idTest() +{ + QPlaceImage testObj; + QVERIFY2(testObj.imageId() == QString(), "Wrong default value"); + testObj.setImageId("testText"); + QVERIFY2(testObj.imageId() == "testText", "Wrong value returned"); +} + +void tst_QPlaceImage::mimeTypeTest() +{ + QPlaceImage testObj; + QVERIFY2(testObj.mimeType() == QString(), "Wrong default value"); + testObj.setMimeType("testText"); + QVERIFY2(testObj.mimeType() == "testText", "Wrong value returned"); +} + +void tst_QPlaceImage::attributionTest() +{ + QPlaceImage image; + QVERIFY(image.attribution().isEmpty()); + image.setAttribution(QStringLiteral("Brought to you by acme")); + QCOMPARE(image.attribution(), QStringLiteral("Brought to you by acme")); + image.setAttribution(QString()); + QVERIFY(image.attribution().isEmpty()); +} + +void tst_QPlaceImage::operatorsTest() +{ + QPlaceImage testObj; + testObj.setMimeType("testValue"); + QPlaceImage testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setImageId("testValue2"); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +void tst_QPlaceImage::conversionTest() +{ + QLocationTestUtils::testConversion(this); +} + +QTEST_APPLESS_MAIN(tst_QPlaceImage); + +#include "tst_qplaceimage.moc" diff --git a/tests/auto/qplacemanager/qplacemanager.pro b/tests/auto/qplacemanager/qplacemanager.pro new file mode 100644 index 0000000..7e7b25e --- /dev/null +++ b/tests/auto/qplacemanager/qplacemanager.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacemanager + +SOURCES += tst_qplacemanager.cpp + +CONFIG -= app_bundle + +QT += location testlib diff --git a/tests/auto/qplacemanager/tst_qplacemanager.cpp b/tests/auto/qplacemanager/tst_qplacemanager.cpp new file mode 100644 index 0000000..fe97b37 --- /dev/null +++ b/tests/auto/qplacemanager/tst_qplacemanager.cpp @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include +#include + + +#ifndef WAIT_UNTIL +#define WAIT_UNTIL(__expr) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if (!(__expr)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + } while (0) +#endif + +QT_USE_NAMESPACE + +class tst_QPlaceManager : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void compatiblePlace(); + void testMetadata(); + void testLocales(); + void testMatchUnsupported(); + +private: + bool checkSignals(QPlaceReply *reply, QPlaceReply::Error expectedError); + + QGeoServiceProvider *provider; + QPlaceManager *placeManager; +}; + +void tst_QPlaceManager::initTestCase() +{ + /* + * Set custom path since CI doesn't install test plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif + provider = 0; + + QStringList providers = QGeoServiceProvider::availableServiceProviders(); + QVERIFY(providers.contains("qmlgeo.test.plugin")); + + provider = new QGeoServiceProvider("qmlgeo.test.plugin"); + provider->setAllowExperimental(true); + QCOMPARE(provider->placesFeatures() & QGeoServiceProvider::OfflinePlacesFeature, + QGeoServiceProvider::OfflinePlacesFeature); + placeManager = provider->placeManager(); + QVERIFY(placeManager); +} + +void tst_QPlaceManager::testMetadata() +{ + QCOMPARE(placeManager->managerName(), QStringLiteral("qmlgeo.test.plugin")); + QCOMPARE(placeManager->managerVersion(), 100); +} + +void tst_QPlaceManager::testLocales() +{ + QCOMPARE(placeManager->locales().count(), 1); + QCOMPARE(placeManager->locales().at(0), QLocale()); + + QLocale locale(QLocale::Norwegian, QLocale::Norway); + placeManager->setLocale(locale); + + QCOMPARE(placeManager->locales().at(0), locale); + + QList locales; + QLocale en_AU = QLocale(QLocale::English, QLocale::Australia); + QLocale en_UK = QLocale(QLocale::English, QLocale::UnitedKingdom); + locales << en_AU << en_UK; + placeManager->setLocales(locales); + QCOMPARE(placeManager->locales().count(), 2); + QCOMPARE(placeManager->locales().at(0), en_AU); + QCOMPARE(placeManager->locales().at(1), en_UK); +} + +void tst_QPlaceManager::testMatchUnsupported() +{ + QPlaceMatchRequest request; + QPlaceMatchReply *reply = placeManager->matchingPlaces(request); + QVERIFY(checkSignals(reply, QPlaceReply::UnsupportedError)); +} + +void tst_QPlaceManager::compatiblePlace() +{ + QPlace place; + place.setPlaceId(QStringLiteral("4-8-15-16-23-42")); + place.setName(QStringLiteral("Island")); + place.setVisibility(QLocation::PublicVisibility); + + QPlace compatPlace = placeManager->compatiblePlace(place); + QVERIFY(compatPlace.placeId().isEmpty()); + QCOMPARE(compatPlace.name(), QStringLiteral("Island")); + QCOMPARE(compatPlace.visibility(), QLocation::UnspecifiedVisibility); +} + +void tst_QPlaceManager::cleanupTestCase() +{ + delete provider; +} + +bool tst_QPlaceManager::checkSignals(QPlaceReply *reply, QPlaceReply::Error expectedError) +{ + QSignalSpy finishedSpy(reply, SIGNAL(finished())); + QSignalSpy errorSpy(reply, SIGNAL(error(QPlaceReply::Error,QString))); + QSignalSpy managerFinishedSpy(placeManager, SIGNAL(finished(QPlaceReply*))); + QSignalSpy managerErrorSpy(placeManager,SIGNAL(error(QPlaceReply*,QPlaceReply::Error,QString))); + + if (expectedError != QPlaceReply::NoError) { + //check that we get an error signal from the reply + WAIT_UNTIL(errorSpy.count() == 1); + if (errorSpy.count() != 1) { + qWarning() << "Error signal for search operation not received"; + return false; + } + + //check that we get the correct error from the reply's signal + QPlaceReply::Error actualError = qvariant_cast(errorSpy.at(0).at(0)); + if (actualError != expectedError) { + qWarning() << "Actual error code in reply signal does not match expected error code"; + qWarning() << "Actual error code = " << actualError; + qWarning() << "Expected error coe =" << expectedError; + return false; + } + + //check that we get an error signal from the manager + WAIT_UNTIL(managerErrorSpy.count() == 1); + if (managerErrorSpy.count() !=1) { + qWarning() << "Error signal from manager for search operation not received"; + return false; + } + + //check that we get the correct reply instance in the error signal from the manager + if (qvariant_cast(managerErrorSpy.at(0).at(0)) != reply) { + qWarning() << "Reply instance in error signal from manager is incorrect"; + return false; + } + + //check that we get the correct error from the signal of the manager + actualError = qvariant_cast(managerErrorSpy.at(0).at(1)); + if (actualError != expectedError) { + qWarning() << "Actual error code from manager signal does not match expected error code"; + qWarning() << "Actual error code =" << actualError; + qWarning() << "Expected error code = " << expectedError; + return false; + } + } + + //check that we get a finished signal + WAIT_UNTIL(finishedSpy.count() == 1); + if (finishedSpy.count() !=1) { + qWarning() << "Finished signal from reply not received"; + return false; + } + + if (reply->error() != expectedError) { + qWarning() << "Actual error code does not match expected error code"; + qWarning() << "Actual error code: " << reply->error(); + qWarning() << "Expected error code" << expectedError; + return false; + } + + if (expectedError == QPlaceReply::NoError && !reply->errorString().isEmpty()) { + qWarning() << "Expected error was no error but error string was not empty"; + qWarning() << "Error string=" << reply->errorString(); + return false; + } + + //check that we get the finished signal from the manager + WAIT_UNTIL(managerFinishedSpy.count() == 1); + if (managerFinishedSpy.count() != 1) { + qWarning() << "Finished signal from manager not received"; + return false; + } + + //check that the reply instance in the finished signal from the manager is correct + if (qvariant_cast(managerFinishedSpy.at(0).at(0)) != reply) { + qWarning() << "Reply instance in finished signal from manager is incorrect"; + return false; + } + + return true; +} + +QTEST_GUILESS_MAIN(tst_QPlaceManager) + +#include "tst_qplacemanager.moc" diff --git a/tests/auto/qplacemanager_nokia/qplacemanager_nokia.pro b/tests/auto/qplacemanager_nokia/qplacemanager_nokia.pro new file mode 100644 index 0000000..41f5998 --- /dev/null +++ b/tests/auto/qplacemanager_nokia/qplacemanager_nokia.pro @@ -0,0 +1,7 @@ +CONFIG += testcase +TARGET = tst_qplacemanager_nokia + +SOURCES += tst_qplacemanager_nokia.cpp + +QT += location testlib + diff --git a/tests/auto/qplacemanager_nokia/tst_qplacemanager_nokia.cpp b/tests/auto/qplacemanager_nokia/tst_qplacemanager_nokia.cpp new file mode 100644 index 0000000..7e1f649 --- /dev/null +++ b/tests/auto/qplacemanager_nokia/tst_qplacemanager_nokia.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include +#include + +#ifndef WAIT_UNTIL +#define WAIT_UNTIL(__expr) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if (!(__expr)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + } while (0) +#endif + +Q_DECLARE_METATYPE(QPlaceIdReply *); + +QT_USE_NAMESPACE + +class tst_QPlaceManagerNokia : public QObject +{ + Q_OBJECT +public: + tst_QPlaceManagerNokia(); + +private Q_SLOTS: + void initTestCase(); + void unsupportedFunctions(); + +private: + bool checkSignals(QPlaceReply *reply, QPlaceReply::Error expectedError); + QGeoServiceProvider *provider; + QPlaceManager *placeManager; + QCoreApplication *coreApp; +}; + +tst_QPlaceManagerNokia::tst_QPlaceManagerNokia() +{ +} + +void tst_QPlaceManagerNokia::initTestCase() +{ + qRegisterMetaType(); + + QStringList providers = QGeoServiceProvider::availableServiceProviders(); + + QVariantMap params; + params.insert(QStringLiteral("here.app_id"), "stub"); + params.insert(QStringLiteral("here.token"), "stub"); + provider = new QGeoServiceProvider("here", params); + placeManager = provider->placeManager(); + QVERIFY(placeManager); +} + +void tst_QPlaceManagerNokia::unsupportedFunctions() +{ + QPlace place; + place.setName(QStringLiteral("Brisbane")); + QPlaceIdReply *savePlaceReply = placeManager->savePlace(place); + QVERIFY(savePlaceReply); + QVERIFY(checkSignals(savePlaceReply, QPlaceReply::UnsupportedError)); + QCOMPARE(savePlaceReply->operationType(), QPlaceIdReply::SavePlace); + + QPlaceIdReply *removePlaceReply = placeManager->removePlace(place.placeId()); + QVERIFY(removePlaceReply); + QVERIFY(checkSignals(removePlaceReply, QPlaceReply::UnsupportedError)); + QCOMPARE(removePlaceReply->operationType(), QPlaceIdReply::RemovePlace); + + QPlaceCategory category; + category.setName(QStringLiteral("Accommodation")); + QPlaceIdReply *saveCategoryReply = placeManager->saveCategory(category); + QVERIFY(saveCategoryReply); + QVERIFY(checkSignals(saveCategoryReply, QPlaceReply::UnsupportedError)); + QCOMPARE(saveCategoryReply->operationType(), QPlaceIdReply::SaveCategory); + + QPlaceIdReply *removeCategoryReply = placeManager->removeCategory(category.categoryId()); + QVERIFY(removeCategoryReply); + QVERIFY(checkSignals(removeCategoryReply, QPlaceReply::UnsupportedError)); + QCOMPARE(removeCategoryReply->operationType(), QPlaceIdReply::RemoveCategory); +} + +bool tst_QPlaceManagerNokia::checkSignals(QPlaceReply *reply, QPlaceReply::Error expectedError) +{ + QSignalSpy finishedSpy(reply, SIGNAL(finished())); + QSignalSpy errorSpy(reply, SIGNAL(error(QPlaceReply::Error,QString))); + QSignalSpy managerFinishedSpy(placeManager, SIGNAL(finished(QPlaceReply*))); + QSignalSpy managerErrorSpy(placeManager,SIGNAL(error(QPlaceReply*,QPlaceReply::Error,QString))); + + if (expectedError != QPlaceReply::NoError) { + //check that we get an error signal from the reply + WAIT_UNTIL(errorSpy.count() == 1); + if (errorSpy.count() != 1) { + qWarning() << "Error signal for operation not received"; + return false; + } + + //check that we get the correct error from the reply's signal + QPlaceReply::Error actualError = qvariant_cast(errorSpy.at(0).at(0)); + if (actualError != expectedError) { + qWarning() << "Actual error code in reply signal does not match expected error code"; + qWarning() << "Actual error code = " << actualError; + qWarning() << "Expected error coe =" << expectedError; + return false; + } + + //check that we get an error signal from the manager + WAIT_UNTIL(managerErrorSpy.count() == 1); + if (managerErrorSpy.count() !=1) { + qWarning() << "Error signal from manager for search operation not received"; + return false; + } + + //check that we get the correct reply instance in the error signal from the manager + if (qvariant_cast(managerErrorSpy.at(0).at(0)) != reply) { + qWarning() << "Reply instance in error signal from manager is incorrect"; + return false; + } + + //check that we get the correct error from the signal of the manager + actualError = qvariant_cast(managerErrorSpy.at(0).at(1)); + if (actualError != expectedError) { + qWarning() << "Actual error code from manager signal does not match expected error code"; + qWarning() << "Actual error code =" << actualError; + qWarning() << "Expected error code = " << expectedError; + return false; + } + } + + //check that we get a finished signal + WAIT_UNTIL(finishedSpy.count() == 1); + if (finishedSpy.count() !=1) { + qWarning() << "Finished signal from reply not received"; + return false; + } + + if (reply->error() != expectedError) { + qWarning() << "Actual error code does not match expected error code"; + qWarning() << "Actual error code: " << reply->error(); + qWarning() << "Expected error code" << expectedError; + return false; + } + + if (expectedError == QPlaceReply::NoError && !reply->errorString().isEmpty()) { + qWarning() << "Expected error was no error but error string was not empty"; + qWarning() << "Error string=" << reply->errorString(); + return false; + } + + //check that we get the finished signal from the manager + WAIT_UNTIL(managerFinishedSpy.count() == 1); + if (managerFinishedSpy.count() != 1) { + qWarning() << "Finished signal from manager not received"; + return false; + } + + //check that the reply instance in the finished signal from the manager is correct + if (qvariant_cast(managerFinishedSpy.at(0).at(0)) != reply) { + qWarning() << "Reply instance in finished signal from manager is incorrect"; + return false; + } + + return true; +} + +QTEST_GUILESS_MAIN(tst_QPlaceManagerNokia) + +#include "tst_qplacemanager_nokia.moc" diff --git a/tests/auto/qplacemanager_unsupported/qplacemanager_unsupported.pro b/tests/auto/qplacemanager_unsupported/qplacemanager_unsupported.pro new file mode 100644 index 0000000..57898ca --- /dev/null +++ b/tests/auto/qplacemanager_unsupported/qplacemanager_unsupported.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacemanager_unsupported + +SOURCES += tst_qplacemanager_unsupported.cpp + +CONFIG -= app_bundle + +QT += location testlib diff --git a/tests/auto/qplacemanager_unsupported/tst_qplacemanager_unsupported.cpp b/tests/auto/qplacemanager_unsupported/tst_qplacemanager_unsupported.cpp new file mode 100644 index 0000000..4171610 --- /dev/null +++ b/tests/auto/qplacemanager_unsupported/tst_qplacemanager_unsupported.cpp @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QPlaceManagerUnsupported : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void testMetadata(); + void testLocales(); + + void testGetPlaceDetails(); + void testGetPlaceContent(); + void testSearch(); + void testSearchSuggestions(); + + void testSavePlace(); + void testRemovePlace(); + void testSaveCategory(); + void testRemoveCategory(); + + void testCategories(); + + void compatiblePlace(); + + void testMatchUnsupported(); + +private: + void checkSignals(QPlaceReply *reply, QPlaceReply::Error expectedError, bool *failed); + bool checkSignals(QPlaceReply *reply, QPlaceReply::Error expectedError); + + QGeoServiceProvider *m_provider; + QPlaceManager *m_manager; +}; + +void tst_QPlaceManagerUnsupported::initTestCase() +{ + /* + * Set custom path since CI doesn't install test plugins + */ +#ifdef Q_OS_WIN + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../../plugins")); +#else + QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() + + QStringLiteral("/../../../plugins")); +#endif + + m_provider = 0; + m_manager = 0; + + QStringList providers = QGeoServiceProvider::availableServiceProviders(); + QVERIFY(providers.contains("test.places.unsupported")); + + m_provider = new QGeoServiceProvider("test.places.unsupported"); + QVERIFY(m_provider); + QCOMPARE(m_provider->error(), QGeoServiceProvider::NotSupportedError); + m_provider->setAllowExperimental(true); + QCOMPARE(m_provider->error(), QGeoServiceProvider::NoError); + + m_manager = m_provider->placeManager(); + QVERIFY(m_manager); +} + +void tst_QPlaceManagerUnsupported::cleanupTestCase() +{ + delete m_provider; +} + +void tst_QPlaceManagerUnsupported::testMetadata() +{ + QCOMPARE(m_manager->managerName(), QStringLiteral("test.places.unsupported")); + QCOMPARE(m_manager->managerVersion(), 1); + QCOMPARE(m_provider->placesFeatures(), QGeoServiceProvider::NoPlacesFeatures); +} + +void tst_QPlaceManagerUnsupported::testLocales() +{ + QVERIFY(m_manager->locales().isEmpty()); + + QLocale locale(QLocale::Norwegian, QLocale::Norway); + m_manager->setLocale(locale); + + QVERIFY(m_manager->locales().isEmpty()); + + QList locales; + QLocale en_AU = QLocale(QLocale::English, QLocale::Australia); + QLocale en_UK = QLocale(QLocale::English, QLocale::UnitedKingdom); + locales << en_AU << en_UK; + m_manager->setLocales(locales); + + QVERIFY(m_manager->locales().isEmpty()); +} + +void tst_QPlaceManagerUnsupported::testGetPlaceDetails() +{ + QPlaceDetailsReply *reply = m_manager->getPlaceDetails(QString()); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; +} + +void tst_QPlaceManagerUnsupported::testGetPlaceContent() +{ + QPlaceContentReply *reply = m_manager->getPlaceContent(QPlaceContentRequest()); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; +} + +void tst_QPlaceManagerUnsupported::testSearch() +{ + QPlaceSearchReply *reply = m_manager->search(QPlaceSearchRequest()); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; +} + +void tst_QPlaceManagerUnsupported::testSearchSuggestions() +{ + QPlaceSearchSuggestionReply *reply = m_manager->searchSuggestions(QPlaceSearchRequest()); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; +} + +void tst_QPlaceManagerUnsupported::testSavePlace() +{ + QPlaceIdReply *reply = m_manager->savePlace(QPlace()); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; +} + +void tst_QPlaceManagerUnsupported::testRemovePlace() +{ + QPlaceIdReply *reply = m_manager->removePlace(QString()); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; +} + +void tst_QPlaceManagerUnsupported::testSaveCategory() +{ + QPlaceIdReply *reply = m_manager->saveCategory(QPlaceCategory()); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; +} + +void tst_QPlaceManagerUnsupported::testRemoveCategory() +{ + QPlaceIdReply *reply = m_manager->removeCategory(QString()); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; +} + +void tst_QPlaceManagerUnsupported::testCategories() +{ + QPlaceReply *reply = m_manager->initializeCategories(); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; + + QVERIFY(m_manager->childCategoryIds().isEmpty()); + QVERIFY(m_manager->parentCategoryId(QString()).isEmpty()); + QCOMPARE(m_manager->category(QString()), QPlaceCategory()); +} + +void tst_QPlaceManagerUnsupported::compatiblePlace() +{ + QPlace place; + place.setPlaceId(QStringLiteral("4-8-15-16-23-42")); + place.setName(QStringLiteral("Island")); + place.setVisibility(QLocation::PublicVisibility); + + QPlace compatPlace = m_manager->compatiblePlace(place); + QCOMPARE(compatPlace, QPlace()); +} + +void tst_QPlaceManagerUnsupported::testMatchUnsupported() +{ + QPlaceMatchReply *reply = m_manager->matchingPlaces(QPlaceMatchRequest()); + if (!checkSignals(reply, QPlaceReply::UnsupportedError)) + return; +} + +void tst_QPlaceManagerUnsupported::checkSignals(QPlaceReply *reply, + QPlaceReply::Error expectedError, bool *failed) +{ + *failed = true; + + QSignalSpy finishedSpy(reply, SIGNAL(finished())); + QSignalSpy errorSpy(reply, SIGNAL(error(QPlaceReply::Error,QString))); + QSignalSpy managerFinishedSpy(m_manager, SIGNAL(finished(QPlaceReply*))); + QSignalSpy managerErrorSpy(m_manager,SIGNAL(error(QPlaceReply*,QPlaceReply::Error,QString))); + + if (expectedError != QPlaceReply::NoError) { + //check that we get an error signal from the reply + QTRY_VERIFY(errorSpy.count() == 1); + + //check that we get the correct error from the reply's signal + QPlaceReply::Error actualError = qvariant_cast(errorSpy.at(0).at(0)); + QCOMPARE(actualError, expectedError); + + //check that we get an error signal from the manager + QTRY_VERIFY(managerErrorSpy.count() == 1); + + //check that we get the correct reply instance in the error signal from the manager + QPlaceReply *managerReply = qvariant_cast(managerErrorSpy.at(0).at(0)); + QCOMPARE(managerReply, reply); + + //check that we get the correct error from the signal of the manager + actualError = qvariant_cast(managerErrorSpy.at(0).at(1)); + QCOMPARE(actualError, expectedError); + } + + //check that we get a finished signal + QTRY_VERIFY(finishedSpy.count() == 1); + + QCOMPARE(reply->error(), expectedError); + + QCOMPARE(reply->errorString().isEmpty(), expectedError == QPlaceReply::NoError); + + //check that we get the finished signal from the manager + QTRY_VERIFY(managerFinishedSpy.count() == 1); + + //check that the reply instance in the finished signal from the manager is correct + QPlaceReply *managerReply = qvariant_cast(managerFinishedSpy.at(0).at(0)); + QCOMPARE(managerReply, reply); + + *failed = false; +} + +bool tst_QPlaceManagerUnsupported::checkSignals(QPlaceReply *reply, + QPlaceReply::Error expectedError) +{ + bool failed; + checkSignals(reply, expectedError, &failed); + return failed; +} + +QTEST_GUILESS_MAIN(tst_QPlaceManagerUnsupported) + +#include "tst_qplacemanager_unsupported.moc" diff --git a/tests/auto/qplacematchreply/qplacematchreply.pro b/tests/auto/qplacematchreply/qplacematchreply.pro new file mode 100644 index 0000000..00c1dcd --- /dev/null +++ b/tests/auto/qplacematchreply/qplacematchreply.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacematchreply + +SOURCES += tst_qplacematchreply.cpp + +QT += location testlib diff --git a/tests/auto/qplacematchreply/tst_qplacematchreply.cpp b/tests/auto/qplacematchreply/tst_qplacematchreply.cpp new file mode 100644 index 0000000..34ee7ba --- /dev/null +++ b/tests/auto/qplacematchreply/tst_qplacematchreply.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class TestMatchReply : public QPlaceMatchReply +{ + Q_OBJECT +public: + TestMatchReply(QObject *parent) : QPlaceMatchReply(parent) {} + TestMatchReply() {} + + void setPlaces(const QList &places) { + QPlaceMatchReply::setPlaces(places); + } + + void setRequest(const QPlaceMatchRequest &request) { + QPlaceMatchReply::setRequest(request); + } +}; + +class tst_QPlaceMatchReply : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceMatchReply(); + +private Q_SLOTS: + void constructorTest(); + void typeTest(); + void requestTest(); +// void resultsTest(); +}; + +tst_QPlaceMatchReply::tst_QPlaceMatchReply() +{ +} + +void tst_QPlaceMatchReply::constructorTest() +{ + QPlaceMatchReply *reply = new TestMatchReply(this); + QVERIFY(reply->parent() == this); + delete reply; +} + +void tst_QPlaceMatchReply::typeTest() +{ + TestMatchReply *reply = new TestMatchReply(this); + QVERIFY(reply->type() == QPlaceReply::MatchReply); + delete reply; +} + +void tst_QPlaceMatchReply::requestTest() +{ + TestMatchReply *reply = new TestMatchReply(this); + QPlaceMatchRequest request; + + QPlace place1; + place1.setName(QStringLiteral("place1")); + + QPlace place2; + place2.setName(QStringLiteral("place2")); + + QList places; + places << place1 << place2; + + request.setPlaces(places); + + reply->setRequest(request); + QCOMPARE(reply->request(), request); + + reply->setRequest(QPlaceMatchRequest()); + QCOMPARE(reply->request(), QPlaceMatchRequest()); + delete reply; +} + + +QTEST_APPLESS_MAIN(tst_QPlaceMatchReply) + +#include "tst_qplacematchreply.moc" diff --git a/tests/auto/qplacematchrequest/qplacematchrequest.pro b/tests/auto/qplacematchrequest/qplacematchrequest.pro new file mode 100644 index 0000000..558cd2e --- /dev/null +++ b/tests/auto/qplacematchrequest/qplacematchrequest.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacematchrequest +SOURCES += tst_qplacematchrequest.cpp + +QT += location testlib diff --git a/tests/auto/qplacematchrequest/tst_qplacematchrequest.cpp b/tests/auto/qplacematchrequest/tst_qplacematchrequest.cpp new file mode 100644 index 0000000..03ba59a --- /dev/null +++ b/tests/auto/qplacematchrequest/tst_qplacematchrequest.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include +#include + +QT_USE_NAMESPACE + +class tst_QPlaceMatchRequest : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceMatchRequest(); + +private Q_SLOTS: + void constructorTest(); + void placesTest(); + void resultsTest(); + void parametersTest(); + void clearTest(); +}; + +tst_QPlaceMatchRequest::tst_QPlaceMatchRequest() +{ +} + +void tst_QPlaceMatchRequest::constructorTest() +{ + QPlaceMatchRequest request; + QVariantMap params; + params.insert(QStringLiteral("key"), QStringLiteral("val")); + + QPlace place1; + place1.setName(QStringLiteral("place1")); + + QPlace place2; + place2.setName(QStringLiteral("place2")); + + QList places; + places << place1 << place2; + + request.setPlaces(places); + request.setParameters(params); + + QPlaceMatchRequest copy(request); + QCOMPARE(copy, request); + QCOMPARE(copy.places(), places); + QCOMPARE(copy.parameters(), params); +} + +void tst_QPlaceMatchRequest::placesTest() +{ + QPlaceMatchRequest request; + QCOMPARE(request.places().count(), 0); + + QPlace place1; + place1.setName(QStringLiteral("place1")); + + QPlace place2; + place2.setName(QStringLiteral("place2")); + + QList places; + places << place1 << place2; + + request.setPlaces(places); + QCOMPARE(request.places(), places); + + request.setPlaces(QList()); + QCOMPARE(request.places().count(), 0); +} + +void tst_QPlaceMatchRequest::resultsTest() +{ + QPlaceMatchRequest request; + QCOMPARE(request.places().count(), 0); + + QPlace place1; + place1.setName(QStringLiteral("place1")); + QPlaceResult result1; + result1.setPlace(place1); + + QPlace place2; + place2.setName(QStringLiteral("place2")); + QPlaceResult result2; + result2.setPlace(place2); + + QList results; + results << result1 << result2; + + request.setResults(results); + + QCOMPARE(request.places().count(), 2); + QCOMPARE(request.places().at(0), place1); + QCOMPARE(request.places().at(1), place2); + + request.setResults(QList()); + QCOMPARE(request.places().count(), 0); +} + +void tst_QPlaceMatchRequest::parametersTest() +{ + QPlaceMatchRequest request; + QVERIFY(request.parameters().isEmpty()); + + QVariantMap params; + params.insert(QStringLiteral("key"), QStringLiteral("value")); + + request.setParameters(params); + QCOMPARE(request.parameters(), params); +} + +void tst_QPlaceMatchRequest::clearTest() +{ + QPlaceMatchRequest request; + QVariantMap params; + params.insert(QStringLiteral("key"), QStringLiteral("value")); + + QPlace place1; + place1.setName(QStringLiteral("place1")); + + QPlace place2; + place2.setName(QStringLiteral("place2")); + + QList places; + places << place1 << place2; + + request.setPlaces(places); + request.setParameters(params); + + request.clear(); + QVERIFY(request.places().isEmpty()); + QVERIFY(request.parameters().isEmpty()); +} + +QTEST_APPLESS_MAIN(tst_QPlaceMatchRequest) + +#include "tst_qplacematchrequest.moc" diff --git a/tests/auto/qplaceperiod/qplaceperiod.pro b/tests/auto/qplaceperiod/qplaceperiod.pro new file mode 100644 index 0000000..c680a75 --- /dev/null +++ b/tests/auto/qplaceperiod/qplaceperiod.pro @@ -0,0 +1,8 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplaceperiod + +SOURCES += tst_qplaceperiod.cpp + +QT += location testlib +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/qplaceperiod/tst_qplaceperiod.cpp b/tests/auto/qplaceperiod/tst_qplaceperiod.cpp new file mode 100644 index 0000000..78db546 --- /dev/null +++ b/tests/auto/qplaceperiod/tst_qplaceperiod.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class tst_QPlacePeriod : public QObject +{ + Q_OBJECT + +public: + tst_QPlacePeriod(); + +private Q_SLOTS: + void constructorTest(); + void startDateTest(); + void startTimeTest(); + void endDateTest(); + void endTimeTest(); + void operatorsTest(); +}; + +tst_QPlacePeriod::tst_QPlacePeriod() +{ +} + +void tst_QPlacePeriod::constructorTest() +{ + QPlacePeriod testObj; + testObj.setStartTime(QTime::currentTime()); + QPlacePeriod *testObjPtr = new QPlacePeriod(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(testObjPtr->startTime() == testObj.startTime(), "Copy constructor - start time"); + delete testObjPtr; +} + +void tst_QPlacePeriod::startDateTest() +{ + QPlacePeriod testObj; + QVERIFY2(testObj.startDate().isNull() == true, "Wrong default value"); + QDate date = QDate::currentDate(); + testObj.setStartDate(date); + QVERIFY2(testObj.startDate() == date, "Wrong value returned"); +} + +void tst_QPlacePeriod::startTimeTest() +{ + QPlacePeriod testObj; + QVERIFY2(testObj.startTime().isNull() == true, "Wrong default value"); + QTime time = QTime::currentTime(); + testObj.setStartTime(time); + QVERIFY2(testObj.startTime() == time, "Wrong value returned"); +} + +void tst_QPlacePeriod::endDateTest() +{ + QPlacePeriod testObj; + QVERIFY2(testObj.endDate().isNull() == true, "Wrong default value"); + QDate date = QDate::currentDate(); + testObj.setEndDate(date); + QVERIFY2(testObj.endDate() == date, "Wrong value returned"); +} + +void tst_QPlacePeriod::endTimeTest() +{ + QPlacePeriod testObj; + QVERIFY2(testObj.endTime().isNull() == true, "Wrong default value"); + QTime time = QTime::currentTime(); + testObj.setEndTime(time); + QVERIFY2(testObj.endTime() == time, "Wrong value returned"); +} + +void tst_QPlacePeriod::operatorsTest() +{ + QPlacePeriod testObj; + QTime time = QTime::currentTime(); + testObj.setEndTime(time); + QPlacePeriod testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj.setEndTime(QTime::currentTime().addSecs(102021)); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +QTEST_APPLESS_MAIN(tst_QPlacePeriod); + +#include "tst_qplaceperiod.moc" diff --git a/tests/auto/qplaceratings/qplaceratings.pro b/tests/auto/qplaceratings/qplaceratings.pro new file mode 100644 index 0000000..b17b6aa --- /dev/null +++ b/tests/auto/qplaceratings/qplaceratings.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplaceratings + +SOURCES += tst_qplaceratings.cpp + +QT += location testlib diff --git a/tests/auto/qplaceratings/tst_qplaceratings.cpp b/tests/auto/qplaceratings/tst_qplaceratings.cpp new file mode 100644 index 0000000..4de19dc --- /dev/null +++ b/tests/auto/qplaceratings/tst_qplaceratings.cpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class tst_QPlaceRatings : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceRatings(); + +private Q_SLOTS: + void constructorTest(); + void averageTest(); + void countTest(); + void operatorsTest(); + void isEmptyTest(); +}; + +tst_QPlaceRatings::tst_QPlaceRatings() +{ +} + +void tst_QPlaceRatings::constructorTest() +{ + QPlaceRatings testObj; + Q_UNUSED(testObj); + + QPlaceRatings *testObjPtr = new QPlaceRatings(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(testObjPtr->count() == 0, "Copy constructor - wrong count"); + QVERIFY2(testObjPtr->average() == 0, "Copy constructor - wrong average"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + delete testObjPtr; +} + +void tst_QPlaceRatings::averageTest() +{ + QPlaceRatings testObj; + QVERIFY2(qFuzzyCompare(testObj.average(), 0) , "Wrong default average"); + testObj.setAverage(-10.23); + QCOMPARE(testObj.average(), -10.23); +} + +void tst_QPlaceRatings::countTest() +{ + QPlaceRatings testObj; + QVERIFY2(testObj.count() == 0, "Wrong default value"); + testObj.setCount(-1002); + QVERIFY2(testObj.count() == -1002, "Wrong value returned"); +} + +void tst_QPlaceRatings::operatorsTest() +{ + QPlaceRatings testObj; + testObj.setAverage(0.123); + QPlaceRatings testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setCount(-10); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +void tst_QPlaceRatings::isEmptyTest() +{ + QPlaceRatings ratings; + + QVERIFY(ratings.isEmpty()); + + ratings.setCount(1); + QVERIFY(!ratings.isEmpty()); + ratings.setCount(0); + QVERIFY(ratings.isEmpty()); + + ratings.setMaximum(1); + QVERIFY(!ratings.isEmpty()); + ratings.setMaximum(0); + QVERIFY(ratings.isEmpty()); + + ratings.setAverage(1); + QVERIFY(!ratings.isEmpty()); + ratings.setAverage(0); + QVERIFY(ratings.isEmpty()); +} + +QTEST_APPLESS_MAIN(tst_QPlaceRatings); + +#include "tst_qplaceratings.moc" diff --git a/tests/auto/qplacereply/qplacereply.pro b/tests/auto/qplacereply/qplacereply.pro new file mode 100644 index 0000000..3d18e71 --- /dev/null +++ b/tests/auto/qplacereply/qplacereply.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacereply + +SOURCES += tst_qplacereply.cpp + +QT += location testlib diff --git a/tests/auto/qplacereply/tst_qplacereply.cpp b/tests/auto/qplacereply/tst_qplacereply.cpp new file mode 100644 index 0000000..a67028d --- /dev/null +++ b/tests/auto/qplacereply/tst_qplacereply.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class TestReply : public QPlaceReply +{ +public: + TestReply(QObject *parent) : QPlaceReply(parent) {} + void setFinished(bool finished) { QPlaceReply::setFinished(finished); } + void setError(QPlaceReply::Error error, const QString &errorString) { + QPlaceReply::setError(error,errorString); + } +}; + +class tst_QPlaceReply : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceReply(); + +private Q_SLOTS: + void constructorTest(); + void typeTest(); + void finishedTest(); + void errorTest(); +}; + +tst_QPlaceReply::tst_QPlaceReply() +{ + +} + +void tst_QPlaceReply::constructorTest() +{ + TestReply *reply = new TestReply(this); + QCOMPARE(reply->parent(), this); + delete reply; +} + +void tst_QPlaceReply::typeTest() +{ + TestReply *reply = new TestReply(this); + QCOMPARE(reply->type(), QPlaceReply::Reply); + + delete reply; +} + +void tst_QPlaceReply::finishedTest() +{ + TestReply *reply = new TestReply(this); + QCOMPARE(reply->isFinished(), false); + reply->setFinished(true); + QCOMPARE(reply->isFinished(), true); + + delete reply; +} + +void tst_QPlaceReply::errorTest() +{ + TestReply *reply = new TestReply(this); + QCOMPARE(reply->error(), QPlaceReply::NoError); + QCOMPARE(reply->errorString(), QString()); + + reply->setError(QPlaceReply::CommunicationError, QStringLiteral("Could not connect to server")); + QCOMPARE(reply->error(), QPlaceReply::CommunicationError); + QCOMPARE(reply->errorString(), QStringLiteral("Could not connect to server")); + + reply->setError(QPlaceReply::NoError, QString()); + QCOMPARE(reply->error(), QPlaceReply::NoError); + QCOMPARE(reply->errorString(), QString()); + + delete reply; +} + +QTEST_APPLESS_MAIN(tst_QPlaceReply) + +#include "tst_qplacereply.moc" diff --git a/tests/auto/qplaceresult/qplaceresult.pro b/tests/auto/qplaceresult/qplaceresult.pro new file mode 100644 index 0000000..966c240 --- /dev/null +++ b/tests/auto/qplaceresult/qplaceresult.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplaceresult + +SOURCES += tst_qplaceresult.cpp + +QT += location testlib diff --git a/tests/auto/qplaceresult/tst_qplaceresult.cpp b/tests/auto/qplaceresult/tst_qplaceresult.cpp new file mode 100644 index 0000000..4074d0d --- /dev/null +++ b/tests/auto/qplaceresult/tst_qplaceresult.cpp @@ -0,0 +1,270 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#include "../utils/qlocationtestutils_p.h" + +QT_USE_NAMESPACE + +class tst_QPlaceResult : public QObject +{ + Q_OBJECT + +public: + QPlaceResult initialSubObject(); + bool checkType(const QPlaceSearchResult &); + void detach(QPlaceSearchResult *); + void setSubClassProperty(QPlaceResult *); + +private Q_SLOTS: + void constructorTest(); + void title(); + void icon(); + void distance(); + void place(); + void sponsored(); + void conversion(); +}; + +QPlaceResult tst_QPlaceResult::initialSubObject() +{ + QPlaceResult placeResult; + placeResult.setTitle(QStringLiteral("title")); + + QPlaceIcon icon; + QVariantMap parameters; + parameters.insert(QPlaceIcon::SingleUrl, + QUrl(QStringLiteral("file:///opt/icons/icon.png"))); + icon.setParameters(parameters); + placeResult.setIcon(icon); + + QPlace place; + place.setName(QStringLiteral("place")); + placeResult.setPlace(place); + + placeResult.setDistance(5); + placeResult.setSponsored(true); + + return placeResult; +} + +bool tst_QPlaceResult::checkType(const QPlaceSearchResult &result) +{ + return result.type() == QPlaceSearchResult::PlaceResult; +} + +void tst_QPlaceResult::detach(QPlaceSearchResult * result) +{ + result->setTitle("title"); +} + +void tst_QPlaceResult::setSubClassProperty(QPlaceResult *result) +{ + result->setSponsored(false); +} + +void tst_QPlaceResult::constructorTest() +{ + QPlaceResult result; + QCOMPARE(result.type(), QPlaceSearchResult::PlaceResult); + + result.setTitle(QStringLiteral("title")); + + QPlaceIcon icon; + QVariantMap parameters; + parameters.insert(QStringLiteral("paramKey"), QStringLiteral("paramValue")); + icon.setParameters(parameters); + result.setIcon(icon); + + QPlace place; + place.setName("place"); + result.setPlace(place); + + result.setDistance(500); + result.setSponsored(true); + + //check copy constructor + QPlaceResult result2(result); + QCOMPARE(result2.title(), QStringLiteral("title")); + QCOMPARE(result2.icon(), icon); + QCOMPARE(result2.place(), place); + QVERIFY(qFuzzyCompare(result.distance(), 500)); + QCOMPARE(result2.isSponsored(), true); + + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + //check results are the same after detachment of underlying shared data pointer + result2.setTitle("title"); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + //check construction of SearchResult using a PlaceResult + QPlaceSearchResult searchResult(result); + QCOMPARE(searchResult.title(), QStringLiteral("title")); + QCOMPARE(searchResult.icon(), icon); + QVERIFY(QLocationTestUtils::compareEquality(searchResult, result)); + QVERIFY(searchResult.type() == QPlaceSearchResult::PlaceResult); + result2 = searchResult; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + //check construction of a SearchResult using a SearchResult + //that is actually a PlaceResult underneath + QPlaceSearchResult searchResult2(searchResult); + QCOMPARE(searchResult2.title(), QStringLiteral("title")); + QCOMPARE(searchResult2.icon(), icon); + QVERIFY(QLocationTestUtils::compareEquality(searchResult2, result)); + QVERIFY(QLocationTestUtils::compareEquality(searchResult, searchResult2)); + QVERIFY(searchResult2.type() == QPlaceSearchResult::PlaceResult); + result2 = searchResult2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceResult::title() +{ + QPlaceResult result; + QVERIFY(result.title().isEmpty()); + + result.setTitle(QStringLiteral("title")); + QCOMPARE(result.title(), QStringLiteral("title")); + + result.setTitle(QString()); + QVERIFY(result.title().isEmpty()); + + QPlaceResult result2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + result2.setTitle("title"); + QVERIFY(QLocationTestUtils::compareInequality(result, result2)); + + result.setTitle("title"); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceResult::icon() +{ + QPlaceResult result; + QVERIFY(result.icon().isEmpty()); + + QPlaceIcon icon; + QVariantMap iconParams; + iconParams.insert(QStringLiteral("paramKey"), QStringLiteral("paramValue")); + icon.setParameters(iconParams); + result.setIcon(icon); + QCOMPARE(result.icon(), icon); + + result.setIcon(QPlaceIcon()); + QVERIFY(result.icon().isEmpty()); + + QPlaceResult result2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + result2.setIcon(icon); + QVERIFY(QLocationTestUtils::compareInequality(result, result2)); + + result.setIcon(icon); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceResult::distance() +{ + QPlaceResult result; + QVERIFY(qIsNaN(result.distance())); + + result.setDistance(3.14); + QVERIFY(qFuzzyCompare(result.distance(), 3.14)); + + result.setDistance(qQNaN()); + QVERIFY(qIsNaN(result.distance())); + + QPlaceResult result2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + result2.setDistance(3.14); + QVERIFY(QLocationTestUtils::compareInequality(result, result2)); + + result.setDistance(3.14); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceResult::place() +{ + QPlaceResult result; + QCOMPARE(result.place(), QPlace()); + + QPlace place; + place.setName("place"); + result.setPlace (place); + QCOMPARE(result.place(), place); + + result.setPlace(QPlace()); + QCOMPARE(result.place(), QPlace()); + + QPlaceResult result2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + result2.setPlace(place); + QVERIFY(QLocationTestUtils::compareInequality(result, result2)); + + result.setPlace(place); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceResult::sponsored() +{ + QPlaceResult result; + QCOMPARE(result.isSponsored(), false); + + result.setSponsored(true); + QCOMPARE(result.isSponsored(), true); + + result.setSponsored(false); + QCOMPARE(result.isSponsored(), false); + + QPlaceResult result2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + result2.setSponsored(true); + QVERIFY(QLocationTestUtils::compareInequality(result, result2)); + + result.setSponsored(true); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceResult::conversion() +{ + QLocationTestUtils::testConversion(this); +} + +QTEST_APPLESS_MAIN(tst_QPlaceResult) + +#include "tst_qplaceresult.moc" diff --git a/tests/auto/qplacereview/qplacereview.pro b/tests/auto/qplacereview/qplacereview.pro new file mode 100644 index 0000000..950e7a3 --- /dev/null +++ b/tests/auto/qplacereview/qplacereview.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacereview + +SOURCES += tst_qplacereview.cpp + +QT += location testlib diff --git a/tests/auto/qplacereview/tst_qplacereview.cpp b/tests/auto/qplacereview/tst_qplacereview.cpp new file mode 100644 index 0000000..7f7edfe --- /dev/null +++ b/tests/auto/qplacereview/tst_qplacereview.cpp @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include +#include +#include + +#include "../utils/qlocationtestutils_p.h" + +QT_USE_NAMESPACE + +class tst_QPlaceReview : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceReview(); + + //needed for QLocationTestUtils::testConversion + QPlaceReview initialSubObject(); + bool checkType(const QPlaceContent &); + void detach(QPlaceContent *); + void setSubClassProperty(QPlaceReview *); + +private Q_SLOTS: + void constructorTest(); + void supplierTest(); + void dateTest(); + void textTest(); + void languageTest(); + void ratingTest(); + void reviewIdTest(); + void titleTest(); + void userTest(); + void operatorsTest(); + void conversionTest(); +}; + +tst_QPlaceReview::tst_QPlaceReview() +{ +} + +QPlaceReview tst_QPlaceReview::initialSubObject() +{ + QPlaceUser user; + user.setName("user 1"); + user.setUserId("0001"); + + QPlaceSupplier supplier; + supplier.setName("supplier"); + supplier.setSupplierId("1"); + + QPlaceReview review; + review.setTitle("title"); + review.setText("text"); + review.setRating(4.5); + review.setLanguage("en"); + review.setDateTime(QDateTime::fromString("01:02 03/04/2000", + "hh:mm dd/MM/yyyy")); + review.setUser(user); + review.setSupplier(supplier); + review.setAttribution("attribution"); + + return review; +} + +bool tst_QPlaceReview::checkType(const QPlaceContent &content) +{ + return content.type() == QPlaceContent::ReviewType; +} + +void tst_QPlaceReview::detach(QPlaceContent *content) +{ + content->setAttribution("attribution"); +} + +void tst_QPlaceReview::setSubClassProperty(QPlaceReview *review) +{ + review->setTitle("new title"); +} + +void tst_QPlaceReview::constructorTest() +{ + QPlaceReview testObj; + testObj.setLanguage("testId"); + QPlaceReview *testObjPtr = new QPlaceReview(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + delete testObjPtr; +} + +void tst_QPlaceReview::supplierTest() +{ + QPlaceReview testObj; + QVERIFY2(testObj.supplier().supplierId() == QString(), "Wrong default value"); + QPlaceSupplier sup; + sup.setName("testName1"); + sup.setSupplierId("testId"); + testObj.setSupplier(sup); + QVERIFY2(testObj.supplier() == sup, "Wrong value returned"); +} + +void tst_QPlaceReview::dateTest() +{ + QPlaceReview testObj; + QCOMPARE(testObj.dateTime(), QDateTime()); + + QDateTime dt = QDateTime::currentDateTime(); + testObj.setDateTime(dt); + QCOMPARE(testObj.dateTime(), dt); +} + +void tst_QPlaceReview::textTest() +{ + QPlaceReview testObj; + QVERIFY2(testObj.text() == QString(), "Wrong default value"); + testObj.setText("testText"); + QVERIFY2(testObj.text() == "testText", "Wrong value returned"); +} + +void tst_QPlaceReview::languageTest() +{ + QPlaceReview testObj; + QVERIFY2(testObj.language() == QString(), "Wrong default value"); + testObj.setLanguage("testText"); + QVERIFY2(testObj.language() == "testText", "Wrong value returned"); +} + +void tst_QPlaceReview::ratingTest() +{ + QPlaceReview testObj; + QVERIFY2(testObj.rating() == 0, "Wrong default value"); + testObj.setRating(-10); + QCOMPARE(testObj.rating(), -10.0); + testObj.setRating(3.4); + QCOMPARE(testObj.rating(), 3.4); +} + +void tst_QPlaceReview::operatorsTest() +{ + QPlaceReview testObj; + testObj.setText("testValue"); + QPlaceReview testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setLanguage("testValue2"); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +void tst_QPlaceReview::reviewIdTest() +{ + QPlaceReview testObj; + QVERIFY2(testObj.reviewId() == QString(), "Wrong default value"); + testObj.setReviewId("testText"); + QVERIFY2(testObj.reviewId() == "testText", "Wrong value returned"); +} +void tst_QPlaceReview::titleTest() +{ + QPlaceReview testObj; + QVERIFY2(testObj.title() == QString(), "Wrong default value"); + testObj.setTitle("testText"); + QVERIFY2(testObj.title() == "testText", "Wrong value returned"); +} + +void tst_QPlaceReview::userTest() +{ + QPlaceReview review; + QVERIFY(review.user().userId().isEmpty()); + QVERIFY(review.user().name().isEmpty()); + QPlaceUser user; + user.setUserId(QStringLiteral("11111")); + user.setName(QStringLiteral("Bob")); + + review.setUser(user); + QCOMPARE(review.user().userId(), QStringLiteral("11111")); + QCOMPARE(review.user().name(), QStringLiteral("Bob")); + + review.setUser(QPlaceUser()); + QVERIFY(review.user().userId().isEmpty()); + QVERIFY(review.user().name().isEmpty()); +} + +void tst_QPlaceReview::conversionTest() +{ + QLocationTestUtils::testConversion(this); +} + +QTEST_APPLESS_MAIN(tst_QPlaceReview) + +#include "tst_qplacereview.moc" diff --git a/tests/auto/qplacesearchreply/qplacesearchreply.pro b/tests/auto/qplacesearchreply/qplacesearchreply.pro new file mode 100644 index 0000000..8a57839 --- /dev/null +++ b/tests/auto/qplacesearchreply/qplacesearchreply.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacesearchreply + +SOURCES += tst_qplacesearchreply.cpp + +QT += location testlib diff --git a/tests/auto/qplacesearchreply/tst_qplacesearchreply.cpp b/tests/auto/qplacesearchreply/tst_qplacesearchreply.cpp new file mode 100644 index 0000000..14c951e --- /dev/null +++ b/tests/auto/qplacesearchreply/tst_qplacesearchreply.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + + +QT_USE_NAMESPACE + +class TestSearchReply : public QPlaceSearchReply +{ + Q_OBJECT +public: + TestSearchReply(QObject *parent) : QPlaceSearchReply(parent) {} + TestSearchReply() {} + + void setResults(const QList &results) { + QPlaceSearchReply::setResults(results); + } + + void setRequest(const QPlaceSearchRequest &request) { + QPlaceSearchReply::setRequest(request); + } +}; + +class tst_QPlaceSearchReply : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceSearchReply(); + +private Q_SLOTS: + void constructorTest(); + void typeTest(); + void requestTest(); + void resultsTest(); +}; + +tst_QPlaceSearchReply::tst_QPlaceSearchReply() +{ +} + +void tst_QPlaceSearchReply::constructorTest() +{ + TestSearchReply *reply = new TestSearchReply(this); + QVERIFY(reply->parent() == this); + delete reply; +} + +void tst_QPlaceSearchReply::typeTest() +{ + TestSearchReply *reply = new TestSearchReply(this); + QVERIFY(reply->type() == QPlaceReply::SearchReply); + delete reply; +} + +void tst_QPlaceSearchReply::requestTest() +{ + TestSearchReply *reply = new TestSearchReply(this); + QPlaceSearchRequest request; + request.setLimit(10); + + QGeoCircle circle; + circle.setCenter(QGeoCoordinate(10,20)); + request.setSearchArea(circle); + + request.setSearchTerm("pizza"); + + reply->setRequest(request); + QCOMPARE(reply->request(), request); + reply->setRequest(QPlaceSearchRequest()); + QCOMPARE(reply->request(), QPlaceSearchRequest()); + delete reply; +} + +void tst_QPlaceSearchReply::resultsTest() +{ + TestSearchReply *reply = new TestSearchReply(this); + QList results; + QPlace winterfell; + winterfell.setName("Winterfell"); + QPlace casterlyRock; + casterlyRock.setName("Casterly Rock"); + QPlace stormsEnd; + stormsEnd.setName("Storm's end"); + + QPlaceResult result1; + result1.setPlace(winterfell); + QPlaceResult result2; + result2.setPlace(casterlyRock); + QPlaceResult result3; + result3.setPlace(stormsEnd); + results << result1 << result2 << result3; + + reply->setResults(results); + QCOMPARE(reply->results(), results); + reply->setResults(QList()); + QCOMPARE(reply->results(), QList()); + delete reply; +} + +QTEST_APPLESS_MAIN(tst_QPlaceSearchReply) + +#include "tst_qplacesearchreply.moc" diff --git a/tests/auto/qplacesearchrequest/qplacesearchrequest.pro b/tests/auto/qplacesearchrequest/qplacesearchrequest.pro new file mode 100644 index 0000000..45d5d1a --- /dev/null +++ b/tests/auto/qplacesearchrequest/qplacesearchrequest.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacesearchrequest +SOURCES += tst_qplacesearchrequest.cpp + +QT += location testlib diff --git a/tests/auto/qplacesearchrequest/tst_qplacesearchrequest.cpp b/tests/auto/qplacesearchrequest/tst_qplacesearchrequest.cpp new file mode 100644 index 0000000..959fede --- /dev/null +++ b/tests/auto/qplacesearchrequest/tst_qplacesearchrequest.cpp @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QPlaceSearchRequest : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceSearchRequest(); + +private Q_SLOTS: + void constructorTest(); + void searchTermTest(); + void categoriesTest(); + void boundingCircleTest(); + void boundingBoxTest(); + void searchAreaTest(); + void visibilityScopeTest(); + void relevanceHintTest(); + void searchContextTest(); + void operatorsTest(); + void clearTest(); +}; + +tst_QPlaceSearchRequest::tst_QPlaceSearchRequest() +{ +} + +void tst_QPlaceSearchRequest::constructorTest() +{ + QPlaceSearchRequest testObj; + Q_UNUSED(testObj); + + QPlaceSearchRequest *testObjPtr = new QPlaceSearchRequest(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + delete testObjPtr; +} + +void tst_QPlaceSearchRequest::searchTermTest() +{ + QPlaceSearchRequest testObj; + QVERIFY2(testObj.searchTerm() == QString(), "Wrong default value"); + testObj.setSearchTerm("testText"); + QVERIFY2(testObj.searchTerm() == "testText", "Wrong value returned"); +} + +void tst_QPlaceSearchRequest::categoriesTest() +{ + QPlaceSearchRequest testObj; + QVERIFY2(testObj.categories().count() == 0, "Wrong default value"); + QPlaceCategory cat; + cat.setCategoryId("45346"); + testObj.setCategory(cat); + QVERIFY2(testObj.categories().count() == 1, "Wrong categories count returned"); + QVERIFY2(testObj.categories()[0] == cat, "Wrong category returned"); + + testObj.setCategory(QPlaceCategory()); + QVERIFY(testObj.categories().isEmpty()); +} + +void tst_QPlaceSearchRequest::boundingCircleTest() +{ + QPlaceSearchRequest query; + QVERIFY2(query.searchArea() == QGeoShape(), "Wrong default value"); + QGeoCircle circle; + circle.setCenter(QGeoCoordinate(30,20)); + circle.setRadius(500.0); + query.setSearchArea(circle); + + QVERIFY(query.searchArea() != QGeoShape()); + QVERIFY(query.searchArea().type() == QGeoShape::CircleType); + QVERIFY(query.searchArea() == circle); + + QGeoCircle retrievedCircle = query.searchArea(); + QVERIFY2(retrievedCircle.center() == QGeoCoordinate(30,20), "Wrong value returned"); + QVERIFY2(retrievedCircle.radius() == 500.0, "Wrong value returned"); + query.clear(); + QVERIFY2(query.searchArea() == QGeoShape(), "Search area not cleared"); +} + +void tst_QPlaceSearchRequest::boundingBoxTest() +{ + QPlaceSearchRequest query; + QVERIFY2(query.searchArea() == QGeoShape(), "Wrong default value"); + QGeoRectangle box; + + box.setTopLeft(QGeoCoordinate(30,20)); + box.setBottomRight(QGeoCoordinate(10,50)); + query.setSearchArea(box); + + QVERIFY(query.searchArea() != QGeoShape()); + QVERIFY(query.searchArea().type() == QGeoShape::RectangleType); + QVERIFY(query.searchArea() == box); + + QGeoRectangle retrievedBox = query.searchArea(); + QVERIFY2(retrievedBox.topLeft() == QGeoCoordinate(30,20), "Wrong value returned"); + QVERIFY2(retrievedBox.bottomRight() == QGeoCoordinate(10,50), "Wrong value returned"); + + query.clear(); + QVERIFY2(query.searchArea() == QGeoShape(), "Wrong cleared value returned"); +} + +void tst_QPlaceSearchRequest::searchAreaTest() +{ + //test assignment of new search area over an old search area + QPlaceSearchRequest *query = new QPlaceSearchRequest; + QGeoCircle circle; + circle.setCenter(QGeoCoordinate(30,20)); + circle.setRadius(500.0); + query->setSearchArea(circle); + + QVERIFY(query->searchArea() == circle); + QGeoRectangle box; + box.setTopLeft(QGeoCoordinate(30,20)); + box.setBottomRight(QGeoCoordinate(10,50)); + query->setSearchArea(box); + QVERIFY2(query->searchArea() == box, "New search area not assigned"); +} + +void tst_QPlaceSearchRequest::visibilityScopeTest() +{ + QPlaceSearchRequest query; + QVERIFY2(query.visibilityScope() == QLocation::UnspecifiedVisibility, "Wrong default value"); + + query.setVisibilityScope(QLocation::DeviceVisibility); + QCOMPARE(query.visibilityScope(), QLocation::DeviceVisibility); + + query.setVisibilityScope(QLocation::DeviceVisibility | QLocation::PublicVisibility); + QVERIFY(query.visibilityScope() & QLocation::DeviceVisibility); + QVERIFY(!(query.visibilityScope() & QLocation::PrivateVisibility)); + QVERIFY(query.visibilityScope() & QLocation::PublicVisibility); +} + +void tst_QPlaceSearchRequest::relevanceHintTest() +{ + QPlaceSearchRequest request; + QCOMPARE(request.relevanceHint(), QPlaceSearchRequest::UnspecifiedHint); + request.setRelevanceHint(QPlaceSearchRequest::DistanceHint); + QCOMPARE(request.relevanceHint(), QPlaceSearchRequest::DistanceHint); + request.setRelevanceHint(QPlaceSearchRequest::UnspecifiedHint); + QCOMPARE(request.relevanceHint(), QPlaceSearchRequest::UnspecifiedHint); +} + +void tst_QPlaceSearchRequest::searchContextTest() +{ + QPlaceSearchRequest request; + QVERIFY(!request.searchContext().value().isValid()); + request.setSearchContext(QUrl(QStringLiteral("http://www.example.com/"))); + QCOMPARE(request.searchContext().value(), QUrl(QStringLiteral("http://www.example.com/"))); +} + +void tst_QPlaceSearchRequest::operatorsTest() +{ + QPlaceSearchRequest testObj; + testObj.setSearchTerm(QStringLiteral("testValue")); + QPlaceSearchRequest testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setSearchTerm(QStringLiteral("abc")); + QVERIFY2(testObj != testObj2, "Object should be different"); + testObj2.setSearchTerm(QStringLiteral("testValue")); + QVERIFY(testObj == testObj2); + + QGeoRectangle b1(QGeoCoordinate(20,20), QGeoCoordinate(10,30)); + QGeoRectangle b2(QGeoCoordinate(20,20), QGeoCoordinate(10,30)); + QGeoRectangle b3(QGeoCoordinate(40,40), QGeoCoordinate(10,40)); + + //testing that identical boxes match + testObj.setSearchArea(b1); + testObj2.setSearchArea(b2); + QVERIFY2(testObj == testObj2, "Identical box areas are not identified as matching"); + + //test that different boxes do not match + testObj2.setSearchArea(b3); + QVERIFY2(testObj != testObj2, "Different box areas identified as matching"); + + QGeoCircle c1(QGeoCoordinate(5,5),500); + QGeoCircle c2(QGeoCoordinate(5,5),500); + QGeoCircle c3(QGeoCoordinate(9,9),600); + + //test that identical cirlces match + testObj.setSearchArea(c1); + testObj2.setSearchArea(c2); + QVERIFY2(testObj == testObj2, "Identical circle areas are not identified as matching"); + + //test that different circle don't match + testObj2.setSearchArea(c3); + QVERIFY2(testObj != testObj2, "Different circle areas identified as matching"); + + //test that circles and boxes do not match + QGeoRectangle b4(QGeoCoordinate(20,20),QGeoCoordinate(10,30)); + QGeoCircle c4(QGeoCoordinate(20,20),500); + testObj.setSearchArea(b4); + testObj2.setSearchArea(c4); + QVERIFY2(testObj != testObj2, "Circle and box identified as matching"); + + //test that identical visibility scopes match + testObj.clear(); + testObj2.clear(); + testObj.setVisibilityScope(QLocation::PublicVisibility); + testObj2.setVisibilityScope(QLocation::PublicVisibility); + QVERIFY2(testObj == testObj2, "Identical scopes not identified as matching"); + + //test that different scopes do not match + testObj2.setVisibilityScope(QLocation::PrivateVisibility); + QVERIFY2(testObj != testObj2, "Different scopes identified as matching"); + + //test that different search contexts do not match + testObj.clear(); + testObj2.clear(); + testObj2.setSearchContext(QUrl(QStringLiteral("http://www.example.com/"))); + QVERIFY(testObj != testObj2); +} + +void tst_QPlaceSearchRequest::clearTest() +{ + QPlaceSearchRequest req; + req.setSearchTerm("pizza"); + req.setSearchArea(QGeoCircle(QGeoCoordinate(1,1), 5000)); + QPlaceCategory category; + category.setName("Fast Food"); + req.setCategory(category); + req.setLimit(100); + + req.clear(); + QVERIFY(req.searchTerm().isEmpty()); + QVERIFY(req.searchArea() == QGeoShape()); + QVERIFY(req.categories().isEmpty()); + QVERIFY(req.limit() == -1); +} + +QTEST_APPLESS_MAIN(tst_QPlaceSearchRequest) + +#include "tst_qplacesearchrequest.moc" diff --git a/tests/auto/qplacesearchresult/qplacesearchresult.pro b/tests/auto/qplacesearchresult/qplacesearchresult.pro new file mode 100644 index 0000000..935819a --- /dev/null +++ b/tests/auto/qplacesearchresult/qplacesearchresult.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacesearchresult + +SOURCES += tst_qplacesearchresult.cpp + +QT += location testlib diff --git a/tests/auto/qplacesearchresult/tst_qplacesearchresult.cpp b/tests/auto/qplacesearchresult/tst_qplacesearchresult.cpp new file mode 100644 index 0000000..9306392 --- /dev/null +++ b/tests/auto/qplacesearchresult/tst_qplacesearchresult.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QPlaceSearchResult : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void constructorTest(); + void title(); + void icon(); + void operators(); +}; + +void tst_QPlaceSearchResult::constructorTest() +{ + QPlaceSearchResult result; + + QCOMPARE(result.type(), QPlaceSearchResult::UnknownSearchResult); + QVERIFY(result.title().isEmpty()); + QVERIFY(result.icon().isEmpty()); + + result.setTitle(QStringLiteral("title")); + QPlaceIcon icon; + QVariantMap parameters; + parameters.insert(QStringLiteral("paramKey"), QStringLiteral("paramValue")); + icon.setParameters(parameters); + result.setIcon(icon); + + QPlaceSearchResult result2(result); + QCOMPARE(result2.title(), QStringLiteral("title")); + QCOMPARE(result2.icon().parameters().value(QStringLiteral("paramKey")).toString(), + QStringLiteral("paramValue")); + + QCOMPARE(result2, result); +} + +void tst_QPlaceSearchResult::title() +{ + QPlaceSearchResult result; + QVERIFY(result.title().isEmpty()); + result.setTitle(QStringLiteral("title")); + QCOMPARE(result.title(), QStringLiteral("title")); + result.setTitle(QString()); + QVERIFY(result.title().isEmpty()); +} + +void tst_QPlaceSearchResult::icon() +{ + QPlaceSearchResult result; + QVERIFY(result.icon().isEmpty()); + QPlaceIcon icon; + QVariantMap iconParams; + iconParams.insert(QStringLiteral("paramKey"), QStringLiteral("paramValue")); + result.setIcon(icon); + QCOMPARE(result.icon(), icon); + result.setIcon(QPlaceIcon()); + QVERIFY(result.icon().isEmpty()); +} + +void tst_QPlaceSearchResult::operators() +{ + QPlaceSearchResult result1; + QPlaceSearchResult result2; + + QVERIFY(result1 == result2); + QVERIFY(!(result1 != result2)); + + result1.setTitle(QStringLiteral("title")); + QVERIFY(!(result1 == result2)); + QVERIFY(result1 != result2); + + result2.setTitle(QStringLiteral("title")); + QVERIFY(result1 == result2); + QVERIFY(!(result1 != result2)); +} + +QTEST_APPLESS_MAIN(tst_QPlaceSearchResult) + +#include "tst_qplacesearchresult.moc" diff --git a/tests/auto/qplacesearchsuggestionreply/qplacesearchsuggestionreply.pro b/tests/auto/qplacesearchsuggestionreply/qplacesearchsuggestionreply.pro new file mode 100644 index 0000000..c436333 --- /dev/null +++ b/tests/auto/qplacesearchsuggestionreply/qplacesearchsuggestionreply.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacesearchsuggestionreply + +SOURCES += tst_qplacesearchsuggestionreply.cpp + +QT += location testlib diff --git a/tests/auto/qplacesearchsuggestionreply/tst_qplacesearchsuggestionreply.cpp b/tests/auto/qplacesearchsuggestionreply/tst_qplacesearchsuggestionreply.cpp new file mode 100644 index 0000000..8670e44 --- /dev/null +++ b/tests/auto/qplacesearchsuggestionreply/tst_qplacesearchsuggestionreply.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class SuggestionReply : public QPlaceSearchSuggestionReply +{ + Q_OBJECT +public: + SuggestionReply(QObject *parent) : QPlaceSearchSuggestionReply(parent){} + + void setSuggestions(const QStringList &suggestions) { + QPlaceSearchSuggestionReply::setSuggestions(suggestions); + } +}; + +class tst_QPlaceSearchSuggestionReply : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceSearchSuggestionReply(); + +private Q_SLOTS: + void constructorTest(); + void typeTest(); + void suggestionsTest(); +}; + +tst_QPlaceSearchSuggestionReply::tst_QPlaceSearchSuggestionReply() +{ +} + +void tst_QPlaceSearchSuggestionReply::constructorTest() +{ + SuggestionReply *reply = new SuggestionReply(this); + QCOMPARE(reply->parent(), this); + + delete reply; +} + +void tst_QPlaceSearchSuggestionReply::typeTest() +{ + SuggestionReply *reply = new SuggestionReply(this); + QCOMPARE(reply->type(), QPlaceReply::SearchSuggestionReply); + + delete reply; +} + +void tst_QPlaceSearchSuggestionReply::suggestionsTest() +{ + QStringList suggestions; + suggestions << QStringLiteral("one") << QStringLiteral("two") + << QStringLiteral("three"); + + SuggestionReply *reply = new SuggestionReply(this); + reply->setSuggestions(suggestions); + QCOMPARE(reply->suggestions(), suggestions); + + delete reply; +} + +QTEST_APPLESS_MAIN(tst_QPlaceSearchSuggestionReply) + +#include "tst_qplacesearchsuggestionreply.moc" diff --git a/tests/auto/qplacesupplier/qplacesupplier.pro b/tests/auto/qplacesupplier/qplacesupplier.pro new file mode 100644 index 0000000..06715f4 --- /dev/null +++ b/tests/auto/qplacesupplier/qplacesupplier.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplacesupplier + +SOURCES += tst_qplacesupplier.cpp + +QT += location testlib diff --git a/tests/auto/qplacesupplier/tst_qplacesupplier.cpp b/tests/auto/qplacesupplier/tst_qplacesupplier.cpp new file mode 100644 index 0000000..6d6bdf8 --- /dev/null +++ b/tests/auto/qplacesupplier/tst_qplacesupplier.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class tst_QPlaceSupplier : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceSupplier(); + +private Q_SLOTS: + void constructorTest(); + void nameTest(); + void supplierIdTest(); + void urlTest(); + void iconTest(); + void operatorsTest(); + void isEmptyTest(); +}; + +tst_QPlaceSupplier::tst_QPlaceSupplier() +{ +} + +void tst_QPlaceSupplier::constructorTest() +{ + QPlaceSupplier testObj; + Q_UNUSED(testObj); + + QPlaceSupplier *testObjPtr = new QPlaceSupplier(testObj); + QVERIFY2(testObjPtr != NULL, "Copy constructor - null"); + QVERIFY2(*testObjPtr == testObj, "Copy constructor - compare"); + delete testObjPtr; +} + +void tst_QPlaceSupplier::nameTest() +{ + QPlaceSupplier testObj; + QVERIFY2(testObj.name() == QString(), "Wrong default value"); + testObj.setName("testText"); + QVERIFY2(testObj.name() == "testText", "Wrong value returned"); +} + +void tst_QPlaceSupplier::supplierIdTest() +{ + QPlaceSupplier testObj; + QVERIFY2(testObj.supplierId() == QString(), "Wrong default value"); + testObj.setSupplierId("testText"); + QVERIFY2(testObj.supplierId() == "testText", "Wrong value returned"); +} + +void tst_QPlaceSupplier::urlTest() +{ + QPlaceSupplier testObj; + const QUrl testUrl = QUrl::fromEncoded("http://example.com/testUrl"); + QVERIFY2(testObj.url() == QString(), "Wrong default value"); + testObj.setUrl(testUrl); + QVERIFY2(testObj.url() == testUrl, "Wrong value returned"); +} + +void tst_QPlaceSupplier::iconTest() +{ + QPlaceSupplier testObj; + QVERIFY(testObj.icon().isEmpty()); + QPlaceIcon icon; + QVariantMap iconParams; + iconParams.insert(QPlaceIcon::SingleUrl, QUrl::fromEncoded("http://example.com/icon.png")); + icon.setParameters(iconParams); + testObj.setIcon(icon); + QCOMPARE(testObj.icon(), icon); + QCOMPARE(testObj.icon().url(), QUrl::fromEncoded("http://example.com/icon.png")); + + testObj.setIcon(QPlaceIcon()); + QVERIFY(testObj.icon().isEmpty()); + QCOMPARE(testObj.icon().url(), QUrl()); +} + +void tst_QPlaceSupplier::operatorsTest() +{ + QPlaceSupplier testObj; + testObj.setName(QStringLiteral("Acme")); + QPlaceIcon icon; + QVariantMap iconParams; + iconParams.insert(QPlaceIcon::SingleUrl, QUrl::fromEncoded("http://example.com/icon.png")); + icon.setParameters(iconParams); + testObj.setIcon(icon); + testObj.setSupplierId(QStringLiteral("34292")); + + QPlaceSupplier testObj2; + testObj2 = testObj; + QVERIFY2(testObj == testObj2, "Not copied correctly"); + testObj2.setSupplierId(QStringLiteral("testValue2")); + QVERIFY2(testObj != testObj2, "Object should be different"); +} + +void tst_QPlaceSupplier::isEmptyTest() +{ + QPlaceIcon icon; + QVariantMap iconParametersMap; + iconParametersMap.insert(QStringLiteral("Para"), QStringLiteral("meter")); + icon.setParameters(iconParametersMap); + QVERIFY(!icon.isEmpty()); + + QPlaceSupplier supplier; + + QVERIFY(supplier.isEmpty()); + + // name + supplier.setName(QStringLiteral("Name")); + QVERIFY(!supplier.isEmpty()); + supplier.setName(QString()); + QVERIFY(supplier.isEmpty()); + + // supplierId + supplier.setSupplierId(QStringLiteral("1")); + QVERIFY(!supplier.isEmpty()); + supplier.setSupplierId(QString()); + QVERIFY(supplier.isEmpty()); + + // url + supplier.setUrl(QUrl(QStringLiteral("www.example.com"))); + QVERIFY(!supplier.isEmpty()); + supplier.setUrl(QUrl()); + QVERIFY(supplier.isEmpty()); + + // icon + supplier.setIcon(icon); + QVERIFY(!supplier.isEmpty()); + supplier.setIcon(QPlaceIcon()); + QVERIFY(supplier.isEmpty()); +} + +QTEST_APPLESS_MAIN(tst_QPlaceSupplier); + +#include "tst_qplacesupplier.moc" diff --git a/tests/auto/qplaceuser/qplaceuser.pro b/tests/auto/qplaceuser/qplaceuser.pro new file mode 100644 index 0000000..3eab126 --- /dev/null +++ b/tests/auto/qplaceuser/qplaceuser.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qplaceuser + +SOURCES += tst_qplaceuser.cpp + +QT += location testlib diff --git a/tests/auto/qplaceuser/tst_qplaceuser.cpp b/tests/auto/qplaceuser/tst_qplaceuser.cpp new file mode 100644 index 0000000..086b184 --- /dev/null +++ b/tests/auto/qplaceuser/tst_qplaceuser.cpp @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +QT_USE_NAMESPACE + +class tst_QPlaceUser : public QObject +{ + Q_OBJECT + +public: + tst_QPlaceUser(); + +private Q_SLOTS: + void constructorTest(); + void nameTest(); + void userIdTest(); + void operatorsTest(); + void operatorsTest_data(); +}; + +tst_QPlaceUser::tst_QPlaceUser() +{ +} + +void tst_QPlaceUser::constructorTest() +{ + QPlaceUser user; + QVERIFY(user.name().isEmpty()); + QVERIFY(user.userId().isEmpty()); + + user.setName(QStringLiteral("Thomas Anderson")); + user.setUserId(QStringLiteral("Neo")); + + QPlaceUser user2(user); + QCOMPARE(user2.name(), QStringLiteral("Thomas Anderson")); + QCOMPARE(user2.userId(), QStringLiteral("Neo")); +} + +void tst_QPlaceUser::nameTest() +{ + QPlaceUser user; + user.setName(QStringLiteral("Thomas Anderson")); + QCOMPARE(user.name(), QStringLiteral("Thomas Anderson")); + user.setName(QString()); + QVERIFY(user.name().isEmpty()); +} + +void tst_QPlaceUser::userIdTest() +{ + QPlaceUser user; + user.setUserId(QStringLiteral("Neo")); + QCOMPARE(user.userId(), QStringLiteral("Neo")); + user.setUserId(QString()); + QVERIFY(user.userId().isEmpty()); +} + +void tst_QPlaceUser::operatorsTest() +{ + QPlaceUser user1; + user1.setName(QStringLiteral("Thomas Anderson")); + user1.setUserId(QStringLiteral("Neo")); + + QPlaceUser user2; + user2.setName(QStringLiteral("Thomas Anderson")); + user2.setUserId(QStringLiteral("Neo")); + + QVERIFY(user1 == user2); + QVERIFY(!(user1 != user2)); + QVERIFY(user2 == user1); + QVERIFY(!(user2 != user1)); + + QPlaceUser user3; + QVERIFY(!(user1 == user3)); + QVERIFY(user1 != user3); + QVERIFY(!(user3 == user1)); + QVERIFY(user3 != user1); + + user3 = user1; + QVERIFY(user1 == user3); + QVERIFY(!(user1 != user3)); + QVERIFY(user3 == user1); + QVERIFY(!(user3 != user1)); + + QFETCH(QString, field); + + if (field == QStringLiteral("name")) + user3.setName(QStringLiteral("bob")); + else if (field == QStringLiteral("userId")) + user3.setUserId(QStringLiteral("Morpheus")); + else + qFatal("Unknown data field"); + + QVERIFY(!(user1 == user3)); + QVERIFY(user1 != user3); + QVERIFY(!(user3 == user1)); + QVERIFY(user3 != user1); +} + +void tst_QPlaceUser::operatorsTest_data() +{ + QTest::addColumn("field"); + + QTest::newRow("user name") << "name"; + QTest::newRow("user id") << "userId"; +} + +QTEST_APPLESS_MAIN(tst_QPlaceUser) + +#include "tst_qplaceuser.moc" diff --git a/tests/auto/qproposedsearchresult/qproposedsearchresult.pro b/tests/auto/qproposedsearchresult/qproposedsearchresult.pro new file mode 100644 index 0000000..bae784c --- /dev/null +++ b/tests/auto/qproposedsearchresult/qproposedsearchresult.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qproposedsearchresult + +SOURCES += tst_qproposedsearchresult.cpp + +QT += location testlib diff --git a/tests/auto/qproposedsearchresult/tst_qproposedsearchresult.cpp b/tests/auto/qproposedsearchresult/tst_qproposedsearchresult.cpp new file mode 100644 index 0000000..8693445 --- /dev/null +++ b/tests/auto/qproposedsearchresult/tst_qproposedsearchresult.cpp @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Aaron McCarthy +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "../utils/qlocationtestutils_p.h" + +QT_USE_NAMESPACE + +class tst_QPlaceProposedSearchResult : public QObject +{ + Q_OBJECT + +public: + QPlaceProposedSearchResult initialSubObject(); + bool checkType(const QPlaceSearchResult &result); + void detach(QPlaceSearchResult *result); + void setSubClassProperty(QPlaceProposedSearchResult *result); + +private Q_SLOTS: + void constructorTest(); + void title(); + void icon(); + void searchRequest(); + void conversion(); +}; + +QPlaceProposedSearchResult tst_QPlaceProposedSearchResult::initialSubObject() +{ + QPlaceProposedSearchResult proposedSearchResult; + proposedSearchResult.setTitle(QStringLiteral("title")); + + QPlaceIcon icon; + QVariantMap parameters; + parameters.insert(QPlaceIcon::SingleUrl, + QUrl(QStringLiteral("file:///opt/icons/icon.png"))); + icon.setParameters(parameters); + proposedSearchResult.setIcon(icon); + + QPlaceSearchRequest searchRequest; + searchRequest.setSearchContext(QUrl(QStringLiteral("http://www.example.com/"))); + proposedSearchResult.setSearchRequest(searchRequest); + + return proposedSearchResult; +} + +bool tst_QPlaceProposedSearchResult::checkType(const QPlaceSearchResult &result) +{ + return result.type() == QPlaceSearchResult::ProposedSearchResult; +} + +void tst_QPlaceProposedSearchResult::detach(QPlaceSearchResult *result) +{ + result->setTitle(QStringLiteral("title")); +} + +void tst_QPlaceProposedSearchResult::setSubClassProperty(QPlaceProposedSearchResult *result) +{ + QPlaceSearchRequest request; + request.setSearchContext(QUrl(QStringLiteral("http://www.example.com/place-search"))); + result->setSearchRequest(request); +} + +void tst_QPlaceProposedSearchResult::constructorTest() +{ + QPlaceProposedSearchResult result; + QCOMPARE(result.type(), QPlaceSearchResult::ProposedSearchResult); + + result.setTitle(QStringLiteral("title")); + + QPlaceIcon icon; + QVariantMap parameters; + parameters.insert(QStringLiteral("paramKey"), QStringLiteral("paramValue")); + icon.setParameters(parameters); + result.setIcon(icon); + + QPlaceSearchRequest searchRequest; + searchRequest.setSearchContext(QUrl(QStringLiteral("http://www.example.com/place-search"))); + result.setSearchRequest(searchRequest); + + //check copy constructor + QPlaceProposedSearchResult result2(result); + QCOMPARE(result2.title(), QStringLiteral("title")); + QCOMPARE(result2.icon(), icon); + QCOMPARE(result2.searchRequest(), searchRequest); + + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + //check results are the same after detachment of underlying shared data pointer + result2.setTitle(QStringLiteral("title")); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + //check construction of SearchResult using a ProposedSearchResult + QPlaceSearchResult searchResult(result); + QCOMPARE(searchResult.title(), QStringLiteral("title")); + QCOMPARE(searchResult.icon(), icon); + QVERIFY(QLocationTestUtils::compareEquality(searchResult, result)); + QVERIFY(searchResult.type() == QPlaceSearchResult::ProposedSearchResult); + result2 = searchResult; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + //check construction of a SearchResult using a SearchResult + //that is actually a PlaceResult underneath + QPlaceSearchResult searchResult2(searchResult); + QCOMPARE(searchResult2.title(), QStringLiteral("title")); + QCOMPARE(searchResult2.icon(), icon); + QVERIFY(QLocationTestUtils::compareEquality(searchResult2, result)); + QVERIFY(QLocationTestUtils::compareEquality(searchResult, searchResult2)); + QVERIFY(searchResult2.type() == QPlaceSearchResult::ProposedSearchResult); + result2 = searchResult2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceProposedSearchResult::title() +{ + QPlaceProposedSearchResult result; + QVERIFY(result.title().isEmpty()); + + result.setTitle(QStringLiteral("title")); + QCOMPARE(result.title(), QStringLiteral("title")); + + result.setTitle(QString()); + QVERIFY(result.title().isEmpty()); + + QPlaceProposedSearchResult result2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + result2.setTitle("title"); + QVERIFY(QLocationTestUtils::compareInequality(result, result2)); + + result.setTitle("title"); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceProposedSearchResult::icon() +{ + QPlaceProposedSearchResult result; + QVERIFY(result.icon().isEmpty()); + + QPlaceIcon icon; + QVariantMap iconParams; + iconParams.insert(QStringLiteral("paramKey"), QStringLiteral("paramValue")); + icon.setParameters(iconParams); + result.setIcon(icon); + QCOMPARE(result.icon(), icon); + + result.setIcon(QPlaceIcon()); + QVERIFY(result.icon().isEmpty()); + + QPlaceProposedSearchResult result2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + result2.setIcon(icon); + QVERIFY(QLocationTestUtils::compareInequality(result, result2)); + + result.setIcon(icon); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceProposedSearchResult::searchRequest() +{ + QPlaceProposedSearchResult result; + QCOMPARE(result.searchRequest(), QPlaceSearchRequest()); + + QPlaceSearchRequest placeSearchRequest; + placeSearchRequest.setSearchContext(QUrl(QStringLiteral("http://www.example.com/"))); + result.setSearchRequest(placeSearchRequest); + QCOMPARE(result.searchRequest(), placeSearchRequest); + + result.setSearchRequest(QPlaceSearchRequest()); + QCOMPARE(result.searchRequest(), QPlaceSearchRequest()); + + QPlaceProposedSearchResult result2; + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); + + result2.setSearchRequest(placeSearchRequest); + QVERIFY(QLocationTestUtils::compareInequality(result, result2)); + + result.setSearchRequest(placeSearchRequest); + QVERIFY(QLocationTestUtils::compareEquality(result, result2)); +} + +void tst_QPlaceProposedSearchResult::conversion() +{ + QLocationTestUtils::testConversion(this); +} + +QTEST_APPLESS_MAIN(tst_QPlaceProposedSearchResult) + +#include "tst_qproposedsearchresult.moc" diff --git a/tests/auto/utils/qlocationtestutils.cpp b/tests/auto/utils/qlocationtestutils.cpp new file mode 100644 index 0000000..d6e7785 --- /dev/null +++ b/tests/auto/utils/qlocationtestutils.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocationtestutils_p.h" + +bool QLocationTestUtils::hasDefaultSource() +{ + return false; +} + +bool QLocationTestUtils::hasDefaultMonitor() +{ + return false; +} + +QString QLocationTestUtils::addNmeaChecksumAndBreaks(const QString &sentence) +{ + Q_ASSERT(sentence[0] == '$' && sentence[sentence.length()-1] == '*'); + + // XOR byte value of all characters between '$' and '*' + int result = 0; + for (int i=1; i +#include +#include + +namespace QLocationTestUtils +{ + bool hasDefaultSource(); + bool hasDefaultMonitor(); + + QString addNmeaChecksumAndBreaks(const QString &sentence); + + QString createRmcSentence(const QDateTime &dt); + QString createGgaSentence(const QTime &time); + QString createGgaSentence(int lat, int lng, const QTime &time); + QString createZdaSentence(const QDateTime &dt); + QString createGsaSentence(); + + //The purpose of compareEquality() is to test equality + //operators where it is expected that A == B. + template + bool compareEquality(const A &first, const B &second) { + if (first != second) { + qWarning() << "compareEquality() failed: first != second"; + return false; + } + + if (second != first) { + qWarning() << "compareEquality() failed: second != first"; + return false; + } + + if (!(first == second)) { + qWarning() << "compareEquality() failed: !(first == second)"; + return false; + } + + if (!(second == first)) { + qWarning() << "compareEquality() failed: !(second == first)"; + return false; + } + + return true; + } + + //The purpose of compareInequality() is to test equality + //operators where it is expected that A != B. + //Using !compareEquality(...) is not sufficient because + //only the first operator checked would end up being tested. + template + bool compareInequality(const A &first, const B &second) { + if (!(first != second)){ + qWarning() << "compareInequality() failed: !(first != second)"; + return false; + } + + if (!(second != first)) { + qWarning() << "compareInequality() failed: !(second != first)"; + return false; + } + + if (first == second) { + qWarning() << "compareInequality() failed: first == second)"; + return false; + } + + if (second == first) { + qWarning() << "compareInequality() failed: second == first"; + return false; + } + return true; + } + + // Tests conversions between sub and base classes + // TC (test case) must implement: + // SubClass initialSubObject(); + // bool checkType(const BaseClass &) + // void detach(BaseClass *) - calls a mutator method, but doesn't actually modify the + // property to something different. + // void setSubClassProperty(SubClass *) - sets a property in the subclass instance + template + void testConversion(TC *tc) { + SubClass sub = tc->initialSubObject(); + //check conversion from SubClass -> BaseClass + //using assignment operator + BaseClass base; + base = sub; + QVERIFY(QLocationTestUtils::compareEquality(base, sub)); + QVERIFY(tc->checkType(base)); + + //check comparing base classes + BaseClass base2; + base2 = sub; + QVERIFY(QLocationTestUtils::compareEquality(base, base2)); + + //check conversion from BaseClass -> SubClass + //using assignment operator + SubClass sub2; + sub2 = base; + QVERIFY(QLocationTestUtils::compareEquality(sub, sub2)); + QVERIFY(tc->checkType(sub2)); + + //check that equality still holds with detachment of underlying data pointer + tc->detach(&base); + sub2 = base; + QVERIFY(QLocationTestUtils::compareEquality(sub, sub2)); + QVERIFY(QLocationTestUtils::compareEquality(sub, base)); + QVERIFY(QLocationTestUtils::compareEquality(base, base2)); + + //check that comparing objects are not the same + //when an underlying subclass field has been modified + tc->setSubClassProperty(&sub2); + base2 = sub2; + QVERIFY(QLocationTestUtils::compareInequality(sub, sub2)); + QVERIFY(QLocationTestUtils::compareInequality(sub, base2)); + QVERIFY(QLocationTestUtils::compareInequality(base, base2)); + + //check conversion from SubClass -> BaseClass + //using copy constructor + BaseClass base3(sub); + QVERIFY(QLocationTestUtils::compareEquality(sub, base3)); + QVERIFY(QLocationTestUtils::compareEquality(base, base3)); + + //check conversion from BaseClass -> SubClass + //using copy constructor + SubClass sub3(base3); + QVERIFY(QLocationTestUtils::compareEquality(sub, sub3)); + + //check conversion to subclass using a default base class instance + BaseClass baseDefault; + SubClass subDefault; + SubClass sub4(baseDefault); + QVERIFY(QLocationTestUtils::compareEquality(sub4, subDefault)); + + SubClass sub5 = baseDefault; + QVERIFY(QLocationTestUtils::compareEquality(sub5, subDefault)); + } +}; + +#endif diff --git a/tests/global/global.cfg b/tests/global/global.cfg new file mode 100644 index 0000000..8cc6269 --- /dev/null +++ b/tests/global/global.cfg @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/plugins/declarativetestplugin/declarativetestplugin.pro b/tests/plugins/declarativetestplugin/declarativetestplugin.pro new file mode 100644 index 0000000..0a99e7a --- /dev/null +++ b/tests/plugins/declarativetestplugin/declarativetestplugin.pro @@ -0,0 +1,27 @@ +CXX_MODULE = location +TARGET = declarative_location_test +TARGETPATH = QtLocation/Test + +QT += gui-private qml quick location testlib + +INCLUDEPATH += ../../../src/imports/location +INCLUDEPATH += ../../../src/location + +HEADERS += \ + qdeclarativepinchgenerator_p.h \ + qdeclarativelocationtestmodel_p.h \ + testhelper.h + +SOURCES += \ + locationtest.cpp \ + qdeclarativepinchgenerator.cpp \ + qdeclarativelocationtestmodel.cpp + +IMPORT_FILES = \ + qmldir + +load(qml_plugin) + + +# must be after load(qml_plugin) +include(../imports.pri) diff --git a/tests/plugins/declarativetestplugin/locationtest.cpp b/tests/plugins/declarativetestplugin/locationtest.cpp new file mode 100644 index 0000000..f0389d9 --- /dev/null +++ b/tests/plugins/declarativetestplugin/locationtest.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativepinchgenerator_p.h" +#include "qdeclarativelocationtestmodel_p.h" +#include "testhelper.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +static QObject *helper_factory(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine); + Q_UNUSED(scriptEngine); + TestHelper *helper = new TestHelper(); + return helper; +} + +class QLocationDeclarativeTestModule: public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0") +public: + virtual void registerTypes(const char* uri) + { + if (QLatin1String(uri) == QLatin1String("QtLocation.Test")) { + qmlRegisterType(uri, 5, 5, "PinchGenerator"); + qmlRegisterType(uri, 5, 5, "TestModel"); + qmlRegisterSingletonType(uri, 5, 6, "LocationTestHelper", helper_factory); + } else { + qWarning() << "Unsupported URI given to load location test QML plugin: " << QLatin1String(uri); + } + } +}; + +#include "locationtest.moc" + +QT_END_NAMESPACE + diff --git a/tests/plugins/declarativetestplugin/qdeclarativelocationtestmodel.cpp b/tests/plugins/declarativetestplugin/qdeclarativelocationtestmodel.cpp new file mode 100644 index 0000000..e000459 --- /dev/null +++ b/tests/plugins/declarativetestplugin/qdeclarativelocationtestmodel.cpp @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativelocationtestmodel_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QDeclarativeLocationTestModel::QDeclarativeLocationTestModel(QObject *parent): + QAbstractListModel(parent), + delay_(0), + datacount_(0), + componentCompleted_(false), + crazyLevel_(0), + crazyMode_(false) +{ + // seed crazy random generator + qsrand(QTime(0,0,0).secsTo(QTime::currentTime()) + QCoreApplication::applicationPid()); + timer_.setSingleShot(true); + connect(&timer_, SIGNAL(timeout()), this, SLOT(timerFired())); +} + +QDeclarativeLocationTestModel::~QDeclarativeLocationTestModel() +{ + if (timer_.isActive()) + timer_.stop(); + if (!dataobjects_.isEmpty()) { + qDeleteAll(dataobjects_); + dataobjects_.clear(); + } +} + +void QDeclarativeLocationTestModel::timerFired() +{ + //qDebug() << "timer fired" ; + repopulate(); + if (crazyMode_) { + //qDebug() << "raw randomw value: " << qrand(); + int delay = (qAbs(qrand()) % crazyLevel_); // writing software is exact science + delay = qMax(1000, delay); // 3 ms at minimum + qDebug() << "starting timer with : " << delay; + timer_.start(delay); + } +} + +void QDeclarativeLocationTestModel::componentComplete() +{ + componentCompleted_ = true; + scheduleRepopulation(); +} + + +int QDeclarativeLocationTestModel::datacount() const +{ + return datacount_; +} + +void QDeclarativeLocationTestModel::setDatacount(int datacount) +{ + if (datacount_ == datacount) + return; + datacount_ = datacount; + emit datacountChanged(); + scheduleRepopulation(); +} + +int QDeclarativeLocationTestModel::delay() const +{ + return delay_; +} + +void QDeclarativeLocationTestModel::setDelay(int delay) +{ + if (delay_ == delay) + return; + delay_ = delay; + emit delayChanged(); +} + +QString QDeclarativeLocationTestModel::datatype() const +{ + return datatype_; +} + +void QDeclarativeLocationTestModel::setDatatype(QString datatype) +{ + if (datatype_ == datatype) + return; + datatype_ = datatype; + emit datatypeChanged(); + scheduleRepopulation(); +} + +int QDeclarativeLocationTestModel::crazyLevel() const +{ + return crazyLevel_; +} + +void QDeclarativeLocationTestModel::setCrazyLevel(int level) +{ + if (level == crazyLevel_) + return; + crazyLevel_ = level; + reset(); + scheduleRepopulation(); + emit crazyLevelChanged(); +} + +bool QDeclarativeLocationTestModel::crazyMode() const +{ + return crazyMode_; +} + +void QDeclarativeLocationTestModel::setCrazyMode(bool mode) +{ + if (mode == crazyMode_) + return; + crazyMode_ = mode; + //if (!crazyMode_) + //reset(); + //else + if (crazyMode_) + scheduleRepopulation(); + emit crazyModeChanged(); +} + +// only coordinate datatype for now to get started with, +// refactor if more usecases for the model emerge. +void QDeclarativeLocationTestModel::repopulate() +{ + double latitude = -30; + double longitude = 153; + beginResetModel(); + if (!dataobjects_.isEmpty()) { + qDeleteAll(dataobjects_); + dataobjects_.clear(); + } + int datacount = datacount_; + if (crazyMode_) + datacount = (qAbs(qrand()) % datacount_); + + for (int i = 0; i < datacount; ++i) { + DataObject* dataobject = new DataObject; + dataobject->coordinate_ = QGeoCoordinate(latitude, longitude); + dataobjects_.append(dataobject); + longitude -= 0.2; + latitude += 0.2; + } + endResetModel(); +} + +void QDeclarativeLocationTestModel::update() +{ + scheduleRepopulation(); +} + +void QDeclarativeLocationTestModel::reset() +{ + if (timer_.isActive()) + timer_.stop(); + beginResetModel(); + if (!dataobjects_.isEmpty()) { + qDeleteAll(dataobjects_); + dataobjects_.clear(); + } + endResetModel(); +} + +void QDeclarativeLocationTestModel::scheduleRepopulation() +{ + if (!componentCompleted_) + return; + + if (datacount_ <= 0) + return; + + if (timer_.isActive()) + timer_.stop(); + + if (crazyMode_) { + // start generating arbitrary amount of data at arbitrary intervals + int delay = (qAbs(qrand()) % crazyLevel_); // writing software is exact science + delay = qMax(3, delay); // 3 ms at minimum + qDebug() << "starting timer with : " << delay; + timer_.start(delay); + } else { + // just update + if (delay_ > 0) + timer_.start(delay_); + else + repopulate(); + } +} + +int QDeclarativeLocationTestModel::rowCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent); + return dataobjects_.count(); +} + +// Returns the stored under the given role for the item referred to by the index. +QVariant QDeclarativeLocationTestModel::data(const QModelIndex& index, int role) const +{ + switch (role) { + case TestDataRole: + if (dataobjects_.at(index.row())) { + return QVariant::fromValue(qobject_cast(dataobjects_.at(index.row()))); + } + break; + } + return QVariant(); +} + +QHash QDeclarativeLocationTestModel::roleNames() const +{ + QHash roles = QAbstractListModel::roleNames(); + roles.insert(TestDataRole, "modeldata"); + return roles; +} + +QT_END_NAMESPACE diff --git a/tests/plugins/declarativetestplugin/qdeclarativelocationtestmodel_p.h b/tests/plugins/declarativetestplugin/qdeclarativelocationtestmodel_p.h new file mode 100644 index 0000000..925125a --- /dev/null +++ b/tests/plugins/declarativetestplugin/qdeclarativelocationtestmodel_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVELOCATIONTESTMODEL_H +#define QDECLARATIVELOCATIONTESTMODEL_H + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class DataObject: public QObject +{ + Q_OBJECT + Q_PROPERTY(QGeoCoordinate coordinate READ coordinate CONSTANT) + +public: + DataObject() {} + ~DataObject() {} + + QGeoCoordinate coordinate_; + QGeoCoordinate coordinate() const {return coordinate_;} +}; + +class QDeclarativeLocationTestModel : public QAbstractListModel, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(int datacount READ datacount WRITE setDatacount NOTIFY datacountChanged) + Q_PROPERTY(int delay READ delay WRITE setDelay NOTIFY delayChanged) + Q_PROPERTY(bool crazyMode READ crazyMode WRITE setCrazyMode NOTIFY crazyModeChanged) + Q_PROPERTY(int crazyLevel READ crazyLevel WRITE setCrazyLevel NOTIFY crazyLevelChanged) + Q_PROPERTY(QString datatype READ datatype WRITE setDatatype NOTIFY datatypeChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + QDeclarativeLocationTestModel(QObject* parent = 0); + ~QDeclarativeLocationTestModel(); + + enum Roles { + TestDataRole = Qt::UserRole + 500 + }; + + // from QQmlParserStatus + virtual void componentComplete(); + virtual void classBegin() {} + + // From QAbstractListModel + virtual int rowCount(const QModelIndex &parent) const; + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QHash roleNames() const; + + int datacount() const; + void setDatacount(int datacount); + + int delay() const; + void setDelay(int delay); + + int crazyLevel() const; + void setCrazyLevel(int level); + + bool crazyMode() const; + void setCrazyMode(bool mode); + + QString datatype() const; + void setDatatype(QString datatype); + + //Q_INVOKABLE void clear(); + Q_INVOKABLE void reset(); + Q_INVOKABLE void update(); + //Q_INVOKABLE void reset(); + +signals: + void countChanged(); + void datacountChanged(); + void datatypeChanged(); + void delayChanged(); + void modelChanged(); + void crazyLevelChanged(); + void crazyModeChanged(); + +private slots: + void repopulate(); + void timerFired(); + +private: + void scheduleRepopulation(); + +private: + int delay_; + int datacount_; + bool componentCompleted_; + QString datatype_; + QTimer timer_; + QList dataobjects_; + int crazyLevel_; + bool crazyMode_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/tests/plugins/declarativetestplugin/qdeclarativepinchgenerator.cpp b/tests/plugins/declarativetestplugin/qdeclarativepinchgenerator.cpp new file mode 100644 index 0000000..3026cd9 --- /dev/null +++ b/tests/plugins/declarativetestplugin/qdeclarativepinchgenerator.cpp @@ -0,0 +1,382 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativepinchgenerator_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QDeclarativePinchGenerator::QDeclarativePinchGenerator(): + target_(0), + state_(Invalid), + window_(0), + activeSwipe_(0), + replayTimer_(-1), + replayBookmark_(-1), + masterSwipe_(-1), + replaySpeedFactor_(1.0), + enabled_(true) +{ + setAcceptedMouseButtons(Qt::LeftButton | Qt::MidButton | Qt::RightButton); + swipeTimer_.invalidate(); + device_ = new QTouchDevice; + device_->setType(QTouchDevice::TouchScreen); + QWindowSystemInterface::registerTouchDevice(device_); +} + +QDeclarativePinchGenerator::~QDeclarativePinchGenerator() +{ + clear(); +} + +void QDeclarativePinchGenerator::componentComplete() +{ + QQuickItem::componentComplete(); +} + +void QDeclarativePinchGenerator::mousePressEvent(QMouseEvent *event) +{ + if (state_ != Idle || !enabled_) { + event->ignore(); + return; + } + Q_ASSERT(!activeSwipe_); + Q_ASSERT(!swipeTimer_.isValid()); + // Start recording a pinch gesture. + activeSwipe_ = new Swipe; + activeSwipe_->touchPoints << event->pos(); + activeSwipe_->durations << 0; + swipeTimer_.start(); + setState(Recording); +} + +void QDeclarativePinchGenerator::mouseMoveEvent(QMouseEvent *event) +{ + if (state_ != Recording || !enabled_) { + event->ignore(); + return; + } + Q_ASSERT(activeSwipe_); + Q_ASSERT(swipeTimer_.isValid()); + + activeSwipe_->touchPoints << event->pos(); + activeSwipe_->durations << swipeTimer_.elapsed(); + swipeTimer_.restart(); +} + +void QDeclarativePinchGenerator::mouseReleaseEvent(QMouseEvent *event) +{ + if (state_ != Recording || !enabled_) { + event->ignore(); + return; + } + Q_ASSERT(activeSwipe_); + Q_ASSERT(swipeTimer_.isValid()); + activeSwipe_->touchPoints << event->pos(); + activeSwipe_->durations << swipeTimer_.elapsed(); + + if (swipes_.count() == SWIPES_REQUIRED) + delete swipes_.takeFirst(); + swipes_ << activeSwipe_; + activeSwipe_ = 0; + swipeTimer_.invalidate(); + if (window_ && target_) setState(Idle); else setState(Invalid); +} + +void QDeclarativePinchGenerator::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + if (!enabled_) { + event->ignore(); + return; + } + stop(); + clear(); + if (window_ && target_) setState(Idle); else setState(Invalid); +} + +void QDeclarativePinchGenerator::keyPressEvent(QKeyEvent *e) +{ + if (!enabled_) { + e->ignore(); + } + + if (e->key() == Qt::Key_C) { + clear(); + } else if (e->key() == Qt::Key_R) { + replay(); + } else if (e->key() == Qt::Key_S) { + stop(); + } else if (e->key() == Qt::Key_Plus) { + setReplaySpeedFactor(replaySpeedFactor() + 0.1); + } else if (e->key() == Qt::Key_Minus) { + setReplaySpeedFactor(replaySpeedFactor() - 0.1); + } else { + qDebug() << metaObject()->className() << "Unsupported key event."; + } +} + +bool QDeclarativePinchGenerator::enabled() const +{ + return enabled_; +} + + +void QDeclarativePinchGenerator::setEnabled(bool enabled) +{ + if (enabled == enabled_) + return; + enabled_ = enabled; + if (!enabled_) { + stop(); + clear(); + } + emit enabledChanged(); +} + + +qreal QDeclarativePinchGenerator::replaySpeedFactor() const +{ + return replaySpeedFactor_; +} + +void QDeclarativePinchGenerator::setReplaySpeedFactor(qreal factor) +{ + if (factor == replaySpeedFactor_ || factor < 0.001) + return; + replaySpeedFactor_ = factor; + emit replaySpeedFactorChanged(); +} + + +QString QDeclarativePinchGenerator::state() const +{ + switch (state_) { + case Invalid: + return "Invalid"; + case Idle: + return "Idle"; + break; + case Recording: + return "Recording"; + break; + case Replaying: + return "Replaying"; + break; + default: + Q_ASSERT(false); + } + return "How emberassing"; +} + +void QDeclarativePinchGenerator::setState(GeneratorState state) +{ + if (state == state_) + return; + state_ = state; + emit stateChanged(); +} + +void QDeclarativePinchGenerator::itemChange(ItemChange change, const ItemChangeData & data) +{ + if (change == ItemSceneChange) { + window_ = data.window; + if (target_) + setState(Idle); + } +} + +void QDeclarativePinchGenerator::timerEvent(QTimerEvent *event) +{ + Q_ASSERT(replayTimer_ == event->timerId()); + Q_ASSERT(state_ == Replaying); + + int slaveSwipe = masterSwipe_ ^ 1; + + int masterCount = swipes_.at(masterSwipe_)->touchPoints.count(); + int slaveCount = swipes_.at(slaveSwipe)->touchPoints.count(); + + if (replayBookmark_ == 0) { + QTest::touchEvent(window_, device_) + .press(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_)) + .press(1, swipes_.at(slaveSwipe)->touchPoints.at(replayBookmark_)); + } else if (replayBookmark_ == (slaveCount - 1)) { + if (masterCount != slaveCount) { + QTest::touchEvent(window_, device_) + .move(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_)) + .release(1, swipes_.at(slaveSwipe)->touchPoints.at(replayBookmark_)); + } else { + QTest::touchEvent(window_, device_) + .release(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_)) + .release(1, swipes_.at(slaveSwipe)->touchPoints.at(replayBookmark_)); + } + } else if (replayBookmark_ == (masterCount - 1)) { + QTest::touchEvent(window_, device_) + .release(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_)); + } + else { + QTest::touchEvent(window_, device_) + .move(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_)) + .move(1, swipes_.at(slaveSwipe)->touchPoints.at(replayBookmark_)); + } + + replayBookmark_++; + if (replayBookmark_ >= swipes_.at(masterSwipe_)->touchPoints.count()) + stop(); + else { + killTimer(replayTimer_); + replayTimer_ = startTimer((swipes_.at(masterSwipe_)->durations.at(replayBookmark_) + 5) / replaySpeedFactor_ ); + } +} + +QQuickItem* QDeclarativePinchGenerator::target() const +{ + return target_; +} + +void QDeclarativePinchGenerator::setTarget(QQuickItem* target) +{ + if (target == target_) + return; + target_ = target; + stop(); + clear(); + if (window_) + setState(Idle); + else + setState(Invalid); + emit targetChanged(); +} + +void QDeclarativePinchGenerator::pinch(QPoint point1From, + QPoint point1To, + QPoint point2From, + QPoint point2To, + int interval1, + int interval2, + int samples1, + int samples2) +{ + Q_ASSERT(interval1 > 10); + Q_ASSERT(interval2 > 10); + Q_ASSERT(samples1 >= 2); // we need press and release events at minimum + Q_ASSERT(samples2 >= 2); + + clear(); + + Swipe* swipe1 = new Swipe; + Swipe* swipe2 = new Swipe; + for (int i = 0; i < samples1; ++i) { + swipe1->touchPoints << point1From + (point1To - point1From) / samples1 * i; + swipe1->durations << interval1; + } + for (int i = 0; i < samples2; ++i) { + swipe2->touchPoints << point2From + (point2To - point2From) / samples2 * i; + swipe2->durations << interval2; + } + swipes_ << swipe1 << swipe2; + Q_ASSERT(swipes_.at(0)); + Q_ASSERT(swipes_.at(1)); + + masterSwipe_ = (samples1 >= samples2) ? 0 : 1; + + replayTimer_ = startTimer(swipes_.at(masterSwipe_)->durations.at(0) / replaySpeedFactor_); + replayBookmark_ = 0; + setState(Replaying); +} + +void QDeclarativePinchGenerator::pinchPress(QPoint point1From, QPoint point2From) +{ + QTest::touchEvent(window_, device_).press(0, point1From).press(1, point2From); +} + +void QDeclarativePinchGenerator::pinchMoveTo(QPoint point1To, QPoint point2To) +{ + QTest::touchEvent(window_, device_).move(0, point1To).move(1, point2To); +} + +void QDeclarativePinchGenerator::pinchRelease(QPoint point1To, QPoint point2To) +{ + QTest::touchEvent(window_, device_).release(0, point1To).release(1, point2To); +} + +void QDeclarativePinchGenerator::replay() +{ + if (state_ != Idle) { + qDebug() << "Wrong state, will not replay pinch, state: " << state_; + return; + } + if (swipes_.count() < SWIPES_REQUIRED) { + qDebug() << "Too few swipes, cannot replay, amount: " << swipes_.count(); + return; + } + if ((swipes_.at(0)->touchPoints.count() < 2) || (swipes_.at(1)->touchPoints.count() < 2)) { + qDebug() << "Too few touchpoints, won't replay, amount: " << + swipes_.at(0)->touchPoints.count() << (swipes_.at(1)->touchPoints.count() < 2); + return; + } + + masterSwipe_ = (swipes_.at(0)->touchPoints.count() >= swipes_.at(1)->touchPoints.count()) ? 0 : 1; + + replayTimer_ = startTimer(swipes_.at(masterSwipe_)->touchPoints.count() / replaySpeedFactor_); + replayBookmark_ = 0; + setState(Replaying); +} + +void QDeclarativePinchGenerator::clear() +{ + stop(); + delete activeSwipe_; + activeSwipe_ = 0; + if (!swipes_.isEmpty()) { + qDeleteAll(swipes_); + swipes_.clear(); + } +} + +void QDeclarativePinchGenerator::stop() +{ + if (state_ != Replaying) + return; + // stop replay + Q_ASSERT(replayTimer_ != -1); + killTimer(replayTimer_); + replayTimer_ = -1; + setState(Idle); +} + +int QDeclarativePinchGenerator::startDragDistance() +{ + return qApp->styleHints()->startDragDistance(); +} + +QT_END_NAMESPACE diff --git a/tests/plugins/declarativetestplugin/qdeclarativepinchgenerator_p.h b/tests/plugins/declarativetestplugin/qdeclarativepinchgenerator_p.h new file mode 100644 index 0000000..23d8b65 --- /dev/null +++ b/tests/plugins/declarativetestplugin/qdeclarativepinchgenerator_p.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPINCHGENERATOR_H +#define QDECLARATIVEPINCHGENERATOR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SWIPES_REQUIRED 2 + +QT_BEGIN_NAMESPACE + +typedef struct { + QList touchPoints; + QList durations; +} Swipe; + +class QDeclarativePinchGenerator : public QQuickItem +{ + Q_OBJECT + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(QString state READ state NOTIFY stateChanged) + Q_PROPERTY(qreal replaySpeedFactor READ replaySpeedFactor WRITE setReplaySpeedFactor NOTIFY replaySpeedFactorChanged) + Q_PROPERTY(QQuickItem* target READ target WRITE setTarget NOTIFY targetChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + QDeclarativePinchGenerator(); + ~QDeclarativePinchGenerator(); + + QString state() const; + QQuickItem* target() const; + void setTarget(QQuickItem* target); + qreal replaySpeedFactor() const; + void setReplaySpeedFactor(qreal factor); + bool enabled() const; + void setEnabled(bool enabled); + + Q_INVOKABLE void replay(); + Q_INVOKABLE void clear(); + Q_INVOKABLE void stop(); + Q_INVOKABLE int startDragDistance(); + + // programmatic interface, useful for autotests + Q_INVOKABLE void pinch(QPoint point1From, + QPoint point1To, + QPoint point2From, + QPoint point2To, + int interval1 = 20, + int interval2 = 20, + int samples1 = 10, + int samples2 = 10); + + Q_INVOKABLE void pinchPress(QPoint point1From, QPoint point2From); + Q_INVOKABLE void pinchMoveTo(QPoint point1To, QPoint point2To); + Q_INVOKABLE void pinchRelease(QPoint point1To, QPoint point2To); + +signals: + void stateChanged(); + void targetChanged(); + void replaySpeedFactorChanged(); + void enabledChanged(); + +public: + enum GeneratorState { + Invalid = 0, + Idle = 1, + Recording = 2, + Replaying = 3 + }; + + // from QQmlParserStatus + virtual void componentComplete(); + // from QQuickItem + void itemChange(ItemChange change, const ItemChangeData & data); + +protected: + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); + void timerEvent(QTimerEvent *event); + +private: + void setState(GeneratorState state); + +private: + QQuickItem* target_; + GeneratorState state_; + QQuickWindow* window_; + QList swipes_; + Swipe* activeSwipe_; + QElapsedTimer swipeTimer_; + int replayTimer_; + int replayBookmark_; + int masterSwipe_; + qreal replaySpeedFactor_; + bool enabled_; + QTouchDevice* device_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/tests/plugins/declarativetestplugin/qmldir b/tests/plugins/declarativetestplugin/qmldir new file mode 100644 index 0000000..c2582ed --- /dev/null +++ b/tests/plugins/declarativetestplugin/qmldir @@ -0,0 +1,3 @@ +module QtLocation.Test +plugin declarative_location_test +classname QLocationDeclarativeTestModule diff --git a/tests/plugins/declarativetestplugin/testhelper.h b/tests/plugins/declarativetestplugin/testhelper.h new file mode 100644 index 0000000..c6d9f3b --- /dev/null +++ b/tests/plugins/declarativetestplugin/testhelper.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TESTHELPER_H +#define TESTHELPER_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class TestHelper: public QObject +{ + Q_OBJECT +public: + TestHelper(QObject *parent = Q_NULLPTR):QObject(parent){} + Q_INVOKABLE bool waitForPolished(QQuickItem *item, int timeout = 10000) const + { + QSignalSpy spy(item->window(), &QQuickWindow::afterAnimating); + return spy.wait(timeout); + } +}; + +QT_END_NAMESPACE + +#endif // TESTHELPER_H diff --git a/tests/plugins/imports.pri b/tests/plugins/imports.pri new file mode 100644 index 0000000..00f9333 --- /dev/null +++ b/tests/plugins/imports.pri @@ -0,0 +1,5 @@ +!contains(DESTDIR, $$[QT_INSTALL_QML]/$$TARGETPATH) { + importfiles.files = $$IMPORT_FILES + importfiles.path = $$DESTDIR + COPIES += importfiles +} diff --git a/tests/tests.pro b/tests/tests.pro new file mode 100644 index 0000000..4fa2b33 --- /dev/null +++ b/tests/tests.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS = auto +qtHaveModule(location):qtHaveModule(quick): SUBDIRS += plugins/declarativetestplugin -- 2.30.2

    Z6$bLU)QjYD6 z!_9;djAI^R$;8B|Rg!;#(N^pZ&Aj~bWU~b)Mj#Ou!k)9R_n<%_?q(v~qcQ}8mEely z%W`FIRmF}L?Wc_4&xo*~TD{Z)@WVyT%;DNfj`WUph8XyH(LujjSSU;XMJdce;(&1H zSl}d~$@B1zaZR0-o$a9nSZ3~)dvvl~{_Xd=Dz?C+8DFQSWvDsy+roZdrijkd3U6Ql zxA~_pzfkAiAbFnYpcPEsrSr`0>uXTO;{%7h%$Q{NCsA!*L(J|Rc=Z|h6 z&&JZ8h#abRh@JI_+ZoDtJHgG8e$_eYo&nhAZ|~IOL5!>cyhj@HKTarH$yuOsozgcDSFlcPs@dNmgp32C#R z3!%o4o(75a8xBIU8S0H4wdp+xpiJzjUl=sJb7t5qd-02Ll~~SI?&a&0aET|H#`)pl zj7&@-m}C&P0hc?es&)Q*#oCVD8o$sFQc{py5G6W?>6E6*kvTQ8)A@O@zG;hBoc+ec z;Y@H>EVxfaf)0U>J1(kOQid`|NcEdZp9hTeEBcVYhorz(#K^Od zx(mzZv&fWEJaHbLsD{~FxmYKU{4=?$WrmY&*VZSS#|?l|Suy{88GB_AaP_jq7NeYmS*yVvuSAf%mFTK9#5{WJ&X15 z&yc4!&^OJlj8tsOd5~P)`1~i3jREm)CB^+ju6}A2gY$>lBz0;y!|KAQU{fwPpLegR zT4&8N9=Q1P%xqe97nR7NAnb4CQMqWJoaQQKrwjtg|IqYR0a3MGxO;+>5)dgV>F$m} zknRR4r8|_a0i^sux>FD-3F)3er4bMi38lNc<81$P&gI^DAY4_fy=L&EK_rY2Ta4rB=>?8mxR3r4OC zJx@yIZF+HkhCebAClku?f0X2USlC}YJ(N0hg>!Q1i9Dxy@e>#QDbNt`Ga^P9@u&hi z76(IOkxZ&M=EZI*>29}$ts<7{T}z1*!m4^x=C(QKH2P8R+4Ozryc^?#Rtq^*8LjEO zgPU90Md)m)wm+~&((}q9!rL*Mp%=Q-^(bVI(aXvXCAdl&!13~$b$_xxSzDf zx-7zjp8;PvLbEeA&S4sKG@>xOrbUO<(&*5?u+oiZYw0KYY;(4pE?jiM#V%W*TMs0d z1jim5O7yODLhOp{6I(Gds^m1V8e!~GT37v}o7bEh+gKS#&muh*f`8ITLvgWP14Ta$ zc&1;4*OOoa!D=Cl!NcmF7v#oLKM)f$_P|}8mmmh-r@Qvg6O2wG|3VdBKo@epS)&LI z^*uk95U$7Ge7iZt1w^uiV0d4OYT)H(E{1bXJoiq}9MFEo1+B(~eKLeH)N3UB7 zV;gGR;r+>0J}q#dFQ_Q^txl)p!(`gnD0qS?&-xH~VvFfnVV;Aw!)_-v_o07Y6&Eb0 z)AHfyWihzD7llndKTD8jS}9PYRx(HkG-L#H$P&rT=s-MaAzOSN>j4^+t=EY9m0ERt z5MCp0hUJ=z1TGA{WX;%*#2eMIym1ue8R2E~ASk^=obX@7Z@J;zd1^XO{r>hj{|^WX zgOQu!rtX60Zomc?*oL}6e*!6T{5aQC$h9U>j?tn?RBXkNLD3l;kB&v!9OU#t% zp3mVm3M0DlYy07P|EV1ld0S%x_){Tm+XJPcN`5^r$hsz(qE=YBi{H=btB3}v)lr1# zHZ#F)Y-OEwNggiD&){`&%eowiPgnCQnaW;TDk2$V7zNaEU;S}a0*q4fR7zOx92Af8;Eh zNs+d2;Z$5>I|~+sqFoa0@J;7=F$rlzqg|V*@^oKu5|kG7Sgn4v&@QV;*iH~^EPFR% zt=}EU)m2Xq$FHZ{58ejSSc4V|p!nEsBPztdqb*C|eg$mf z^PSmvdktU%w|9YDEQFtbsjbb)zI*b6+`8w@&6Ed);M}*OwWnsgcrth15eEr+TzuFR zbn6Yj@pLfmPCg#(M3p2so~0mB1_Mu7_bV%Z9Q-|!|0$-!Z$cr5kUVuNCVE- z==Hn+0#H=+On;YB{Qg^aaRjP$_WSuvTm%5xI61%&72!m#e_P|4I?u>}ZtiX?B}CI~ zHyrhzgu&@Mp&p|Fgn!we=plkSN3S;Q5`NR2oK-*e_b24$^413{I+q)ApY)2GZ4VC_ z?kxIlqN4tTbRlcmJMEXx(rIO}ppN=Nz=;s{$GY*C!G^%~05pUI$Nf>LuLe{lWVrvO zTxxmzjfSl`81H0SxX!S(%fUsfqBKRoQ`X|4&NkdY;wG z!defowDQwK>cz2nP2&g0VI zYiJ&}h|Lp4K^Td;JyK$61@4G1F^Lm;NId^5j9+!16E;UX0}m|^)>b6%1pC(EF!qRK z#@4y=T$ev*XN~^1zBR9h7r)0u5xaXM1gFohbu%_jSLTR}!(u@#pW=hCjBD+T&2-z7 z@{r`rKv(+cOaAEI8NF7?q~tF@&Zoo<^5Aw|($ptRmkxgsYR|$^=Y-q`$>3L6xoM2+*KrS;XVT`|Yyw&*pKx5GU z712|pm+Zywx)wAElMOaf6neq46sQ7ZRvDp}IVDu4A?H$Kd!pSu#_h_^hDDGp9{pAC zSIXP9CY29ufz8K_`0m_ua%6G>; zdd$M)LA=yQ+e@Ws(r!mQ*mjW{ULkd!Tf*((nbL6Vb;Id%U?}Y;#QVO>of>+w+; zU_`S@Fi)GeTF8y z0~dBn|7iS8vv|qFVvO+Ss(#vRfd;K6rib zHw?uLd7;9_j{dPiFCg;qvRqu&?1Nof^pM4fb&k+&cEf9_Z$A7Fcq;E%uA$ZxA8G?2 zk(`hO&RvWefxwPW^OjwuZ)SJ0I=;`=mkE6kB?0lV^P*Nwwa-ZbGJUw{6_vkEulF~l zv+Er>w+=IVD(;sS9)8#Vo=}|RL2zo9R1p$K-9$S^V1}h!oV+rkuPXBH@_wQ$85sHK zpg>(EAW*mA-RjK6phLB#qij(t^PVZ?M&pwHcHLTm@Ub-8P^(-?$(_Jjkk>uObxu)O ztd;0=P2X;^6hJ3qZCp?|5-t%fxlgN<*1QocgV+Pr*}m7iO;pQ-w9>Jl#PSv0g_F8R^HIwAJPhC-1Xz2_Phc_{ZC5$8{U38hxg>2 z1MZ)3u^*QpEYwLlh_I41QeP?|bkwz7d2zcW@fg1m22lW>yo4|3t`7`=a1``Z{#O$5 zC?XFk^NWOF@}||%?gb`s%_c!hbnka-DFai3iFF;jabaQ|NhG>4(#`jmR0s9`o|w4VM+||!!?@z zo6a=$dru)6QL3l)wpM3B{ZQjN_lQOH*nh@!IJ`fP)w55R;7pu|JnW1-?xS687p*!C6Qa$y?zl5wt0Ma);wg9a?> zq7FNf&rC3kZNM0wlZ27*q2r95-x4XKeb?G?Vj1&FksDO`kT!OJIbt$)j_7$0<}Jri zIT9F5E3@{oj-@TNAV(w>!4>WSMaJp23hs#+bi-v1KI*V(CI6hb;8sn(K5Vj}+E&3t zS-fq{t?%LAb>aj<2FP@NYrcse%Oxc-KsrX4CcJ%21>jvVIHkixF{W`pVndIycQ!-a z9bbNh52z_ta7omhIxD5fLoNg&^225NUH6?i$T$|NQ{ikwLTgN_N$4jVAid-z#;TD*)3?b`Z#9VrSB!?(JbS}D&VW4o_yG&rZoiu5u?=xT zcsq?;W-Ld4Dc@IPv%cAd~)k= zmvc@DEU%>#VSa0}WCB7o9F0U;NlJNfIdM_pUoM0~?{67lR8-mjBI(~@A=EWAmdZ-< z@Onk(i$OggHNg{cQO8g$xT*Bxb_6tq9NOyFvA{8am7 z+~3Bix&HONZRu~9`Y=d(F(kr&lHxKJp5YXT)@4{Qg@U7tOvhGcbJnGmNkNN}@>S{F~eHOTv zo#BQRV;`kdQw;b!7hBPk)0{!Rz`Oj%XQsm}L$*4(GJOmU4Hd73uQ#JK2aR+PB4X2o zmX2uYX+GLjfh5bDMG?GyWCDRAb8v$j9FB&cQ3e)CzVZF62tA6v<6W-KKpQ07P?`tgUab_6G)VJ8xF!b?QQ#uA=UqRL4u5iTaZSHR$bA{0>*k zSbs1Do7^VlQ;LBm0<=EL0SD{04Z+$o3BUBq6O_4imvG6I;3mWD9{5BlBg4b*ca|D4 zJm<4GNZ!f6+-!Z^H3YSJc#%s*a96fz<~HFp^?^N?!;xD|Mwx8J5u;@@zpTE$swN`jTGwM{$U;iW z;A*UY92h3=rW@7@oKta4F`Ky!rd5om(MK`|Nr~}M>}O(Llm1!mP*DnNWPBT7z;=WP zrm(1=B(&>wrF&fN3>qAZVE9pfOJrpI51D{FO|-p)CMG7bI~%yERz^zsw+&Re^dZ2! z;bxFJ17@TRPH)D8^fBMH%o31J+uz3h)C=SatdGd>J9cUU&DB`&zl6{ORlr;2p=Veg zYq!OHoJDW$FA3Y0Vn>t!=MdeDiAM?$G2MJ> zqW{p_krPSO8PcJbxiF_hI9Y)y&Vqc~bsde^v8v;X95f5l@ ze_Cp0LqDuwubk2Vl-o-A_}E6TI|RvL&U5i1iZX-p1Zua6bTFm^&FZ4o(^LGw(+ZxK zK|$f)%FDs{S-XFSyqAuSjw_P0t>6p&x1Hjki7cJ$Q?GV@5|sp`8N|`O0F=%UxI8AUcoXKSE`SVsir^grn^r`_7-b zG%+n!3RcRm=90Zb_k-@&govDHguP0d^0Tb`O82#r#`WS77thHGQ-|bvFi51XMs{y! z`t?BjE|JfV5A*r;gG!WrbR$IPBBvfqmvB_9Cc*FX_;F*uMnDgDf*z4;j9c*IVkv;i!li$@B0dDyrq51iLuhoL}JnTNL5)H`+U*_p~y? zW4=?B5*|zNwY4<`Wlv(X=KC`7%r;sw0Jz9jf9PeFoxPd!t@{3|dT}^cqJPH%_`8FH z0QN3hV4|wMfx}BCpsJe%OeiR$r18#1mZLzY(vfP1%57v^V7<^6z7vE zS-1Ys;i@X9qLov9z})8BDQ7qeQVBtqcIgiyU{#prbA2r6s!$8mvfDYdwC0sb|7^xF z#5xTqcCM(uD@Rey4h3zig%k(b_R7)Zzc1nMtGG&trVuFX^YUW~)1pMOx!YL{6Tu^M zzuMPZsp{L(joQh7fFDgCr+VU~tFr?Opt=;M8KC<2XOW`E)#5l3QZl;MA8Zj?k9ocD zJKIWj*(mN&R)01{@PF>v((S1LrHrI4$J6R`!X0Tcr#(fr!C=hOw~`1Y*_s-*LhY=- zP0TQ5@$}?S=4Yewa03Gaunt!rxQ0OQj@M~hvITB@R%!fJOs%A-{-99J_3oCYr$6^P ziT)YM-U=gCiQ1*oX-(i}eo#Lk-R<}cJq&ZF>5YHVn;A6%jIN(<^Jw+YAx%~v zhB?|x0O?(W*o|tKK1WWg#%~P5Q%PI3Cz#?&R*a^PVLQ#OtG{!IlM4HhS6l<1@$Oo8 zpF`0q%4`}XAuNo@)CkPrUMje%1=fnF4YUD8vRc?5t-7W8juXSH($X}GP;Z{doCDjd z(V{M(^2#h8gIhHYCt&=fxhO!JTq5(;P*(?UUKRdoJpc3<|{8jj9F zNmkdo)qH@=5snK_=K%w?JuIIRm5WP45=8VRsp%#sYtgBO%o8ne@@CB<^_v-S-c@44 zi_TAjPFjNmYndqlnpD?sN%d-@x*Ui{)o^z*sfrRqLpMk9?$ifihE=9rV9Ic0Yn2D6 z0K*Ix|9KeP784Vr%|(C(Vqbjvz0KDx%Li`2h3s2_gt*zcIa>n-kt4&9Q^UjW%bE+| zcT`}--@t^H9T|S&b`h{~p?q?k?0ec^_@%CnOBKV5V4*noD-E9na5D)R#oLP+Q_b9@ zv!Nl$z~hg|TS+G~GFV%D)2;C<5#OVDOClA<24E6tU*OrZB2fhY;Sz=~8I=vV$su%B z-8ddJu8X!xanJG4#uwpJ39X*uV@W6JJr`p!$m6Xyom5nlyu88b?bgji{?}VSaMUo&=lVx2%DOk(?RK>5YnJ7fGfZxAk$J)+NR^CL$mxKyCkNuK z0?&Hr%r;fhft)ReLmHgxx8||h>%V0_wRTsx=Q8A*<0#OJwd*kIeFc+3@42^UVf;Xu zNT_N1dGz3a6D8DyVp)d!Lhm-8UCn$fa!=&w#=14s&99Msgez*%d-0Ad{X^a%wxwa& z@G0U%VpxnCV~6NHc~xPjwanl00p-~sPu|v|r{&-Dy&|t-+hT?M2#O^2vh{{vI`Zu zsoC$y7KwGO-xAHfhqMcmmDxl{EJ7VRc4(#i(tbD=!IIoaAoguc3~zpQhuEJeQq-Yt z@XuEXCIB)AE~o`%DoB((COdLxX8eJYC-7xX93VEb$58qB{ioXSUG}YxY51c!OrL?* zz(on!Ta}TNlh*dSwXQeH1_Z~A8Ze+aW-DW4saHzPf4y2r2t31{tCK_~tBRd(x%- zSmSyc^_qM5?pO1>K@I)Y(Ooau6{Gme17}77-}LO$A8L6={kC^}BPWk!TM4&PWrHKg z9M!}rlGjQt{MYKF9_aYYd?Px0TDop{`Qd2fGrl@i$OC~um3ik!edl;uv?*y=jg5_m zdu{uR=up^2pJHAw==wlIet`8gDqJJS*FJ?Y-^kR&*WdJ%-e(>g@#db7k^qza$*{ZC zC1&vAW9|r;8$b`iJvhCR6eF~(m5CvgI;IAze*}Dk3BrGzEfaA~!ytkvOCvSk(|Mk| zlDN52_^H8NIf+2al;5Y2UC#kWpe%7 zu(J8{(NI4E_b6d_R36cgzTW`x!!U^mR_!G`=Sy*hz$-`I(@I&sTP}g$^k49OX>}1_ z#{5mNQTNQVX2hf2XohZ``a<8xlZuMUcei0}*<5w+Up7xyIM(WEqjeydT}xrXPUH9o zqb@|!ngzq>z2S0hF9XCyjqo-nn!339qCJ?GZB+3#$#@=U;MPqH{Zl=B8Xr788xBRs zD_{yX+o{5p^S7Ib$JE2$5SEueYntuK6d+vgOMH4Ly?gf~)T3p&Hy(`3!dnRc6CYQ` zzO%>izlj42xU192IhU6C2q6eL+N!unwYV&ijl9=JR?iKdCsF*Tj%J~oHwW#_5TZYw{k&-2ih!e-P>U~$$^e!qDMZ{O|k z_+gGe)0&^1Q%9k6OY)G4Uvwl)Ubx3pwokr^d|NIry--Qc;Ts5zui5|3&CUHV@2hDB zZWk0~uC#;KtTkf*ZpeQ~$7h()z8=21?H|*RUIibTJV$)wQ}!zy`+Y$6teXXQ%kx>F zK92FIylEe>l3BFgFjnW%Crwd+fhJ^zLHD>Sp$T`aZj^XJ+o%3%H?Nva4M1XxRtn;LpXEFY6gHTKjkof{P`cI^qBR6n^ zH1EEzX<_OxYJmtl+{Y5VJ}u*D%weF4AIJUe_%B{<*emKRw*gX>mbG`u79K;SfmMgu zeGo$}SW)=tc)ox%W-#RbE%j(P{`CTZxTYkAzm*G;{lD#katnLavXJq)eq}P}D=3nH zTD7?J1Hyt@?1zouiojL|S6GMyBdtDTI{RtpU}aP_>JD>qa`HtgVsF|mzkBE;R_M1= zLK(IA6BFpdPk8AeOM&qhrUGlfa#L5F0*|3fKG{@BV}omL3UtJ9^4NA}-Q7k!104Yl z&icC0oQDW-^CAg?dEaONQOCyfb6|$5FWeUpg+5hOxY9tg0r?cV`oIq4e=ph-;9gtz z24s&lYJUxg54ng4iLNlL{*L@~8d7QlC4(>g+&($zYf(H{w9D@ey#!}!I{}#`%+#+= z@>GTg{$d0Gl5kIYg8a52mX;z=(H(w+Noe&ZM-~9 zNpp@r`y-+q_lM@+(gm5ow0Zs_&iR0(9~(zpxO0^zb8|DoPWS0N7Cj(oUdaz+HY{DP z%e=A>VX6s38kUTMY+L^qOb`Zvx+W{*>h*E8yioOhG4~`*{nZ5?`I$r4pj$rLI%_Q) zK0dnY8SLav;xQn`?KCJp)EZcQ^N!TmQZxELs$6Wm>)8h)e0+RcEr5(x% z57D%#4DIJe78WDIC(bS|OChL(^DpgzQn>6xBf-}KVe~JSm+I6@h_tIN(>2&7sFNx7Y)_)Bn$Cw&1X|o3YGTli}k^j z)c3xb;LW_x6+W&QA%kV%=|$pwq;q`#UEvunF4dVya_HL>Qo_}x-(+67N%$SGXk=QE zaEb=GTF8C`)lb|5m*sM-8b$a5$b*^1wvcAe`?1*p#)`6gJ38SsdstD(+@o$JJI~I3 z7h3k8XjTXM*-#(MkOKf1iP6;p`Y0CB`a!@A4kzft6B={(mzKz*8vDDbz0#pq9X&?+ z7~YSFNo~y4Y@4OJ+Las35w~|Au{!$t--~nhqqK4b3-m+Mhx5tV`5D)Ro2*T9P9XGc zZ60&K*G@Y*467DEgFb4pd_~$IQrf%GI>*;#%a5MG+}|wVXRhI7r1s~981jN~#1(mo zB@2CPH*b3KUjSp7?PWZq8IJq~*l+LrnSZ_L%`OV#2y)THq?9$!JU2_`%s!rd7#dh- zS(bl{>M$Y$C*lv$+9L%*4WSDH18yR<+vsFOp`RerdafgtR<#| zzRF8OUcI)*caTDN>AIiV8l68r}64SQ_pmpW9Xk0|^zlq@FR;Ia27vsn8pV|C#l5JA#~7 z#_opYpRsp6ZC^=#!Fzf;y4X;?`yF)0Y&dDl62kxNvn-UFZr9!8w^yDN8_Rc1RsP>#BzT<3WyC^X8X#5{>@t4QMa%ZqVuh+jHtN+&01Pd}e-2wX^+h<(= zM4A%0G&Hlb5*JI4QPEDntCwf*Cs<^oUN<`z^n|Q92*0F{OTl<-PMednvu!7?6F#y2 zQSxz!E_s0`x6!*N1+RZazN#)7H+rp8|i? z73=sxPa0V;r4kCsbroF_U_O#7$^G=1CuO=e7fKA{;YBG~QHJ+r15;VnbC^g6*Bn4; z&!x|*`DoyX^>jbMxwi4+QSpg7Ai>K?SmrdcfC5$GU^2AS>aRd7_9#ut0 znTf401aZOhl#mb3TXnEt4r7Zsa&eUJ2>{QnUG{yyCF?DhHC^stG5ytwt$pl?O_3%X zOEVQeC%*xvMtbGQde+K`|7uM(MVhZ=q2Q)ffgYI0wB&?aR-XYiZQvL@0$k){_W#RS z1drIG`}G0)huNr0rkgRIkcU*+RD5`&$dS5eLA=86pNZ2?e(>j2o4h1nlFej8^7|4| zl)(pf6b$87xu3K6t$oE|Ee{+QpS4zg@#ujFs)4jY_O#CJApBMurz(jw%^- zTd{0$V>bJyn1c8GO9``Ir{dM!s58k{6G2@r&vO4)}}Wn()x(ZV($tp~VFrEs%8mTtW7c!kTI@TiuO*s-RO1 z96m%R(qpxjVQ6V`5>PzsLv-NtDfO!+DVFWm5ofIy&&AC}TGPCkeF65wsB;<)4w1vF z?do{X&*dKZcPAwtM=LmN^FMt#B+)msU=W82duG`?nc*Ay5hi2YmCQn4v!R4cnbqx8 zY|}4e+FXp4=U#!#T}SS~mOE)pLQr39K6M4Tzst<5ai^GPcAi<^YYl_Y(mxv_4GfY; zj_+Kr_?xip#e4()2t6OK#Cxt2;Kj48SFVcH>|qQ89SF4a^}qSN;6IP~q6eLtl>%l! zei}8_feo-8*?)E&H1uQm!}#uz6MJ%UP~c9tTledc5V&sgc#S4I=|PyAB&37|{qm%! z3lr1A-N_7M{g*ZNycQA4HdtRold z_3WOq_S)hI9r0hjB^)j}7*a=0gD@D19*gXN>Xwv@c4(%r=O#slg(<`K=_ZU0%X<@PFdm%%}f`3K``_DA!e((CL@crNJNEbyRf zWBYdH-;fY^oqy#mQ7AacY!y3$4}dh@75E%(%{=iZ`bC%OgSBAoTn9Y&JCD!V0R>Ch z`%DXdZb0+XK!2f;_o2lup{~)N9&7cm~i}#J^fRLoq~r4UgAcUQB8Cl zyWtpjY@)rMro{Qh9-r`-A0&v|z@H`KzW#ao(Hp}}S%#{cWSJjBSI;0^7JE)4 zJR}*ajA+KYDvZvuj)VjK07UR;=EW{b0InxvN+9neLUD{)irFSGW4=6OtTl6f(^q+EHUXkrqDQbf zHGF$M)^U4g(|#Yk{o6ps6SPO4NB#ElEx-nVf7E?CWC&0&uh3b}4$_NWf&suCb8wPS z&>?4Wnf6+gCg9{t&ERAH|3=W+J#@IUo|M**nw|o^SXcH;56q29F^1gRHcl@_ezpU< zl>(5(!~fr>x+B*8qj{$3S69R_N=LN9$4x=$rwp-QhiYpm4JyvV4WJ#)5HZ!_h+ohP|-v2`K`$|Mu7?j2e zwI2_+b-P8xF46dJ&~x5QW4vkGG~U^)BM&J%eEu!a-=Dd?vm43y^*IX1&`J-M#V*VQ z6H72j_$2+*IBBrw3G&l**6pwoV3wHji>|siiSn@|cgwp=0d!FE`|&ZiC$?8FdPv_; z_^XxQ4Xl`)%yxM0qwXPd!PmQuOY{HiuK87|;R9zc{&rS(ppR-jH` zQ8uo6>xqOn9#8Og)6!06-O(NszEVPq^fS@Zh_CjP?It=lN$${e#&mueFEOh_+pWdOvH^~wpB7eC5l;WT%+Q1_Ke+j@`T0Zwm;VP z_BL24dykIz!N0;k`CCz9tZ60Be~T?}#I6(TZ`I$U6^Ga|j=0anhdEhUNs^`2`nF;F z(u6ET`c{)oEn_J2qhIGtSRFak6LVMNUy|0uqVYzjcs;)mv@t+U-UsW>b+7A&H=gJ^ zNDlb^zO|m^*U~uGFWcWAGi&q4cWXbieQ_A^5BL2l#b(rkWkXWn@o(PR1?R1J$i;Jj zr(PYj&JB&*rUK(16i_lTWo2y6E^do6GMBxgz6W%}xl&P4w87+x1hicf6ME#>8u()y zU-6ma78qll-?GWNCu!`@;usQwd?~(zcjOo3X<1#Hp^yTfUk=9Z(&pwb<^H@`nQ-%^ zhngP^nUDNL;Vk?@`XE!}TzI4i9ehh$|5}%la+Oi{()8j!&HYgP&d|&#(;byc>V(5e z5^}(-QdD5-SyLA*@j}+Bz0WA(%O$Nhk?FN|4uPZPELN$y%(h~`}ff-(&lq=t4o%zJtQU4B}b=S-4hS9`j~?S zPJY$NZ+*iqAcBIol2KG2vmS|)iGw-Vpzl4M6}t&`q&F>I(pY8 z!L%>1I9v&tcKG8D4i2taquRVcee(Q$P=5Dvyo|K`*tFB)BVO&R0ZLnpo#u%V%HF8S z@#Dp(_E@}x3s2@g{sdH~%bx5~?kv|a8sH6RMna1l&P|UB)@N0jk<1W1;X+~F{i|LT zGTBxHoHT&-CnQW|fkh%6 zazl?qMR;5DyavOTo4vNlPy(P!)?yJJs2db@UjV>0)O(OwwRCY91*RH?_m{D$o%nB@ z`Tyf6ZA~Ur3Nmc`5~1wR%TqZ8ji;HBAJ=8kBBJVaqAu1aYR@LRXc5%0-=m)%sbmD- zMh~3v2P~wODJI%7mKz+QuOmwgWeQ}CDTouUV&ui#+&*gx2utPpVq5E5bOQv(CfxtJ z9`^5oc(uk*##rF42XA=gS-3yLx5EjVkzJCZUvOFL=AbfD=aOgZA4eVu)lByjS|mK@ zm2v~8N;^BOY$_@vvj^*be_HC^fdrseHa4qO!lyzlAVcNmfbdpT)O*LscFC<3%67J1 z-t${%g;BMyPQxKp1%TNk+2!cjKg|kmv?u!ksJAjD!rCF{qwhEc{+H~6DUNiC68IvqeV!!e`(g#s8B4kWOnJ|3hpbDY{w+Q?kBw0iOZ9h}wpTG<2rm1)!-_QGd05Ko+!^pp#1Ir*Im48KB)prs*v*2H zH?ZE-sI14D*l(WkKQm`|s_$VMbyEr0rEop@^TCn(hexdz+L667S?BvE%$UE<2^CBz zf)V_rDh93@E7WIq2p@WeH>6+u#UsldodBa!uz8DWC2-nkI6pXS!+DdSv+Fd`5LM!2 z*6X$C9u59VCTz!dx*FG*PtMJ)Nke@B?ZzwBotZ3ls7fS)t^|WGqJqiC#|xQbV^D`G zwc<_Gep{_%-RgKWz`|3urff?J#4mY-0cG??fIy?%y}|>CG3JDtdGC66%o9C)p4Ruvd-yoy zi2(JCKE2!@m?qE%ruJ-(ft)ud>}=RbY+HNW4}525S4$fkjQx)Dx#s_7eXd>S{}u<_ zwX7dXz6}_38J)yL2V>ixJ zG?~^~2qIsWfw4<28>?GTlW~a8=;w)SH1JC`8uH^rYRv1N-T)uY1O!UZ9z@@ zogRM^pDG@dg)Fumd?(Qq{5*{-2=%=4W0$b+NKc<}EWRlErNVp#fBVq9zkF3!>-XY- zNncurwLqS@**GQSdzkECQ2)de%$pczY;|N!O07PEpBj7LhLlA|J4v9Sz64GdoNg*{_S;f?~A%ZunyVk{*HnO#z~RVAte=T{wZns=gAi@Cg z^&wqs+}lnA68^yn`;dz~@bW-ZApnqW{ePDi*QKu?@mTr_+PN&zNdlS&;7U;6(gqFG zSLRHcX5ASWm;HzPdt4d{baabAz(RtZeM)eJt!Dku-SLe2XHOt;6Z}f&$>=4V^2Wx@ zB0LG`$o%y<4J#`KdGgR0TB`BsGv2}=^DQ%R!tl<23PB987*54F6)57;;h*#?xIrMK zN~1P~zi;;iu=nBLRo-`i)O}6J4aVEzP{p%G1@NobZ3|e<^Sh!5JogC_0m)%G$a<%P z2cjfX>jFX6jP!6JP1Wg%2P647-X54-3mPqh*$8Re0g1zbKmv%zb-5Y*l(AEf3S;Fbvgy7f5e1rRuaBR(3M&#=FbSD#3 zjTBdYVY?rCfS|z$Y;yH*_yAh~#{C!nqYww_T);>x5p3l~Fg0lmt7-=yk-lC#qi+Ykk@kaY#>PJB>-= zd#M}{Wx1`&)A4q;X&)~oX0HcSzjca@K&ImU-A)3e#eX3oLY-m=>xJ%)is??U&Q$fQ ztEnx3eCKbvnvxN{G$GWw$-0=n4bwDQmKJb#-UX4+pyinm6WcTo_b%K~$cI+y8@V(f z^YXQ1=9NR5TW#aZS{}4Uf0PMyj6zx`8I`+P*Q=Y~?<|sWTl6`raoZ;Jqi&WuV4MT5 zd4w3*`JpB=JaWnVB`pm()v2HgbaOn2Zv3NUnDCq_vE@JY(Cq9=F2IWK9yVV9r}H`# zc!wffkTC{#mE>qbx`fbuDDRgjeek;XvS)t$_^}w%n~&Djl#X~EFBwLV-%jzTAk%+S ztL&2?<)8ny?~4+U8@1~8JvZ^b&aMaJN61@AvC3mzYBlpoO;*l_@B_>>A+ZE?Ad+Y* zRzKiXo(&F_}GJ90Hm_x9KU6Ec37ssy%Afd#h&io$@39jvETErmjw4 zfEaBwbdMInfsFp|lZRax12S#R^}MhJKA;51iT!_Rea^@N>lfmerJ?kY(b5*6xsTD9*eg2pjN+taXbwU8fc_vDIwxCm{?jP zY6zFOY6s{VC8 za_)NrBV_dOi;VBnJoqC-r)&>K{xp^>6{O<{iSt9P)g&qfOpujgvi}I(iD4=E?{D}J zal%mZjG$^}0ib>^`d`Zr-}^{Unaf=fnUKAOGtKxU+uI3sHOZs>t`;^Bni9vJ`3UO` zZV}f5oXJh`{xe)>e`GF{@~6<%@Qy${YuX4_iGfk66c_VHo0E|n`r(f2=>2j|vS4zu z?&pI--rr#pFH6R|c6RXmJ7VDW508I2JU9Mix&H?c(~tU4VA0ILB{zcYm8oQ^3=@5B zB)O7Jb+BZ}NLPq!pSShvF*4^Yn+H~Q)R8h+u-fH-|B}4ssab8I1#bC|S_Ge;T+UF^ zlf)0!3VB)Aa^-1^K*DrLec_70sSzgHMwdgVR$xx;C&T@@nB zjlu$VEm;o2l8?vs{i*xl=@6LlVD=_|-)uGY03O0XKw4wGST#g0W zis^Zz+MN2pkRDuA5k>uMnVPKRd3C)S5*E`xj$T1piJBEx(PS&i#7Jn~0VC9|bFB_T zK6AeT`!h!Sxptf(mb&sBys6Q*jmZqQ)j);~mH8izi zma%^#PVa-e!lJR{WD%gcd*yXdN`J+Q@kSk&?w)m5dWdqvVc?m* zRW~cjiv;gN*9n^x3n^nPJqAiw-S(Y}Uid6Uslm01o$ac~+%cjBK-Z3EE4fJ{Luan& z)LHsd0Y4VY`I8r|Z?K_XKa?{Z__|cmyGJoWPBtwvW*r)_OFzdIFhijW1A*1Y1@#z2 z=M($gnVs;ILz&D?J5QREuG%TKaAm~jikV;*c~H70ha}BkynSdAnT}&AOC#U;suG`1 zYlIxW@~z|N+d1$nPWMQK2Dj25fS!9rfmjQb;D{4s(>%5JM;bEa^StReLNka4J;v%F ztnml2zoAJV$iN3}fdnFurlD=s4;TTAT10jTtIKRLlCr|>?zx(|YnP26vV~wz@{r^p z*Wu?A8qwez&@Q{{ddkS+G1F?OJ(gB-MHmL~rJu&~*j4Mzw5)<*YU@kd9+sh9%n!}65(Fywaa#slfKPoRL?P+@pmlx~uViSmTa!!-rEZ`Ej0Yk;6Athiqqj&>d_*%gd@r)TAgTQ`wEiIcZ~Mn{wgaE3NAV6q+jhZoAkR&IMx zQqy`iH8-cwG#ehK!%S76H5QG76d69k`f(?eAi!6Hwt(KeNw%_e7=0NrU+(Z@_{4?D za(yMkG!re3$@Jjr;TAsHj;*Wrgjv3z|B;w%vZ=#6&8Hfb$1jMHRM911mc?Ca( zJkf8UYH~jFhKjvv*u21w;ZA4Gk1tA#a@Jz{LDB%Or3jpP<^Rxhl~GZBU;EC`(v5U? zgM`%3B_K#Q0@5vALy3ZC>Xx>$;>uU(#YSy?$v5ilo#^9)6kij89nX+$YhdOo_oKdV?tq?S7_fvgLZ% z4F5L?cgZ0$qh}ugalfRr?xS1n%ZSs4_?h6u&g( ze105HlSk^H#aZu7{Tu-#IKN-L95n#5>VD2W3z&Dq#O_PAxs&C@Rwg^6`C}#Xdl`E0TH zv&J$#F)p{NYaX0=wx#LIgiX+aXR{-x(wzKzAzL}`;QOY}VwFt|a7>V906qPz#s$$U zH|<2+o|&h6yE6%FHjD#GJ$te(4^l7 z^6^|qo<2=&Hhmgw&nkrKkvBhxY?EL!K>lzm=U*UC`}BX>4?&e?5wNG9En+@*LX|df zVjEF~!23|1{_a3Cg4o(AWT#*2{pzt1J?cNnYt6I8tQr!H1Ycr-eA}~VilrX=d$h-N zSGDf~^&z_2U2I<)b3Cwjl>8&>ay=_;v=mxPhiVL*F%Q81ZYqLtt#j})It9{1RPpon zWV89Ssi4*f(FN+Q_c-zoyV~RQ6U*N`Fb|4;d+teITwGl5b>~;&;8f8#snVTaM0d1R zsF^+cOuwQ?xn9feyxfN2+lzR~Q2H&g)B&qi7%}C*?BBm3943->npKp#qCc?&g)o*&oAr>1!`ZJH+=hgYnX0ZV zJhjjMVS6AusxiWMG^7X#@MbNAySgdG$uE%L+gUfN-%B7aXSPlHpGrml2HRhdQ*a=Q0?b0teM9SUVIHk0ZOSQqu50JvGZllWXw*G*|FVDMh@_ z?{Zh_2lw!O`jtGb2F#EijRc-6a`8}gk*Ar+q?fkj{d>XVLf>P!zrUn^=HgFl1r|L_ zFfi-uyG=W6TfbWBFK?DyuWi>0wj$oZV4Zg3^D@}zhFQ{DRjkP>Aq*iu62pHK{LwXx zV*fr<>|Qw7FTo%nWVY~HNNVBDSO0oNpH-6cwWGJ*{^z@C62TT1O;cs_m~>ja3R&W= zTB9-cGP;r^9gFl3=Vd7(2ZPA>IUh=&?4;=C-8bG?I^jnT6Ywvj6Jf1PzzckbFJ7#(a-% zLcEOP$L};e10O3H1)zxZ7i>LvVVk40hV}x1(^aQ4k$cBYwN|MovBQ+^bRHZxB6<9& zAoFkOX>F)nnYMDE_}=HkS+fiUqI{ptG2HK1Tb8zW-D1Sq&oi|TT5p`kcPtjXax;lZ zKlz6AE!N!F#iJCyH4;-NC`!{NJjU5W+-Acj?yC-FriwD$@M|)dwTWd?@`$||a|6$s z*f%{1UK+ar%jI~)1NrSouVaT_A_Vdfgh-ZHAN9}DH20Uy0;bITe0A!YNd{)>u6yV1 z1f)`V>U#?k=zCiPKD<7o-0XdpSRg@pEnqILEs>dgEG(Qse1h?++<^O^ZCqB6X7ug) z*|>v!Fpa#)pK^y<`xwTTEqD%5+o^-(TI~6M3e&Y_cYlb{-iB=Z-vs})LK+*$i zg_=;2z9@1cyB7n|NtKJ=&q!IBs=)>rpYpGj(>@X}>kH`wd1|D+kx0m!`QkE)yVt0V z;m3McjQ)bD`CEsXXxR3uLN;+gj5;jAziP z#VFJHcKa9YxSxXS@9*DcXHjQlA_|Pq^m<BI(x5x0wAoV{ zCy6lgi?}z>kF#c4-eS1tIO=1Dj+!>#;vJs$w&n2E4;B^Ds)4P6BjW@QyDiTuGw`5~ z)7ayG6y}}f*y_k|4793#jEngPzOI)gn|wL_Vtj7N7XvW3MkV@fs|&o^SN*U;Q{7Nb zgAX^IBHFLmknl!AecFepf7ZC$L_{wv%n^BpE3r&)n|sL9GpBmV@vqvfpHKFXFoF40 zAjGVNbP~vm2l9zwg+_QGi9~nVi(o`%Z;7qr8koHqqX2Lsngyeorv1OmVxlI)B)rgUkez3Mz?{9yG@U{sT(@@I;^h}dz zI0XF^2JOQ>vsWhsdZS;5R@ip+%0pkyqnY&ST?{qnr(|iTr5g^@QJ&d^^q-jM=16Cx zSGJzd)2-eJT6{wOcU|UT61r$VRpO%J*QD$}*S0m5Kf3jHF5UZI0{BQEFaR_qkbf5v z4tmnIV?Mj3%s)DEe|a3))Z~rPZe@LKrqkqYyW{)`@iz6sKiTtDwdu8}pt`ek!wblS zy*eKZgXL*q-h&9r!pQ{NTLOPCrPY5RZb!z$ZB=45RbFUUly|5n#bH>LYa-B6LbRUi*=Bk&l?rI@z}-qSv@Vh zQoaKRgDGjs0iJChN8@4oK-cs);zm6l7U>7}BjUZ+`?OEiYM2WxV!RMNDdLMxXf5$u4R07=n3N4!- zSeWV?-Wf!oEl0Be-yKm(-oxQPxPf+b4E}BkEhx?XByz0jZVP@MIA~cGO)@ESpTtMKc1#(?+fGFmG+Z*A&r}> zE+0B))h3^zZL!yiz$rQ2tw(wDpnw|WbzN(F(J-Y2E6*_85aX$CJoG;M-7179Zz6s% zchc#2g9HbATT=SH1+4=Uv<{woLOAN6XE4FDc&y+3p}k7Zt@xbNJH`l_{+}*w&+epC zpov5O@P>ncW&w*_4t1o*F5;PA4LT=MQ#f02SQyse`HdcWJ2NgtK1HErY4 zxP@Rug2@xn{&ZPX^EnJb;}hj86h%oX48z1Wh?*0Lugm=c0gU(zoY=kW+-5HB^P9*G zbfR~rT;qHz5F~o&twm+8Zt9*VVXEsmaG7ccdT_B3$r$(-LR*M-i=M&wt~f%!JSB-J ziS8#;A;`jmgbgNjy2p$Wh0X;2ami(p!~R}UJCVv*&%J4zv-#!$n!_-ZbAA-mv1kws z%v63kq$zRv>&=Fo5qREUA#)Ffg}$I`DtQbp^iD&!$nb-R&QshJ^)FwfXh-KgAni%} zXHuVgTb=%RV;t)&I?1B^&{w!PM9AE&I9tEf$yBZss+8h1_|}K+#*v+T5ols|aS$OA z>$W@17qIdv7(3~83*Kd*62MNtE6Vo)jm8IMzwyL*+~MpSt&-CBc;LgXBs=rN1n)>! zUOMl4g(|;a?7yE2HaDk*F6l=)E>{;N+G21i5*KqDnCWO)B!-2`5zY9s?NS|d+axm< z>}kuiZkJeBZ8B{}l2`3;pE4WNdfLX@^asQm%QQZS4tb&0AHxSdSDkQ9yunvv7Mxtz zEfEj6IMgDiw-qdafFKO+F)59FMrCF;GoX(p+UGTO^-UAtn$+Q4IumFy z>75Wk=S}oh{f=O}x3fgz57jSVTSlvwZ)i2mV|kcM@dcLOFgVwRI`Fg@Y^US$1|_O( zYU<&;?Spmq@l7n1*kO@g3%T%O0H(-5EvAgp+4!H^gRc1FAiR=p0$!z<3`TQwEN0nj zOnf7yGG;s-(c-WBRsV#ZzKlSWrYCyhJ8;b~iaU>~EV_~GZKLQZ>Bnx0dP>UPZR7sf z!gZhj&Rx8``YY9KefO`0h7sx^hXZ1JW*MVxy|WzhIh5^Id7LTNGH;#7AoMa%xx!WI z+n46TH)&X=r@-NRmLcvX^2REGtdML}AE5yhcSJybSwf{WQoIUOgf#Y#! zeh&n4V)md*fHvw*9Qwa*9+X(dAkX?CLE z%{@OwDsSlDkp9ys$ZB5g!H0=ZB^D&%%anRNdfU1E4_WWai9Z6S- z;*SiNKN5Yf>f&y(wt1okS8aZO3P?b9X%{-@0xltvVtV@nS4VZN*23^|q-m+mKt}o( z;0+IUc`#DYCV25}L8d$8pTzYx)#xgD*J>gWqS67=#`v<55e8?C!dJx4E{JB0#qY(R zDJ97kLAAvC2KH~3PGVa6McEJU_vI9jSGA5YGV$iVNFaUVylwKXvZXn&Gp*EkfH|kc zF^+O=+t8el<1`g|h){UhQ*-{G)O5y^x67_J*&LfZWa>OX)mFn$x#ki@5w4@3z$PTx-k!Ky&vE?oX29`KRKN(gc+ZQ)D=7&{`aAWbpYL?B$g!rroF5U{ zKl~#@cIE?X*B5QvS4R!K_5y|Zgh{U|hhuA8Yov+}>t<+cxDBiuVcfCAp$1OH?z_bc zo{+NV*E)CqByv zsSQNhd5)A)`x)t)c=uK-l$db1KINP9ioVzT88@q&qO)BtzefY(Q%^)zjHT`dAK6dR z--7qVKRMWLAz*ey0b#f|(j^OTxD=2mT|IWDNVX?}BzbrDmo2wZrKia?DOGKU$A+Rq z8k$8IlT8WxbI-haH5`_>=gTv}mZYT;b`|iEyYTD@P%en+@=fIkci)buj(+A%`m%j4 z=RW+B|FYqvK)9z}ME1=@)~!UjK>*J_573k;n5>=2OQF{1q?J5&9#5^4 z&irzcGPVSTVNi}$K7XU<8#ACw4zDBpqRY`_UmVlN7^*KC$C%bJ-6ez!nd;SSoM!DR z7%@H3C8=%20E&^Njxh&hHdmRno_cT}_Fq)usFdZ_zDmO`xRoLd(5Z={)1FGu!vA}P z&8GT8Q#S)MSDmgo`aJod09ouM#_@zQjA=6@O2pPP3;n9nc>#kRUDpj!+)kR=pZhc& z!c zL-`}dx8`DrCC>niAABg_Ed}T>A>vPDpTsq*P5U6d@*W&p$0OVmzAh+Z z^*@~+-;W66Y?CGlU!X>xe{~?YcN$KAQ7olG(9UE934TX%FMp-aN=hIto%Fg{&)RZm zxCv~ifKr`J((z%hAVzI1HsHB*0y$q_xNhA~n!W4Ne%B(5T0HT*C2vC}Z41sqBAeWb zeiWq%X3jk_`>`iKUp`yZdLNclTYRJ~oR2rIUBko?;)LOKRwa#IsaR+qdAxzopAX38 z)_gaI$wlOuQOa(`THMu!Z;NZogp@pJ&NtaF#nM2d-kHsJ%FObhAhdJ?b;DcOZZR!N zlfJX^+^@;ynDZ2!^TY)E)#aKD6J%_&M7(CbWB;1Cp4Nim@3ytw7WvHdtX=yJ2$`FhM( z(D94z@Z@4QvRKeLZHmoW-%a(&Po`q|P?eojl`(d{msSmvi|$2eD#ao8+uVc4X?VI< zX@eMwc2umH40d+ctWrB&($7ZpRQx61%%@~da51&Wp!d7!A}oB%W7bHl3;wnwYNum% z(_2cSqw>9s2)rsLW6WYiYQO^}Xo!Oe8=XVh!py0kebNgQk>}-!B&F(ZPW8ymzJ%<@ z-zRj?V-2P}rF35B5Y4P+H8vTVs1|%hi494*_$|Vu{OX&8Kd4*!*1obrpUt}MiKN@f z0=eC($!NwuL|%FhR$Lg6W`>6%0-%p}hD%xd%H_mPZQM}Fdk;C^tspk|uiKb5IIAt! z8uo0lICQ?6B&!l@vx8quuS=26?3T>Os{95ip0PvqT7OVebXi;u$m{LOU3>4O5piE9 z9Y{V#n}W~7!>}ia7waU?Tgz~Po23HaR|sC&GX|(K*{G@+&{*0*;v>lPwo^Q_w8ToW zKd4M^pLT%v8~(XTFV9}H>W0}<8aVywg|9Ski~I;0!~AW0LB}c>ptD5UmGfl*bJ{G1kg@k$uw}VU%WRdcm}!@n;7~VU+qE^Yw9!yIx9L)F|B1k-DARFLap0+pbPF z9xvpK4eP^o9Zc^1%%F#pps!=MXt*+f!SgIS6Y5IgI2J5>I6eL3^PRF>6KUJdVg#p8 z+;TVzB%c__CRmMdO|7-gGQ<@675bWwbvt>{va5G7=6dDa^v^fOxO0)XHWElNLsQ=> z-VMahtUgS{uw$hIWt;^x%2sS~N~HJk$84PHEkh*hl?3_fW5GwJNyI-oH0A#xM+m-Z zvf)Ns-IYDLW~rf)$4litB9?yHn`O6>i`{`Yqa6=5;Noj%IwYomS5BI#O+Oc9y*43$ zbf23UviF-7Tfwr8_qA;0zZZTb0rpH&Xtdj-D)&~rOBeh*~M*i*epVg$E&# zL@7)>5Jxc3<^H#67+My(dopqxGbY^4`TJQXJzfzFOrE6DZ~i3s{Y)VcD1X&fZjQshiSN?y^ zWZgz5y9O|@bK5u~avLe*>oPHJqmcV#V5yeu)mPt6lLK|A?)d4{*BJ89-Q_R!9PSz> zaSJ~|b2C)9$BjxbeRA)zdHN-|-iOT)74~XQ@bnZDK<1(5QY_r`v{adE?2Is@9iD#d zQZp8yyJ6i1O_O&vHH!8bGNJ`pGXb+Wmg$t3-+Y0k%9Ll1rYK(cI@eUD&?fOov|POy zisP^IcI+PGgz;v7gt1j9P$HFc=vZ4P9g@P&9xtfgiu0zT=#flC`cI zzMl)-L{*kCP#V4mN&W_|?afUSqViv(26?17EIDa@uB2%xCn|g-LPK=?tY{n~$A3v) z&?9H43~H_8lJR(U*h8n(phVK98TsgM_dJ-A(xC`62$;#0d9k3`rbxM2dN=EM*VZ=^ zBH9mVI^sL-2BY`6hB^;MW7WYmrlUE>D+(r?6JvtiD=P#;W+HKzq}*8(_Tap>61`cT z3Sf!lvHl)mC7)&Wfx6x;2cx?3yja%A=Jt7O`OI>i_Ji$|Z^euQE#38QZ)Em&+{zZ* zT5A{qBgVj!c(tx4Y)afP>KpXXecjC!=?J65i`0YENKSNReY?oDW}P`_I(S@=Kh*Ba zN=JmRas44HSd%+^&dBJgb((b@YYm*P^rXnk&*KFZnjb!3jN4<6?>q~);KHPXAwP3Z zC+Z6?E_kjr7(8)*ksWsR`J)m>+DK`*+Zzj>Tc{Pcu<}@!TK3Bnwo%-wTAlJct&g&^ z`Fi7=AHsF3{NAg2y%gtb>@lM%43B$5_a_BMr7=E66alm2g-QPyq-msAWfrt3N2jf6 zW80*%?rtbrDo1YAM9@GsnQr~{QA@1RUKLP;w@a4cK~<~twr%hDxbWafP_5PPDbd6H zxDCbuB!7La8~!{%WzZRwqeNmy_ur0efq&IMD=OgK-QAi`0*Hqpm!n|Nln~o~r-%(2 zEdy9;2LksfSou-_p2Y@9{GiUCZCu6-5!@GxTH~SYZO^!<8g&;3EHkm37U@w5B)o{; zyT=GWKSL`pn|}SVEnN1qs*T;Sg5Q8Q)CXzFGAj#m=mw92G((>BK$PAK;8NskmvWVk zCy>l8B$Y|JSA%7f@j+#hzP|n=H5N-PVSmPm4h5?s!_s$0M||Tu*n;HP;T8o#T385H z-hFw*K~e2^j)dY(QHqh>@wvBClX+pnr&`F5KZnKFKkV1m1cbUSn!5+D&1=gq(~|Pn z){(6OIWqT-=lV4hi=tPjUP+Qpv6Tu>q*qN&CHohp)mkjLoZhMpR!R@K1)B{ESdJ&B z@q{HoWrz;s_rrIat{Qw|WXLe-DqUyXJ_?$QN#30;jr9pWsHWa@ID~)*2;79Hk= zQP}spXxbUqCxa|wx{$_l|=YJQ32T9s(w|EkMXwc4!!7|vN zz{Y`Tk@qnn(`2xJv_cuFYH;vhHK-%Biv{LL(8UZ0VH~pIeTA+auu8=_T>O&T7%XRS z%2aUDJd+yCL$I#fdWO0ub^oZR>v633SaDZ6X<49uZ3>#p)5|zDU zR=?8qhO%wh14heGCS8-7etbS}n|XshzMc>H<#{6sPxivr9OT$hj!oru3-}i5?|e=~VzR800C`wC<&!y(awFwrT~P`o46_ZoqCmK=36y~q|8g&}h+<>AO90rt(2 zE}@UV`u(fE(2~1R@PmmQbE%$SR&}5Rv+51RYSdrbU+@Dj_N;fCMc%%%7*f%}g!

  • w8HR%LZ0Z{mv6 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 literal 0 HcmV?d00001 diff --git a/examples/positioning/weatherinfo/icons/weather-showers.png b/examples/positioning/weatherinfo/icons/weather-showers.png new file mode 100644 index 0000000000000000000000000000000000000000..07489aaafbc535d764bead3870e422ba237ac45d GIT binary patch literal 76340 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